    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}