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 import java.io.IOException; 022 import java.math.BigInteger; 023 import java.nio.ByteBuffer; 024 import org.apache.commons.compress.archivers.zip.ZipEncoding; 025 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper; 026 027 /** 028 * This class provides static utility methods to work with byte streams. 029 * 030 * @Immutable 031 */ 032 // CheckStyle:HideUtilityClassConstructorCheck OFF (bc) 033 public class TarUtils { 034 035 private static final int BYTE_MASK = 255; 036 037 static final ZipEncoding DEFAULT_ENCODING = 038 ZipEncodingHelper.getZipEncoding(null); 039 040 /** 041 * Encapsulates the algorithms used up to Commons Compress 1.3 as 042 * ZipEncoding. 043 */ 044 static final ZipEncoding FALLBACK_ENCODING = new ZipEncoding() { 045 public boolean canEncode(String name) { return true; } 046 047 public ByteBuffer encode(String name) { 048 final int length = name.length(); 049 byte[] buf = new byte[length]; 050 051 // copy until end of input or output is reached. 052 for (int i = 0; i < length; ++i) { 053 buf[i] = (byte) name.charAt(i); 054 } 055 return ByteBuffer.wrap(buf); 056 } 057 058 public String decode(byte[] buffer) { 059 final int length = buffer.length; 060 StringBuffer result = new StringBuffer(length); 061 062 for (int i = 0; i < length; ++i) { 063 byte b = buffer[i]; 064 if (b == 0) { // Trailing null 065 break; 066 } 067 result.append((char) (b & 0xFF)); // Allow for sign-extension 068 } 069 070 return result.toString(); 071 } 072 }; 073 074 /** Private constructor to prevent instantiation of this utility class. */ 075 private TarUtils(){ 076 } 077 078 /** 079 * Parse an octal string from a buffer. 080 * 081 * <p>Leading spaces are ignored. 082 * The buffer must contain a trailing space or NUL, 083 * and may contain an additional trailing space or NUL.</p> 084 * 085 * <p>The input buffer is allowed to contain all NULs, 086 * in which case the method returns 0L 087 * (this allows for missing fields).</p> 088 * 089 * <p>To work-around some tar implementations that insert a 090 * leading NUL this method returns 0 if it detects a leading NUL 091 * since Commons Compress 1.4.</p> 092 * 093 * @param buffer The buffer from which to parse. 094 * @param offset The offset into the buffer from which to parse. 095 * @param length The maximum number of bytes to parse - must be at least 2 bytes. 096 * @return The long value of the octal string. 097 * @throws IllegalArgumentException if the trailing space/NUL is missing or if a invalid byte is detected. 098 */ 099 public static long parseOctal(final byte[] buffer, final int offset, final int length) { 100 long result = 0; 101 int end = offset + length; 102 int start = offset; 103 104 if (length < 2){ 105 throw new IllegalArgumentException("Length "+length+" must be at least 2"); 106 } 107 108 if (buffer[start] == 0) { 109 return 0L; 110 } 111 112 // Skip leading spaces 113 while (start < end){ 114 if (buffer[start] == ' '){ 115 start++; 116 } else { 117 break; 118 } 119 } 120 121 // Must have trailing NUL or space 122 byte trailer; 123 trailer = buffer[end-1]; 124 if (trailer == 0 || trailer == ' '){ 125 end--; 126 } else { 127 throw new IllegalArgumentException( 128 exceptionMessage(buffer, offset, length, end-1, trailer)); 129 } 130 // May have additional NUL or space 131 trailer = buffer[end-1]; 132 if (trailer == 0 || trailer == ' '){ 133 end--; 134 } 135 136 for ( ;start < end; start++) { 137 final byte currentByte = buffer[start]; 138 // CheckStyle:MagicNumber OFF 139 if (currentByte < '0' || currentByte > '7'){ 140 throw new IllegalArgumentException( 141 exceptionMessage(buffer, offset, length, start, currentByte)); 142 } 143 result = (result << 3) + (currentByte - '0'); // convert from ASCII 144 // CheckStyle:MagicNumber ON 145 } 146 147 return result; 148 } 149 150 /** 151 * Compute the value contained in a byte buffer. If the most 152 * significant bit of the first byte in the buffer is set, this 153 * bit is ignored and the rest of the buffer is interpreted as a 154 * binary number. Otherwise, the buffer is interpreted as an 155 * octal number as per the parseOctal function above. 156 * 157 * @param buffer The buffer from which to parse. 158 * @param offset The offset into the buffer from which to parse. 159 * @param length The maximum number of bytes to parse. 160 * @return The long value of the octal or binary string. 161 * @throws IllegalArgumentException if the trailing space/NUL is 162 * missing or an invalid byte is detected in an octal number, or 163 * if a binary number would exceed the size of a signed long 164 * 64-bit integer. 165 * @since 1.4 166 */ 167 public static long parseOctalOrBinary(final byte[] buffer, final int offset, 168 final int length) { 169 170 if ((buffer[offset] & 0x80) == 0) { 171 return parseOctal(buffer, offset, length); 172 } 173 final boolean negative = buffer[offset] == (byte) 0xff; 174 if (length < 9) { 175 return parseBinaryLong(buffer, offset, length, negative); 176 } 177 return parseBinaryBigInteger(buffer, offset, length, negative); 178 } 179 180 private static long parseBinaryLong(final byte[] buffer, final int offset, 181 final int length, 182 final boolean negative) { 183 if (length >= 9) { 184 throw new IllegalArgumentException("At offset " + offset + ", " 185 + length + " byte binary number" 186 + " exceeds maximum signed long" 187 + " value"); 188 } 189 long val = 0; 190 for (int i = 1; i < length; i++) { 191 val = (val << 8) + (buffer[offset + i] & 0xff); 192 } 193 if (negative) { 194 // 2's complement 195 val--; 196 val ^= ((long) Math.pow(2, (length - 1) * 8) - 1); 197 } 198 return negative ? -val : val; 199 } 200 201 private static long parseBinaryBigInteger(final byte[] buffer, 202 final int offset, 203 final int length, 204 final boolean negative) { 205 byte[] remainder = new byte[length - 1]; 206 System.arraycopy(buffer, offset + 1, remainder, 0, length - 1); 207 BigInteger val = new BigInteger(remainder); 208 if (negative) { 209 // 2's complement 210 val = val.add(BigInteger.valueOf(-1)).not(); 211 } 212 if (val.bitLength() > 63) { 213 throw new IllegalArgumentException("At offset " + offset + ", " 214 + length + " byte binary number" 215 + " exceeds maximum signed long" 216 + " value"); 217 } 218 return negative ? -val.longValue() : val.longValue(); 219 } 220 221 /** 222 * Parse a boolean byte from a buffer. 223 * Leading spaces and NUL are ignored. 224 * The buffer may contain trailing spaces or NULs. 225 * 226 * @param buffer The buffer from which to parse. 227 * @param offset The offset into the buffer from which to parse. 228 * @return The boolean value of the bytes. 229 * @throws IllegalArgumentException if an invalid byte is detected. 230 */ 231 public static boolean parseBoolean(final byte[] buffer, final int offset) { 232 return buffer[offset] == 1; 233 } 234 235 // Helper method to generate the exception message 236 private static String exceptionMessage(byte[] buffer, final int offset, 237 final int length, int current, final byte currentByte) { 238 String string = new String(buffer, offset, length); // TODO default charset? 239 string=string.replaceAll("\0", "{NUL}"); // Replace NULs to allow string to be printed 240 final String s = "Invalid byte "+currentByte+" at offset "+(current-offset)+" in '"+string+"' len="+length; 241 return s; 242 } 243 244 /** 245 * Parse an entry name from a buffer. 246 * Parsing stops when a NUL is found 247 * or the buffer length is reached. 248 * 249 * @param buffer The buffer from which to parse. 250 * @param offset The offset into the buffer from which to parse. 251 * @param length The maximum number of bytes to parse. 252 * @return The entry name. 253 */ 254 public static String parseName(byte[] buffer, final int offset, final int length) { 255 try { 256 return parseName(buffer, offset, length, DEFAULT_ENCODING); 257 } catch (IOException ex) { 258 try { 259 return parseName(buffer, offset, length, FALLBACK_ENCODING); 260 } catch (IOException ex2) { 261 // impossible 262 throw new RuntimeException(ex2); 263 } 264 } 265 } 266 267 /** 268 * Parse an entry name from a buffer. 269 * Parsing stops when a NUL is found 270 * or the buffer length is reached. 271 * 272 * @param buffer The buffer from which to parse. 273 * @param offset The offset into the buffer from which to parse. 274 * @param length The maximum number of bytes to parse. 275 * @param encoding name of the encoding to use for file names 276 * @since Commons Compress 1.4 277 * @return The entry name. 278 */ 279 public static String parseName(byte[] buffer, final int offset, 280 final int length, 281 final ZipEncoding encoding) 282 throws IOException { 283 284 int len = length; 285 for (; len > 0; len--) { 286 if (buffer[offset + len - 1] != 0) { 287 break; 288 } 289 } 290 if (len > 0) { 291 byte[] b = new byte[len]; 292 System.arraycopy(buffer, offset, b, 0, len); 293 return encoding.decode(b); 294 } 295 return ""; 296 } 297 298 /** 299 * Copy a name into a buffer. 300 * Copies characters from the name into the buffer 301 * starting at the specified offset. 302 * If the buffer is longer than the name, the buffer 303 * is filled with trailing NULs. 304 * If the name is longer than the buffer, 305 * the output is truncated. 306 * 307 * @param name The header name from which to copy the characters. 308 * @param buf The buffer where the name is to be stored. 309 * @param offset The starting offset into the buffer 310 * @param length The maximum number of header bytes to copy. 311 * @return The updated offset, i.e. offset + length 312 */ 313 public static int formatNameBytes(String name, byte[] buf, final int offset, final int length) { 314 try { 315 return formatNameBytes(name, buf, offset, length, DEFAULT_ENCODING); 316 } catch (IOException ex) { 317 try { 318 return formatNameBytes(name, buf, offset, length, 319 FALLBACK_ENCODING); 320 } catch (IOException ex2) { 321 // impossible 322 throw new RuntimeException(ex2); 323 } 324 } 325 } 326 327 /** 328 * Copy a name into a buffer. 329 * Copies characters from the name into the buffer 330 * starting at the specified offset. 331 * If the buffer is longer than the name, the buffer 332 * is filled with trailing NULs. 333 * If the name is longer than the buffer, 334 * the output is truncated. 335 * 336 * @param name The header name from which to copy the characters. 337 * @param buf The buffer where the name is to be stored. 338 * @param offset The starting offset into the buffer 339 * @param length The maximum number of header bytes to copy. 340 * @param encoding name of the encoding to use for file names 341 * @since Commons Compress 1.4 342 * @return The updated offset, i.e. offset + length 343 */ 344 public static int formatNameBytes(String name, byte[] buf, final int offset, 345 final int length, 346 final ZipEncoding encoding) 347 throws IOException { 348 int len = name.length(); 349 ByteBuffer b = encoding.encode(name); 350 while (b.limit() > length && len > 0) { 351 b = encoding.encode(name.substring(0, --len)); 352 } 353 final int limit = b.limit(); 354 System.arraycopy(b.array(), b.arrayOffset(), buf, offset, limit); 355 356 // Pad any remaining output bytes with NUL 357 for (int i = limit; i < length; ++i) { 358 buf[offset + i] = 0; 359 } 360 361 return offset + length; 362 } 363 364 /** 365 * Fill buffer with unsigned octal number, padded with leading zeroes. 366 * 367 * @param value number to convert to octal - treated as unsigned 368 * @param buffer destination buffer 369 * @param offset starting offset in buffer 370 * @param length length of buffer to fill 371 * @throws IllegalArgumentException if the value will not fit in the buffer 372 */ 373 public static void formatUnsignedOctalString(final long value, byte[] buffer, 374 final int offset, final int length) { 375 int remaining = length; 376 remaining--; 377 if (value == 0) { 378 buffer[offset + remaining--] = (byte) '0'; 379 } else { 380 long val = value; 381 for (; remaining >= 0 && val != 0; --remaining) { 382 // CheckStyle:MagicNumber OFF 383 buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7)); 384 val = val >>> 3; 385 // CheckStyle:MagicNumber ON 386 } 387 if (val != 0){ 388 throw new IllegalArgumentException 389 (value+"="+Long.toOctalString(value)+ " will not fit in octal number buffer of length "+length); 390 } 391 } 392 393 for (; remaining >= 0; --remaining) { // leading zeros 394 buffer[offset + remaining] = (byte) '0'; 395 } 396 } 397 398 /** 399 * Write an octal integer into a buffer. 400 * 401 * Uses {@link #formatUnsignedOctalString} to format 402 * the value as an octal string with leading zeros. 403 * The converted number is followed by space and NUL 404 * 405 * @param value The value to write 406 * @param buf The buffer to receive the output 407 * @param offset The starting offset into the buffer 408 * @param length The size of the output buffer 409 * @return The updated offset, i.e offset+length 410 * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer 411 */ 412 public static int formatOctalBytes(final long value, byte[] buf, final int offset, final int length) { 413 414 int idx=length-2; // For space and trailing null 415 formatUnsignedOctalString(value, buf, offset, idx); 416 417 buf[offset + idx++] = (byte) ' '; // Trailing space 418 buf[offset + idx] = 0; // Trailing null 419 420 return offset + length; 421 } 422 423 /** 424 * Write an octal long integer into a buffer. 425 * 426 * Uses {@link #formatUnsignedOctalString} to format 427 * the value as an octal string with leading zeros. 428 * The converted number is followed by a space. 429 * 430 * @param value The value to write as octal 431 * @param buf The destinationbuffer. 432 * @param offset The starting offset into the buffer. 433 * @param length The length of the buffer 434 * @return The updated offset 435 * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer 436 */ 437 public static int formatLongOctalBytes(final long value, byte[] buf, final int offset, final int length) { 438 439 int idx=length-1; // For space 440 441 formatUnsignedOctalString(value, buf, offset, idx); 442 buf[offset + idx] = (byte) ' '; // Trailing space 443 444 return offset + length; 445 } 446 447 /** 448 * Write an long integer into a buffer as an octal string if this 449 * will fit, or as a binary number otherwise. 450 * 451 * Uses {@link #formatUnsignedOctalString} to format 452 * the value as an octal string with leading zeros. 453 * The converted number is followed by a space. 454 * 455 * @param value The value to write into the buffer. 456 * @param buf The destination buffer. 457 * @param offset The starting offset into the buffer. 458 * @param length The length of the buffer. 459 * @return The updated offset. 460 * @throws IllegalArgumentException if the value (and trailer) 461 * will not fit in the buffer. 462 * @since 1.4 463 */ 464 public static int formatLongOctalOrBinaryBytes( 465 final long value, byte[] buf, final int offset, final int length) { 466 467 // Check whether we are dealing with UID/GID or SIZE field 468 final long maxAsOctalChar = length == TarConstants.UIDLEN ? TarConstants.MAXID : TarConstants.MAXSIZE; 469 470 final boolean negative = value < 0; 471 if (!negative && value <= maxAsOctalChar) { // OK to store as octal chars 472 return formatLongOctalBytes(value, buf, offset, length); 473 } 474 475 if (length < 9) { 476 formatLongBinary(value, buf, offset, length, negative); 477 } 478 formatBigIntegerBinary(value, buf, offset, length, negative); 479 480 buf[offset] = (byte) (negative ? 0xff : 0x80); 481 return offset + length; 482 } 483 484 private static void formatLongBinary(final long value, byte[] buf, 485 final int offset, final int length, 486 final boolean negative) { 487 final int bits = (length - 1) * 8; 488 final long max = 1l << bits; 489 long val = Math.abs(value); 490 if (val >= max) { 491 throw new IllegalArgumentException("Value " + value + 492 " is too large for " + length + " byte field."); 493 } 494 if (negative) { 495 val ^= max - 1; 496 val |= 0xff << bits; 497 val++; 498 } 499 for (int i = offset + length - 1; i >= offset; i--) { 500 buf[i] = (byte) val; 501 val >>= 8; 502 } 503 } 504 505 private static void formatBigIntegerBinary(final long value, byte[] buf, 506 final int offset, 507 final int length, 508 final boolean negative) { 509 BigInteger val = BigInteger.valueOf(value); 510 final byte[] b = val.toByteArray(); 511 final int len = b.length; 512 final int off = offset + length - len; 513 System.arraycopy(b, 0, buf, off, len); 514 final byte fill = (byte) (negative ? 0xff : 0); 515 for (int i = offset + 1; i < off; i++) { 516 buf[i] = fill; 517 } 518 } 519 520 /** 521 * Writes an octal value into a buffer. 522 * 523 * Uses {@link #formatUnsignedOctalString} to format 524 * the value as an octal string with leading zeros. 525 * The converted number is followed by NUL and then space. 526 * 527 * @param value The value to convert 528 * @param buf The destination buffer 529 * @param offset The starting offset into the buffer. 530 * @param length The size of the buffer. 531 * @return The updated value of offset, i.e. offset+length 532 * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer 533 */ 534 public static int formatCheckSumOctalBytes(final long value, byte[] buf, final int offset, final int length) { 535 536 int idx=length-2; // for NUL and space 537 formatUnsignedOctalString(value, buf, offset, idx); 538 539 buf[offset + idx++] = 0; // Trailing null 540 buf[offset + idx] = (byte) ' '; // Trailing space 541 542 return offset + length; 543 } 544 545 /** 546 * Compute the checksum of a tar entry header. 547 * 548 * @param buf The tar entry's header buffer. 549 * @return The computed checksum. 550 */ 551 public static long computeCheckSum(final byte[] buf) { 552 long sum = 0; 553 554 for (int i = 0; i < buf.length; ++i) { 555 sum += BYTE_MASK & buf[i]; 556 } 557 558 return sum; 559 } 560 561 }