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        // Helper method to generate the exception message
110        private static String exceptionMessage(byte[] buffer, final int offset,
111                final int length, int current, final byte currentByte) {
112            String string = new String(buffer, offset, length);
113            string=string.replaceAll("\0", "{NUL}"); // Replace NULs to allow string to be printed
114            final String s = "Invalid byte "+currentByte+" at offset "+(current-offset)+" in '"+string+"' len="+length;
115            return s;
116        }
117    
118        /**
119         * Parse an entry name from a buffer.
120         * Parsing stops when a NUL is found
121         * or the buffer length is reached.
122         *
123         * @param buffer The buffer from which to parse.
124         * @param offset The offset into the buffer from which to parse.
125         * @param length The maximum number of bytes to parse.
126         * @return The entry name.
127         */
128        public static String parseName(byte[] buffer, final int offset, final int length) {
129            StringBuffer result = new StringBuffer(length);
130            int          end = offset + length;
131    
132            for (int i = offset; i < end; ++i) {
133                byte b = buffer[i];
134                if (b == 0) { // Trailing null
135                    break;
136                }
137                result.append((char) (b & 0xFF)); // Allow for sign-extension
138            }
139    
140            return result.toString();
141        }
142    
143        /**
144         * Copy a name (StringBuffer) into a buffer.
145         * Copies characters from the name into the buffer
146         * starting at the specified offset. 
147         * If the buffer is longer than the name, the buffer
148         * is filled with trailing NULs.
149         * If the name is longer than the buffer,
150         * the output is truncated.
151         *
152         * @param name The header name from which to copy the characters.
153         * @param buf The buffer where the name is to be stored.
154         * @param offset The starting offset into the buffer
155         * @param length The maximum number of header bytes to copy.
156         * @return The updated offset, i.e. offset + length
157         */
158        public static int formatNameBytes(String name, byte[] buf, final int offset, final int length) {
159            int i;
160    
161            // copy until end of input or output is reached.
162            for (i = 0; i < length && i < name.length(); ++i) {
163                buf[offset + i] = (byte) name.charAt(i);
164            }
165    
166            // Pad any remaining output bytes with NUL
167            for (; i < length; ++i) {
168                buf[offset + i] = 0;
169            }
170    
171            return offset + length;
172        }
173    
174        /**
175         * Fill buffer with unsigned octal number, padded with leading zeroes.
176         * 
177         * @param value number to convert to octal - treated as unsigned
178         * @param buffer destination buffer
179         * @param offset starting offset in buffer
180         * @param length length of buffer to fill
181         * @throws IllegalArgumentException if the value will not fit in the buffer
182         */
183        public static void formatUnsignedOctalString(final long value, byte[] buffer,
184                final int offset, final int length) {
185            int remaining = length;
186            remaining--;
187            if (value == 0) {
188                buffer[offset + remaining--] = (byte) '0';
189            } else {
190                long val = value;
191                for (; remaining >= 0 && val != 0; --remaining) {
192                    // CheckStyle:MagicNumber OFF
193                    buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7));
194                    val = val >>> 3;
195                    // CheckStyle:MagicNumber ON
196                }
197                if (val != 0){
198                    throw new IllegalArgumentException
199                    (value+"="+Long.toOctalString(value)+ " will not fit in octal number buffer of length "+length);
200                }
201            }
202    
203            for (; remaining >= 0; --remaining) { // leading zeros
204                buffer[offset + remaining] = (byte) '0';
205            }
206        }
207    
208        /**
209         * Write an octal integer into a buffer.
210         *
211         * Uses {@link #formatUnsignedOctalString} to format
212         * the value as an octal string with leading zeros.
213         * The converted number is followed by space and NUL
214         * 
215         * @param value The value to write
216         * @param buf The buffer to receive the output
217         * @param offset The starting offset into the buffer
218         * @param length The size of the output buffer
219         * @return The updated offset, i.e offset+length
220         * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
221         */
222        public static int formatOctalBytes(final long value, byte[] buf, final int offset, final int length) {
223    
224            int idx=length-2; // For space and trailing null
225            formatUnsignedOctalString(value, buf, offset, idx);
226    
227            buf[offset + idx++] = (byte) ' '; // Trailing space
228            buf[offset + idx]   = 0; // Trailing null
229    
230            return offset + length;
231        }
232    
233        /**
234         * Write an octal long integer into a buffer.
235         * 
236         * Uses {@link #formatUnsignedOctalString} to format
237         * the value as an octal string with leading zeros.
238         * The converted number is followed by a space.
239         * 
240         * @param value The value to write as octal
241         * @param buf The destinationbuffer.
242         * @param offset The starting offset into the buffer.
243         * @param length The length of the buffer
244         * @return The updated offset
245         * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
246         */
247        public static int formatLongOctalBytes(final long value, byte[] buf, final int offset, final int length) {
248    
249            int idx=length-1; // For space
250            
251            formatUnsignedOctalString(value, buf, offset, idx);
252            buf[offset + idx] = (byte) ' '; // Trailing space
253    
254            return offset + length;
255        }
256    
257        /**
258         * Writes an octal value into a buffer.
259         * 
260         * Uses {@link #formatUnsignedOctalString} to format
261         * the value as an octal string with leading zeros.
262         * The converted number is followed by NUL and then space.
263         *
264         * @param value The value to convert
265         * @param buf The destination buffer
266         * @param offset The starting offset into the buffer.
267         * @param length The size of the buffer.
268         * @return The updated value of offset, i.e. offset+length
269         * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
270         */
271        public static int formatCheckSumOctalBytes(final long value, byte[] buf, final int offset, final int length) {
272    
273            int idx=length-2; // for NUL and space
274            formatUnsignedOctalString(value, buf, offset, idx);
275    
276            buf[offset + idx++]   = 0; // Trailing null
277            buf[offset + idx]     = (byte) ' '; // Trailing space
278    
279            return offset + length;
280        }
281    
282        /**
283         * Compute the checksum of a tar entry header.
284         *
285         * @param buf The tar entry's header buffer.
286         * @return The computed checksum.
287         */
288        public static long computeCheckSum(final byte[] buf) {
289            long sum = 0;
290    
291            for (int i = 0; i < buf.length; ++i) {
292                sum += BYTE_MASK & buf[i];
293            }
294    
295            return sum;
296        }
297    }