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 */
019package org.apache.commons.compress.compressors.xz;
020
021import java.util.HashMap;
022import java.util.Map;
023
024import org.apache.commons.compress.compressors.FileNameUtil;
025import org.apache.commons.compress.utils.OsgiUtils;
026
027/**
028 * Utility code for the XZ compression format.
029 *
030 * @ThreadSafe
031 * @since 1.4
032 */
033public class XZUtils {
034
035    enum CachedAvailability {
036        DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
037    }
038
039    private static final FileNameUtil fileNameUtil;
040
041    /**
042     * XZ Header Magic Bytes begin a XZ file.
043     *
044     * <p>This is a copy of {@code org.tukaani.xz.XZ.HEADER_MAGIC} in
045     * XZ for Java version 1.5.</p>
046     */
047    private static final byte[] HEADER_MAGIC = {
048        (byte) 0xFD, '7', 'z', 'X', 'Z', '\0'
049    };
050
051    private static volatile CachedAvailability cachedXZAvailability;
052
053    static {
054        final Map<String, String> uncompressSuffix = new HashMap<>();
055        uncompressSuffix.put(".txz", ".tar");
056        uncompressSuffix.put(".xz", "");
057        uncompressSuffix.put("-xz", "");
058        fileNameUtil = new FileNameUtil(uncompressSuffix, ".xz");
059        cachedXZAvailability = CachedAvailability.DONT_CACHE;
060        setCacheXZAvailablity(!OsgiUtils.isRunningInOsgiEnvironment());
061    }
062
063    // only exists to support unit tests
064    static CachedAvailability getCachedXZAvailability() {
065        return cachedXZAvailability;
066    }
067
068    /**
069     * Maps the given file name to the name that the file should have after
070     * compression with xz. Common file types with custom suffixes for
071     * compressed versions are automatically detected and correctly mapped.
072     * For example the name "package.tar" is mapped to "package.txz". If no
073     * custom mapping is applicable, then the default ".xz" suffix is appended
074     * to the file name.
075     *
076     * @param fileName name of a file
077     * @return name of the corresponding compressed file
078     * @deprecated Use {@link #getCompressedFileName(String)}.
079     */
080    @Deprecated
081    public static String getCompressedFilename(final String fileName) {
082        return fileNameUtil.getCompressedFileName(fileName);
083    }
084
085    /**
086     * Maps the given file name to the name that the file should have after
087     * compression with xz. Common file types with custom suffixes for
088     * compressed versions are automatically detected and correctly mapped.
089     * For example the name "package.tar" is mapped to "package.txz". If no
090     * custom mapping is applicable, then the default ".xz" suffix is appended
091     * to the file name.
092     *
093     * @param fileName name of a file
094     * @return name of the corresponding compressed file
095     * @since 1.25.0
096     */
097    public static String getCompressedFileName(final String fileName) {
098        return fileNameUtil.getCompressedFileName(fileName);
099    }
100
101    /**
102     * Maps the given name of a xz-compressed file to the name that the
103     * file should have after uncompression. Commonly used file type specific
104     * suffixes like ".txz" are automatically detected and
105     * correctly mapped. For example the name "package.txz" is mapped to
106     * "package.tar". And any file names with the generic ".xz" suffix
107     * (or any other generic xz suffix) is mapped to a name without that
108     * suffix. If no xz suffix is detected, then the file name is returned
109     * unmapped.
110     *
111     * @param fileName name of a file
112     * @return name of the corresponding uncompressed file
113     * @deprecated Use {@link #getUncompressedFileName(String)}.
114     */
115    @Deprecated
116    public static String getUncompressedFilename(final String fileName) {
117        return fileNameUtil.getUncompressedFileName(fileName);
118    }
119
120    /**
121     * Maps the given name of a xz-compressed file to the name that the
122     * file should have after uncompression. Commonly used file type specific
123     * suffixes like ".txz" are automatically detected and
124     * correctly mapped. For example the name "package.txz" is mapped to
125     * "package.tar". And any file names with the generic ".xz" suffix
126     * (or any other generic xz suffix) is mapped to a name without that
127     * suffix. If no xz suffix is detected, then the file name is returned
128     * unmapped.
129     *
130     * @param fileName name of a file
131     * @return name of the corresponding uncompressed file
132     * @since 1.25.0
133     */
134    public static String getUncompressedFileName(final String fileName) {
135        return fileNameUtil.getUncompressedFileName(fileName);
136    }
137
138    private static boolean internalIsXZCompressionAvailable() {
139        try {
140            XZCompressorInputStream.matches(null, 0);
141            return true;
142        } catch (final NoClassDefFoundError error) { // NOSONAR
143            return false;
144        }
145    }
146
147    /**
148     * Detects common xz suffixes in the given file name.
149     *
150     * @param fileName name of a file
151     * @return {@code true} if the file name has a common xz suffix,
152     *         {@code false} otherwise
153     * @deprecated Use {@link #isCompressedFileName(String)}.
154     */
155    @Deprecated
156    public static boolean isCompressedFilename(final String fileName) {
157        return fileNameUtil.isCompressedFileName(fileName);
158    }
159
160    /**
161     * Detects common xz suffixes in the given file name.
162     *
163     * @param fileName name of a file
164     * @return {@code true} if the file name has a common xz suffix,
165     *         {@code false} otherwise
166     * @since 1.25.0
167     */
168    public static boolean isCompressedFileName(final String fileName) {
169        return fileNameUtil.isCompressedFileName(fileName);
170    }
171
172    /**
173     * Are the classes required to support XZ compression available?
174     * @since 1.5
175     * @return true if the classes required to support XZ compression are available
176     */
177    public static boolean isXZCompressionAvailable() {
178        final CachedAvailability cachedResult = cachedXZAvailability;
179        if (cachedResult != CachedAvailability.DONT_CACHE) {
180            return cachedResult == CachedAvailability.CACHED_AVAILABLE;
181        }
182        return internalIsXZCompressionAvailable();
183    }
184
185    /**
186     * Checks if the signature matches what is expected for a .xz file.
187     *
188     * <p>This is more or less a copy of the version found in {@link
189     * XZCompressorInputStream} but doesn't depend on the presence of
190     * XZ for Java.</p>
191     *
192     * @param   signature     the bytes to check
193     * @param   length        the number of bytes to check
194     * @return  true if signature matches the .xz magic bytes, false otherwise
195     * @since 1.9
196     */
197    public static boolean matches(final byte[] signature, final int length) {
198        if (length < HEADER_MAGIC.length) {
199            return false;
200        }
201
202        for (int i = 0; i < HEADER_MAGIC.length; ++i) {
203            if (signature[i] != HEADER_MAGIC[i]) {
204                return false;
205            }
206        }
207
208        return true;
209    }
210
211    /**
212     * Whether to cache the result of the XZ for Java check.
213     *
214     * <p>This defaults to {@code false} in an OSGi environment and {@code true} otherwise.</p>
215     * @param doCache whether to cache the result
216     * @since 1.9
217     */
218    public static void setCacheXZAvailablity(final boolean doCache) {
219        if (!doCache) {
220            cachedXZAvailability = CachedAvailability.DONT_CACHE;
221        } else if (cachedXZAvailability == CachedAvailability.DONT_CACHE) {
222            final boolean hasXz = internalIsXZCompressionAvailable();
223            cachedXZAvailability = hasXz ? CachedAvailability.CACHED_AVAILABLE // NOSONAR
224                : CachedAvailability.CACHED_UNAVAILABLE;
225        }
226    }
227
228    /** Private constructor to prevent instantiation of this utility class. */
229    private XZUtils() {
230    }
231}