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 * https://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 020package org.apache.commons.compress.compressors.gzip; 021 022import java.io.OutputStream; 023import java.nio.charset.Charset; 024import java.time.Instant; 025import java.util.Objects; 026import java.util.zip.Deflater; 027 028import org.apache.commons.io.Charsets; 029import org.apache.commons.lang3.ArrayUtils; 030import org.apache.commons.lang3.StringUtils; 031 032/** 033 * Parameters for the GZIP compressor. 034 * 035 * @see GzipCompressorInputStream 036 * @see GzipCompressorOutputStream 037 * @see <a href="https://datatracker.ietf.org/doc/html/rfc1952">RFC 1952 GZIP File Format Specification</a> 038 * @since 1.7 039 */ 040public class GzipParameters { 041 042 /** 043 * Enumerates OS types. 044 * <ul> 045 * <li>0 - FAT filesystem (MS-DOS, OS/2, NT/Win32)</li> 046 * <li>1 - Amiga</li> 047 * <li>2 - VMS (or OpenVMS)</li> 048 * <li>3 - Unix</li> 049 * <li>4 - VM/CMS</li> 050 * <li>5 - Atari TOS</li> 051 * <li>6 - HPFS filesystem (OS/2, NT)</li> 052 * <li>7 - Macintosh</li> 053 * <li>8 - Z-System</li> 054 * <li>9 - CP/M</li> 055 * <li>10 - TOPS-20</li> 056 * <li>11 - NTFS filesystem (NT)</li> 057 * <li>12 - QDOS</li> 058 * <li>13 - Acorn RISCOS</li> 059 * <li>255 - unknown</li> 060 * </ul> 061 * 062 * @see <a href="https://datatracker.ietf.org/doc/html/rfc1952#page-7">RFC 1952: GZIP File Format Specification - OS (Operating System)</a> 063 * @since 1.28.0 064 */ 065 public enum OS { 066 067 /** 068 * 13: Acorn RISCOS. 069 */ 070 ACORN_RISCOS(OS_ACORN_RISCOS), 071 072 /** 073 * 1: Amiga. 074 */ 075 AMIGA(OS_AMIGA), 076 077 /** 078 * 5: Atari TOS. 079 */ 080 ATARI_TOS(OS_ATARI_TOS), 081 082 /** 083 * 9: CP/M. 084 */ 085 CPM(OS_CPM), 086 087 // @formatter:off 088 /** 089 * 0: FAT filesystem (MS-DOS, OS/2, NT/Win32). 090 */ 091 FAT(OS_FAT), 092 093 /** 094 * 6: HPFS filesystem (OS/2, NT). 095 */ 096 HPFS(OS_HPFS), 097 098 /** 099 * 7: Macintosh. 100 */ 101 MACINTOSH(OS_MACINTOSH), 102 103 /** 104 * 11: NTFS filesystem (NT). 105 */ 106 NTFS(OS_NTFS), 107 108 /** 109 * 12: QDOS. 110 */ 111 QDOS(OS_QDOS), 112 113 /** 114 * 10: TOPS-20. 115 */ 116 TOPS_20(OS_TOPS_20), 117 118 /** 119 * 3: Unix. 120 */ 121 UNIX(OS_UNIX), 122 123 /** 124 * 255: unknown. 125 */ 126 UNKNOWN(OS_UNKNOWN), 127 128 /** 129 * 4: VM/CMS. 130 */ 131 VM_CMS(OS_VM_CMS), 132 133 /** 134 * 2: VMS (or OpenVMS). 135 */ 136 VMS(OS_VMS), 137 138 /** 139 * 8: Z-System. 140 */ 141 Z_SYSTEM(OS_Z_SYSTEM); 142 // @formatter:on 143 144 /** 145 * Gets the {@link OS} matching the given code. 146 * 147 * @param code an OS or {@link #UNKNOWN} for no match. 148 * @return a {@link OS}. 149 */ 150 public static OS from(final int code) { 151 switch (code) { 152 case OS_ACORN_RISCOS: 153 return ACORN_RISCOS; 154 case OS_AMIGA: 155 return AMIGA; 156 case OS_ATARI_TOS: 157 return ATARI_TOS; 158 case OS_CPM: 159 return CPM; 160 case OS_FAT: 161 return FAT; 162 case OS_HPFS: 163 return HPFS; 164 case OS_MACINTOSH: 165 return MACINTOSH; 166 case OS_NTFS: 167 return NTFS; 168 case OS_QDOS: 169 return QDOS; 170 case OS_TOPS_20: 171 return TOPS_20; 172 case OS_UNIX: 173 return UNIX; 174 case OS_UNKNOWN: 175 return UNKNOWN; 176 case OS_VM_CMS: 177 return VM_CMS; 178 case OS_VMS: 179 return VMS; 180 case OS_Z_SYSTEM: 181 return Z_SYSTEM; 182 default: 183 return UNKNOWN; 184 } 185 } 186 187 private final int type; 188 189 /** 190 * Constructs a new instance. 191 * 192 * @param type the OS type. 193 */ 194 OS(final int type) { 195 this.type = type; 196 } 197 198 /** 199 * Gets the OS type. 200 * 201 * @return the OS type. 202 */ 203 public int type() { 204 return type; 205 } 206 207 } 208 209 private static final int BUFFER_SIZE = 512; 210 211 /** 212 * 13: Acorn RISCOS. 213 */ 214 private static final int OS_ACORN_RISCOS = 13; 215 216 /** 217 * 1: Amiga. 218 */ 219 private static final int OS_AMIGA = 1; 220 221 /** 222 * 5: Atari TOS. 223 */ 224 private static final int OS_ATARI_TOS = 5; 225 226 /** 227 * 9: CP/M. 228 */ 229 private static final int OS_CPM = 9; 230 231 /** 232 * 0: FAT. 233 */ 234 private static final int OS_FAT = 0; 235 236 /** 237 * 6: HPFS filesystem (OS/2, NT). 238 */ 239 private static final int OS_HPFS = 6; 240 241 /** 242 * 7: Macintosh. 243 */ 244 private static final int OS_MACINTOSH = 7; 245 246 /** 247 * 11: NTFS filesystem (NT). 248 */ 249 private static final int OS_NTFS = 11; 250 251 /** 252 * 12: QDOS. 253 */ 254 private static final int OS_QDOS = 12; 255 256 /** 257 * 10: TOPS-20. 258 */ 259 private static final int OS_TOPS_20 = 10; 260 261 /** 262 * 3: Unix. 263 */ 264 private static final int OS_UNIX = 3; 265 266 /** 267 * 255: unknown. 268 */ 269 private static final int OS_UNKNOWN = 255; 270 271 /** 272 * 4: VM/CMS. 273 */ 274 private static final int OS_VM_CMS = 4; 275 276 /** 277 * 2: VMS (or OpenVMS). 278 */ 279 private static final int OS_VMS = 2; 280 281 /** 282 * 8: Z-System. 283 */ 284 private static final int OS_Z_SYSTEM = 8; 285 286 private int bufferSize = BUFFER_SIZE; 287 288 private String comment; 289 private int compressionLevel = Deflater.DEFAULT_COMPRESSION; 290 private int deflateStrategy = Deflater.DEFAULT_STRATEGY; 291 private ExtraField extraField; 292 private String fileName; 293 private Charset fileNameCharset = GzipUtils.GZIP_ENCODING; 294 private boolean headerCrc; 295 /** 296 * The most recent modification time (MTIME) of the original file being compressed. 297 * <p> 298 * The time is in Unix format, for example, seconds since 00:00:00 GMT, Jan. 1, 1970. (Note that this may cause problems for MS-DOS and other systems that 299 * use local rather than Universal time.) If the compressed data did not come from a file, MTIME is set to the time at which compression started. MTIME = 0 300 * means no time stamp is available. 301 * </p> 302 */ 303 private Instant modificationInstant = Instant.EPOCH; 304 private OS operatingSystem = OS.UNKNOWN; // Unknown OS by default 305 private long trailerCrc; 306 private long trailerISize; 307 308 @Override 309 public boolean equals(final Object obj) { 310 if (this == obj) { 311 return true; 312 } 313 if (!(obj instanceof GzipParameters)) { 314 return false; 315 } 316 final GzipParameters other = (GzipParameters) obj; 317 return bufferSize == other.bufferSize && Objects.equals(comment, other.comment) && compressionLevel == other.compressionLevel 318 && deflateStrategy == other.deflateStrategy && Objects.equals(extraField, other.extraField) && Objects.equals(fileName, other.fileName) 319 && Objects.equals(fileNameCharset, other.fileNameCharset) && headerCrc == other.headerCrc 320 && Objects.equals(modificationInstant, other.modificationInstant) && operatingSystem == other.operatingSystem && trailerCrc == other.trailerCrc 321 && trailerISize == other.trailerISize; 322 } 323 324 /** 325 * Gets size of the buffer used to retrieve compressed data. 326 * 327 * @return The size of the buffer used to retrieve compressed data. 328 * @see #setBufferSize(int) 329 * @since 1.21 330 */ 331 public int getBufferSize() { 332 return this.bufferSize; 333 } 334 335 /** 336 * Gets an arbitrary user-defined comment. 337 * 338 * @return a user-defined comment. 339 */ 340 public String getComment() { 341 return comment; 342 } 343 344 /** 345 * Gets the compression level. 346 * 347 * @return the compression level. 348 * @see Deflater#NO_COMPRESSION 349 * @see Deflater#BEST_SPEED 350 * @see Deflater#DEFAULT_COMPRESSION 351 * @see Deflater#BEST_COMPRESSION 352 */ 353 public int getCompressionLevel() { 354 return compressionLevel; 355 } 356 357 /** 358 * Gets the deflater strategy. 359 * 360 * @return the deflater strategy, {@link Deflater#DEFAULT_STRATEGY} by default. 361 * @see #setDeflateStrategy(int) 362 * @see Deflater#setStrategy(int) 363 * @since 1.23 364 */ 365 public int getDeflateStrategy() { 366 return deflateStrategy; 367 } 368 369 /** 370 * Gets the Extra subfields from the header. 371 * 372 * @return the extra subfields from the header. 373 * @since 1.28.0 374 */ 375 public ExtraField getExtraField() { 376 return extraField; 377 } 378 379 /** 380 * Gets the file name. 381 * 382 * @return the file name. 383 * @deprecated Use {@link #getFileName()}. 384 */ 385 @Deprecated 386 public String getFilename() { 387 return fileName; 388 } 389 390 /** 391 * Gets the file name. 392 * 393 * @return the file name. 394 * @since 1.25.0 395 */ 396 public String getFileName() { 397 return fileName; 398 } 399 400 /** 401 * Gets the Charset to use for writing file names and comments. 402 * <p> 403 * The default value is {@link GzipUtils#GZIP_ENCODING}. 404 * </p> 405 * 406 * @return the Charset to use for writing file names and comments. 407 * @since 1.28.0 408 */ 409 public Charset getFileNameCharset() { 410 return fileNameCharset; 411 } 412 413 /** 414 * Returns if the header CRC is to be added (when writing) or was present (when reading). 415 * 416 * @return true is header CRC will be added (on write) or was found (after read). 417 * @since 1.28.0 418 */ 419 public boolean getHeaderCRC() { 420 return headerCrc; 421 } 422 423 /** 424 * Gets the most recent modification time (MTIME) of the original file being compressed. 425 * 426 * @return the most recent modification time. 427 * @since 1.28.0 428 */ 429 public Instant getModificationInstant() { 430 return modificationInstant; 431 } 432 433 /** 434 * Gets the most recent modification time (MTIME) of the original file being compressed, in seconds since 00:00:00 GMT, Jan. 1, 1970. 435 * <p> 436 * The time is in Unix format, for example, seconds since 00:00:00 GMT, Jan. 1, 1970. (Note that this may cause problems for MS-DOS and other systems that 437 * use local rather than Universal time.) If the compressed data did not come from a file, MTIME is set to the time at which compression started. MTIME = 0 438 * means no time stamp is available. 439 * </p> 440 * 441 * @return the most recent modification time in seconds since 00:00:00 GMT, Jan. 1, 1970. 442 */ 443 public long getModificationTime() { 444 return modificationInstant.getEpochSecond(); 445 } 446 447 /** 448 * Gets the OS code type. 449 * 450 * @return the OS code type. 451 */ 452 public int getOperatingSystem() { 453 return operatingSystem.type; 454 } 455 456 /** 457 * Gets the OS type. 458 * 459 * @return the OS type. 460 * @since 1.28.0 461 */ 462 public OS getOS() { 463 return operatingSystem; 464 } 465 466 /** 467 * Gets the trailer CRC value. 468 * 469 * @return the trailer CRC value. 470 * @since 1.28.0 471 */ 472 public long getTrailerCrc() { 473 return trailerCrc; 474 } 475 476 /** 477 * Gets the trailer ISIZE value. 478 * 479 * @return the trailer ISIZE value. 480 * @since 1.28.0 481 */ 482 public long getTrailerISize() { 483 return trailerISize; 484 } 485 486 @Override 487 public int hashCode() { 488 return Objects.hash(bufferSize, comment, compressionLevel, deflateStrategy, extraField, fileName, fileNameCharset, headerCrc, modificationInstant, 489 operatingSystem, trailerCrc, trailerISize); 490 } 491 492 private String requireNonNulByte(final String text) { 493 if (StringUtils.isNotEmpty(text) && ArrayUtils.contains(text.getBytes(fileNameCharset), (byte) 0)) { 494 throw new IllegalArgumentException("String encoded in Charset '" + fileNameCharset + "' contains the nul byte 0 which is not supported in gzip."); 495 } 496 return text; 497 } 498 499 /** 500 * Sets size of the buffer used to retrieve compressed data from {@link Deflater} and write to underlying {@link OutputStream}. 501 * 502 * @param bufferSize the bufferSize to set. Must be a positive type. 503 * @since 1.21 504 */ 505 public void setBufferSize(final int bufferSize) { 506 if (bufferSize <= 0) { 507 throw new IllegalArgumentException("invalid buffer size: " + bufferSize); 508 } 509 this.bufferSize = bufferSize; 510 } 511 512 /** 513 * Sets an arbitrary user-defined comment. 514 * 515 * @param comment a user-defined comment. 516 * @throws IllegalArgumentException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. 517 */ 518 public void setComment(final String comment) { 519 this.comment = requireNonNulByte(comment); 520 } 521 522 /** 523 * Sets the compression level. 524 * 525 * @param compressionLevel the compression level (between 0 and 9) 526 * @see Deflater#NO_COMPRESSION 527 * @see Deflater#BEST_SPEED 528 * @see Deflater#DEFAULT_COMPRESSION 529 * @see Deflater#BEST_COMPRESSION 530 */ 531 public void setCompressionLevel(final int compressionLevel) { 532 if (compressionLevel < -1 || compressionLevel > 9) { 533 throw new IllegalArgumentException("Invalid gzip compression level: " + compressionLevel); 534 } 535 this.compressionLevel = compressionLevel; 536 } 537 538 /** 539 * Sets the deflater strategy. 540 * 541 * @param deflateStrategy the new compression strategy 542 * @see Deflater#setStrategy(int) 543 * @since 1.23 544 */ 545 public void setDeflateStrategy(final int deflateStrategy) { 546 this.deflateStrategy = deflateStrategy; 547 } 548 549 /** 550 * Sets the extra subfields. Note that a non-null extra will appear in the gzip header regardless of the presence of subfields, while a null extra will not 551 * appear at all. 552 * 553 * @param extra the series of extra sub fields. 554 * @since 1.28.0 555 */ 556 public void setExtraField(final ExtraField extra) { 557 this.extraField = extra; 558 } 559 560 /** 561 * Sets the name of the compressed file. 562 * 563 * @param fileName the name of the file without the directory path 564 * @throws IllegalArgumentException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. 565 * @deprecated Use {@link #setFileName(String)}. 566 */ 567 @Deprecated 568 public void setFilename(final String fileName) { 569 setFileName(fileName); 570 } 571 572 /** 573 * Sets the name of the compressed file. 574 * 575 * @param fileName the name of the file without the directory path 576 * @throws IllegalArgumentException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. 577 */ 578 public void setFileName(final String fileName) { 579 this.fileName = requireNonNulByte(fileName); 580 } 581 582 /** 583 * Sets the Charset to use for writing file names and comments, where null maps to {@link GzipUtils#GZIP_ENCODING}. 584 * <p> 585 * <em>Setting a value other than {@link GzipUtils#GZIP_ENCODING} is not compliant with the <a href="https://datatracker.ietf.org/doc/html/rfc1952">RFC 1952 586 * GZIP File Format Specification</a></em>. Use at your own risk of interoperability issues. 587 * </p> 588 * <p> 589 * The default value is {@link GzipUtils#GZIP_ENCODING}. 590 * </p> 591 * 592 * @param charset the Charset to use for writing file names and comments, null maps to {@link GzipUtils#GZIP_ENCODING}. 593 * @since 1.28.0 594 */ 595 public void setFileNameCharset(final Charset charset) { 596 this.fileNameCharset = Charsets.toCharset(charset, GzipUtils.GZIP_ENCODING); 597 } 598 599 /** 600 * Establishes the presence of the header flag FLG.FHCRC and its headers CRC16 value. 601 * 602 * @param headerCRC when true, the header CRC16 (actually low 16 buts of a CRC32) is calculated and inserted 603 * in the gzip header on write; on read it means the field was present. 604 * @since 1.28.0 605 */ 606 public void setHeaderCRC(final boolean headerCRC) { 607 this.headerCrc = headerCRC; 608 } 609 610 /** 611 * Sets the modification time (MTIME) of the compressed file. 612 * 613 * @param modificationTime the modification time, in milliseconds 614 * @since 1.28.0 615 */ 616 public void setModificationInstant(final Instant modificationTime) { 617 this.modificationInstant = modificationTime != null ? modificationTime : Instant.EPOCH; 618 } 619 620 /** 621 * Sets the modification time (MTIME) of the compressed file, in seconds since 00:00:00 GMT, Jan. 1, 1970. 622 * <p> 623 * The time is in Unix format, for example, seconds since 00:00:00 GMT, Jan. 1, 1970. (Note that this may cause problems for MS-DOS and other systems that 624 * use local rather than Universal time.) If the compressed data did not come from a file, MTIME is set to the time at which compression started. MTIME = 0 625 * means no time stamp is available. 626 * </p> 627 * 628 * @param modificationTimeSeconds the modification time, in seconds. 629 */ 630 public void setModificationTime(final long modificationTimeSeconds) { 631 this.modificationInstant = Instant.ofEpochSecond(modificationTimeSeconds); 632 } 633 634 /** 635 * Sets the operating system on which the compression took place. The defined values are: 636 * <ul> 637 * <li>0: FAT file system (MS-DOS, OS/2, NT/Win32)</li> 638 * <li>1: Amiga</li> 639 * <li>2: VMS (or OpenVMS)</li> 640 * <li>3: Unix</li> 641 * <li>4: VM/CMS</li> 642 * <li>5: Atari TOS</li> 643 * <li>6: HPFS file system (OS/2, NT)</li> 644 * <li>7: Macintosh</li> 645 * <li>8: Z-System</li> 646 * <li>9: CP/M</li> 647 * <li>10: TOPS-20</li> 648 * <li>11: NTFS file system (NT)</li> 649 * <li>12: QDOS</li> 650 * <li>13: Acorn RISCOS</li> 651 * <li>255: Unknown</li> 652 * </ul> 653 * 654 * @param operatingSystem the code of the operating system 655 */ 656 public void setOperatingSystem(final int operatingSystem) { 657 this.operatingSystem = OS.from(operatingSystem); 658 } 659 660 /** 661 * Sets the operating system on which the compression took place. 662 * 663 * @param os operating system, null maps to {@link OS#UNKNOWN}. 664 * @since 1.28.0 665 */ 666 public void setOS(final OS os) { 667 this.operatingSystem = os != null ? os : OS.UNKNOWN; 668 } 669 670 void setTrailerCrc(final long trailerCrc) { 671 this.trailerCrc = trailerCrc; 672 } 673 674 void setTrailerISize(final long trailerISize) { 675 this.trailerISize = trailerISize; 676 } 677 678 @Override 679 public String toString() { 680 final StringBuilder builder = new StringBuilder(); 681 builder.append("GzipParameters [bufferSize=").append(bufferSize).append(", comment=").append(comment).append(", compressionLevel=") 682 .append(compressionLevel).append(", deflateStrategy=").append(deflateStrategy).append(", extraField=").append(extraField).append(", fileName=") 683 .append(fileName).append(", fileNameCharset=").append(fileNameCharset).append(", headerCrc=").append(headerCrc).append(", modificationInstant=") 684 .append(modificationInstant).append(", operatingSystem=").append(operatingSystem).append(", trailerCrc=").append(trailerCrc) 685 .append(", trailerISize=").append(trailerISize).append("]"); 686 return builder.toString(); 687 } 688}