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}