001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.commons.compress.archivers.tar;
020    
021    /**
022     * This class provides static utility methods to work with byte streams.
023     *
024     * @Immutable
025     */
026    // CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
027    public class TarUtils {
028    
029        private static final int BYTE_MASK = 255;
030    
031        /** Private constructor to prevent instantiation of this utility class. */
032        private TarUtils(){    
033        }
034    
035        /**
036         * Parse an octal string from a buffer.
037         * Leading spaces are ignored.
038         * The buffer must contain a trailing space or NUL,
039         * and may contain an additional trailing space or NUL.
040         *
041         * The input buffer is allowed to contain all NULs,
042         * in which case the method returns 0L
043         * (this allows for missing fields).
044         *
045         * @param buffer The buffer from which to parse.
046         * @param offset The offset into the buffer from which to parse.
047         * @param length The maximum number of bytes to parse - must be at least 2 bytes.
048         * @return The long value of the octal string.
049         * @throws IllegalArgumentException if the trailing space/NUL is missing or if a invalid byte is detected.
050         */
051        public static long parseOctal(final byte[] buffer, final int offset, final int length) {
052            long    result = 0;
053            int     end = offset + length;
054            int     start = offset;
055    
056            if (length < 2){
057                throw new IllegalArgumentException("Length "+length+" must be at least 2");
058            }
059    
060            boolean allNUL = true;
061            for (int i = start; i < end; i++){
062                if (buffer[i] != 0){
063                    allNUL = false;
064                    break;
065                }
066            }
067            if (allNUL) {
068                return 0L;
069            }
070    
071            // Skip leading spaces
072            while (start < end){
073                if (buffer[start] == ' '){
074                    start++;
075                } else {
076                    break;
077                }
078            }
079    
080            // Must have trailing NUL or space
081            byte trailer;
082            trailer = buffer[end-1];
083            if (trailer == 0 || trailer == ' '){
084                end--;
085            } else {
086                throw new IllegalArgumentException(
087                        exceptionMessage(buffer, offset, length, end-1, trailer));
088            }
089            // May have additional NUL or space
090            trailer = buffer[end-1];
091            if (trailer == 0 || trailer == ' '){
092                end--;
093            }
094    
095            for ( ;start < end; start++) {
096                final byte currentByte = buffer[start];
097                // CheckStyle:MagicNumber OFF
098                if (currentByte < '0' || currentByte > '7'){
099                    throw new IllegalArgumentException(
100                            exceptionMessage(buffer, offset, length, start, currentByte));
101                }
102                result = (result << 3) + (currentByte - '0'); // convert from ASCII
103                // CheckStyle:MagicNumber ON
104            }
105    
106            return result;
107        }
108    
109        /**
110         * Parse a boolean byte from a buffer.
111         * Leading spaces and NUL are ignored.
112         * The buffer may contain trailing spaces or NULs.
113         *
114         * @param buffer The buffer from which to parse.
115         * @param offset The offset into the buffer from which to parse.
116         * @return The boolean value of the bytes.
117         * @throws IllegalArgumentException if an invalid byte is detected.
118         */
119        public static boolean parseBoolean(final byte[] buffer, final int offset) {
120            return buffer[offset] == 1;
121        }
122    
123        // Helper method to generate the exception message
124        private static String exceptionMessage(byte[] buffer, final int offset,
125                final int length, int current, final byte currentByte) {
126            String string = new String(buffer, offset, length);
127            string=string.replaceAll("\0", "{NUL}"); // Replace NULs to allow string to be printed
128            final String s = "Invalid byte "+currentByte+" at offset "+(current-offset)+" in '"+string+"' len="+length;
129            return s;
130        }
131    
132        /**
133         * Parse an entry name from a buffer.
134         * Parsing stops when a NUL is found
135         * or the buffer length is reached.
136         *
137         * @param buffer The buffer from which to parse.
138         * @param offset The offset into the buffer from which to parse.
139         * @param length The maximum number of bytes to parse.
140         * @return The entry name.
141         */
142        public static String parseName(byte[] buffer, final int offset, final int length) {
143            StringBuffer result = new StringBuffer(length);
144            int          end = offset + length;
145    
146            for (int i = offset; i < end; ++i) {
147                byte b = buffer[i];
148                if (b == 0) { // Trailing null
149                    break;
150                }
151                result.append((char) (b & 0xFF)); // Allow for sign-extension
152            }
153    
154            return result.toString();
155        }
156    
157        /**
158         * Copy a name (StringBuffer) into a buffer.
159         * Copies characters from the name into the buffer
160         * starting at the specified offset. 
161         * If the buffer is longer than the name, the buffer
162         * is filled with trailing NULs.
163         * If the name is longer than the buffer,
164         * the output is truncated.
165         *
166         * @param name The header name from which to copy the characters.
167         * @param buf The buffer where the name is to be stored.
168         * @param offset The starting offset into the buffer
169         * @param length The maximum number of header bytes to copy.
170         * @return The updated offset, i.e. offset + length
171         */
172        public static int formatNameBytes(String name, byte[] buf, final int offset, final int length) {
173            int i;
174    
175            // copy until end of input or output is reached.
176            for (i = 0; i < length && i < name.length(); ++i) {
177                buf[offset + i] = (byte) name.charAt(i);
178            }
179    
180            // Pad any remaining output bytes with NUL
181            for (; i < length; ++i) {
182                buf[offset + i] = 0;
183            }
184    
185            return offset + length;
186        }
187    
188        /**
189         * Fill buffer with unsigned octal number, padded with leading zeroes.
190         * 
191         * @param value number to convert to octal - treated as unsigned
192         * @param buffer destination buffer
193         * @param offset starting offset in buffer
194         * @param length length of buffer to fill
195         * @throws IllegalArgumentException if the value will not fit in the buffer
196         */
197        public static void formatUnsignedOctalString(final long value, byte[] buffer,
198                final int offset, final int length) {
199            int remaining = length;
200            remaining--;
201            if (value == 0) {
202                buffer[offset + remaining--] = (byte) '0';
203            } else {
204                long val = value;
205                for (; remaining >= 0 && val != 0; --remaining) {
206                    // CheckStyle:MagicNumber OFF
207                    buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7));
208                    val = val >>> 3;
209                    // CheckStyle:MagicNumber ON
210                }
211                if (val != 0){
212                    throw new IllegalArgumentException
213                    (value+"="+Long.toOctalString(value)+ " will not fit in octal number buffer of length "+length);
214                }
215            }
216    
217            for (; remaining >= 0; --remaining) { // leading zeros
218                buffer[offset + remaining] = (byte) '0';
219            }
220        }
221    
222        /**
223         * Write an octal integer into a buffer.
224         *
225         * Uses {@link #formatUnsignedOctalString} to format
226         * the value as an octal string with leading zeros.
227         * The converted number is followed by space and NUL
228         * 
229         * @param value The value to write
230         * @param buf The buffer to receive the output
231         * @param offset The starting offset into the buffer
232         * @param length The size of the output buffer
233         * @return The updated offset, i.e offset+length
234         * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
235         */
236        public static int formatOctalBytes(final long value, byte[] buf, final int offset, final int length) {
237    
238            int idx=length-2; // For space and trailing null
239            formatUnsignedOctalString(value, buf, offset, idx);
240    
241            buf[offset + idx++] = (byte) ' '; // Trailing space
242            buf[offset + idx]   = 0; // Trailing null
243    
244            return offset + length;
245        }
246    
247        /**
248         * Write an octal long integer into a buffer.
249         * 
250         * Uses {@link #formatUnsignedOctalString} to format
251         * the value as an octal string with leading zeros.
252         * The converted number is followed by a space.
253         * 
254         * @param value The value to write as octal
255         * @param buf The destinationbuffer.
256         * @param offset The starting offset into the buffer.
257         * @param length The length of the buffer
258         * @return The updated offset
259         * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
260         */
261        public static int formatLongOctalBytes(final long value, byte[] buf, final int offset, final int length) {
262    
263            int idx=length-1; // For space
264            
265            formatUnsignedOctalString(value, buf, offset, idx);
266            buf[offset + idx] = (byte) ' '; // Trailing space
267    
268            return offset + length;
269        }
270    
271        /**
272         * Writes an octal value into a buffer.
273         * 
274         * Uses {@link #formatUnsignedOctalString} to format
275         * the value as an octal string with leading zeros.
276         * The converted number is followed by NUL and then space.
277         *
278         * @param value The value to convert
279         * @param buf The destination buffer
280         * @param offset The starting offset into the buffer.
281         * @param length The size of the buffer.
282         * @return The updated value of offset, i.e. offset+length
283         * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
284         */
285        public static int formatCheckSumOctalBytes(final long value, byte[] buf, final int offset, final int length) {
286    
287            int idx=length-2; // for NUL and space
288            formatUnsignedOctalString(value, buf, offset, idx);
289    
290            buf[offset + idx++]   = 0; // Trailing null
291            buf[offset + idx]     = (byte) ' '; // Trailing space
292    
293            return offset + length;
294        }
295    
296        /**
297         * Compute the checksum of a tar entry header.
298         *
299         * @param buf The tar entry's header buffer.
300         * @return The computed checksum.
301         */
302        public static long computeCheckSum(final byte[] buf) {
303            long sum = 0;
304    
305            for (int i = 0; i < buf.length; ++i) {
306                sum += BYTE_MASK & buf[i];
307            }
308    
309            return sum;
310        }
311    }