OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.utils.mime;

/*
 * Copyright 2007-2009 Medsea Business Solutions S.L.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.Log;
import org.openconcerto.utils.OSFamily;
import org.openconcerto.utils.StreamUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.spi.FileTypeDetector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.regex.Pattern;

/**
 * <p>
 * The Opendesktop shared mime database contains glob rules and magic number lookup information to
 * enable applications to detect the mime types of files.
 * </p>
 * <p>
 * This class uses the mime.cache file which is one of the files created by the update-mime-database
 * application. This file is a memory mapped file that enables the database to be updated and copied
 * without interrupting applications.
 * </p>
 * <p>
 * This implementation follows the memory mapped spec so it is not required to restart an
 * application using this mime detector should the underlying mime.cache database change.
 * </p>
 * <p>
 * For a complete description of the information contained in this file please see:
 * http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html
 * </p>
 * <p>
 * This class also follows, where possible, the RECOMENDED order of detection as detailed in this
 * spec. Thanks go to Mathias Clasen at Red Hat for pointing me to the original xdgmime
 * implementation http://svn.gnome.org/viewvc/glib/trunk/
 * gio/xdgmime/xdgmimecache.c?revision=7784&view=markup
 * </p>
 * More up to date : https://github.com/GNOME/beagle/blob/master/beagle/glue/xdgmime/xdgmimecache.c
 * 
 * @author Steven McArdle
 * @see <a
 *      href="http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html">Shared
 *      MIME-Info</a>
 */
@SuppressWarnings({ "unqualified-field-access" })
public class FreeDesktopMimeDetector extends FileTypeDetector {

    public static enum Mode {
        /**
         * <a href=
         * "http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html#idm140625828606432"
         * >Recommended checking order</a>
         */
        RECOMMENDED, DATA_ONLY, NAME_ONLY
    }

    public static final String DEFAULT_CACHE = "freeDesktop.mime.cache";
    public static final String DEFAULT_MODE = "freeDesktop.mime.mode";

    static private Mode getDefaultMode() {
        final String m = System.getProperty(DEFAULT_MODE);
        if (m != null) {
            try {
                return Mode.valueOf(m.toUpperCase());
            } catch (Exception e) {
                Log.get().log(Level.CONFIG, "Ignoring invalid mode : " + m, e);
            }
        }
        return Mode.RECOMMENDED;
    }

    private static File mimeCacheFile = OSFamily.getInstance() == OSFamily.Windows ? null : new File("/usr/share/mime/mime.cache");

    static private boolean canReadFile(final File f, final String msg) {
        if (f == null)
            return false;
        final boolean res = f.isFile() && f.canRead();
        if (!res)
            Log.get().config(msg + f);
        return res;
    }

    static private Object getDefaultCache() {
        final String m = System.getProperty(DEFAULT_CACHE);
        final File f = m != null ? new File(m) : null;
        if (canReadFile(f, "Ignoring invalid passed file : ")) {
            return f;
        } else if (canReadFile(mimeCacheFile, "Ignoring invalid system file : ")) {
            return mimeCacheFile;
        } else {
            final URL res = FreeDesktopMimeDetector.class.getResource("mime.cache");
            if (res == null)
                throw new IllegalStateException("No mime.cache found for " + FreeDesktopMimeDetector.class);
            return res;
        }
    }

    private final ByteBuffer content;
    private final Mode mode;

    public FreeDesktopMimeDetector() throws IOException {
        this(getDefaultCache(), getDefaultMode());
    }

    public FreeDesktopMimeDetector(final File mimeCacheFile, final Mode mode) throws IOException {
        this((Object) mimeCacheFile, mode);
    }

    public FreeDesktopMimeDetector(final InputStream is, final Mode mode) throws IOException {
        this((Object) is, mode);
    }

    // InputStream will be closed
    private FreeDesktopMimeDetector(final Object cache, final Mode mode) throws IOException {
        if (cache instanceof File || cache instanceof FileInputStream) {
            // Map the mime.cache file as a memory mapped file
            try (final FileInputStream is = cache instanceof FileInputStream ? (FileInputStream) cache : new FileInputStream((File) cache); final FileChannel rCh = is.getChannel();) {
                content = rCh.map(FileChannel.MapMode.READ_ONLY, 0, rCh.size());
            }
        } else {
            final ByteArrayOutputStream out = new ByteArrayOutputStream(250 * 1024);
            try (final InputStream is = cache instanceof URL ? ((URL) cache).openStream() : (InputStream) cache) {
                StreamUtils.copy(is, out);
            }
            content = ByteBuffer.wrap(out.toByteArray());
        }
        this.mode = mode;
    }

    @Override
    public String probeContentType(Path path) throws IOException {
        final Collection<String> col;
        switch (this.mode) {
        case RECOMMENDED:
            col = this.getMimeTypesFile(path.toFile());
            break;
        case DATA_ONLY:
            try (final InputStream is = new BufferedInputStream(new FileInputStream(path.toFile()))) {
                col = this.getMimeTypesInputStream(is);
            }
            break;
        case NAME_ONLY:
            col = this.getMimeTypesFileName(path.getFileName().toString());
            break;
        default:
            throw new IllegalStateException("Unknown mode : " + this.mode);
        }
        return CollectionUtils.getFirst(col);
    }

    /**
     * This method resolves mime types closely in accordance with the RECOMENDED order of detection
     * detailed in the Opendesktop shared mime database specification
     * http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html See
     * the Recommended checking order.
     * 
     * @param fileName the name to inspect.
     * @return a collection of MIME types.
     */
    public Collection<String> getMimeTypesFileName(String fileName) {
        Collection<WeightedMimeType> mimeTypes = new ArrayList<WeightedMimeType>();
        // Lookup the globbing methods first
        lookupMimeTypesForGlobFileName(fileName, mimeTypes);

        return normalizeWeightedMimeList(mimeTypes);
    }

    /**
     * This method resolves mime types closely in accordance with the RECOMENDED order of detection
     * detailed in the Opendesktop shared mime database specification
     * http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html See
     * the Recommended checking order.
     * 
     * @param file the file to inspect.
     * @return a collection of MIME types.
     * @throws IOException if the file couldn't be read.
     */
    public Collection<String> getMimeTypesFile(File file) throws IOException {
        Collection<String> mimeTypes = getMimeTypesFileName(file.getName());
        if (!file.exists()) {
            return mimeTypes;
        }
        try (final InputStream is = new BufferedInputStream(new FileInputStream(file))) {
            return _getMimeTypes(mimeTypes, is);
        }
    }

    /**
     * This method is unable to perform glob matching as no name is available. This means that it
     * does not follow the recommended order of detection defined in the shared mime database spec
     * http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html
     * 
     * @param in the stream to inspect.s
     * @return a collection of MIME types.
     * @throws IOException if the stream couldn't be read.
     */
    public Collection<String> getMimeTypesInputStream(InputStream in) throws IOException {
        return lookupMimeTypesForMagicData(in);
    }

    /**
     * This method is unable to perform glob matching as no name is available. This means that it
     * does not follow the recommended order of detection defined in the shared mime database spec
     * http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html
     * 
     * @param data the data to inspect.
     * @return a collection of MIME types.
     */
    public Collection<String> getMimeTypesByteArray(byte[] data) {
        return lookupMagicData(data);
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + " using the mime.cache file version [" + getMajorVersion() + "." + getMinorVersion() + "].";
    }

    public String dump() {
        return "{MAJOR_VERSION=" + getMajorVersion() + " MINOR_VERSION=" + getMinorVersion() + " ALIAS_LIST_OFFSET=" + getAliasListOffset() + " PARENT_LIST_OFFSET=" + getParentListOffset()
                + " LITERAL_LIST_OFFSET=" + getLiteralListOffset() + " REVERSE_SUFFIX_TREE_OFFSET=" + getReverseSuffixTreeOffset() + " GLOB_LIST_OFFSET=" + getGlobListOffset() + " MAGIC_LIST_OFFSET="
                + getMagicListOffset() + " NAMESPACE_LIST_OFFSET=" + getNameSpaceListOffset() + " ICONS_LIST_OFFSET=" + getIconListOffset() + " GENERIC_ICONS_LIST_OFFSET="
                + getGenericIconListOffset() + "}";
    }

    private Collection<String> lookupMimeTypesForMagicData(InputStream in) throws IOException {
        int offset = 0;
        int len = getMaxExtents();
        byte[] data = new byte[len];
        // Mark the input stream
        in.mark(len);

        try {
            // Since an InputStream might return only some data (not all
            // requested), we have to read in a loop until
            // either EOF is reached or the desired number of bytes have been
            // read.
            int restBytesToRead = len;
            while (restBytesToRead > 0) {
                int bytesRead = in.read(data, offset, restBytesToRead);
                if (bytesRead < 0)
                    break; // EOF

                offset += bytesRead;
                restBytesToRead -= bytesRead;
            }
        } finally {
            // Reset the input stream to where it was marked.
            in.reset();
        }
        return lookupMagicData(data);
    }

    private Collection<String> lookupMagicData(byte[] data) {

        Collection<String> mimeTypes = new ArrayList<String>();

        int listOffset = getMagicListOffset();
        int numEntries = content.getInt(listOffset);
        int offset = content.getInt(listOffset + 8);

        for (int i = 0; i < numEntries; i++) {
            final int matchOffset = offset + (16 * i);
            final String mimeType = compareToMagicData(matchOffset, data);
            if (mimeType != null) {
                mimeTypes.add(mimeType);
            } else {
                final String nonMatch = getMimeType(content.getInt(matchOffset + 4));
                mimeTypes.remove(nonMatch);
            }
        }

        return mimeTypes;
    }

    private String compareToMagicData(int offset, byte[] data) {
        // TODO
        // int priority = content.getInt(offset);
        int mimeOffset = content.getInt(offset + 4);
        int numMatches = content.getInt(offset + 8);
        int matchletOffset = content.getInt(offset + 12);

        for (int i = 0; i < numMatches; i++) {
            if (matchletMagicCompare(matchletOffset + (i * 32), data)) {
                return getMimeType(mimeOffset);
            }
        }
        return null;
    }

    private boolean matchletMagicCompare(int offset, byte[] data) {
        int n_children = content.getInt(offset + 24);
        int child_offset = content.getInt(offset + 28);

        if (magic_matchlet_compare_to_data(offset, data)) {
            if (n_children == 0)
                return true;

            for (int i = 0; i < n_children; i++) {
                if (matchletMagicCompare(child_offset + 32 * i, data))
                    return true;
            }
        }

        return false;
    }

    private boolean magic_matchlet_compare_to_data(int offset, byte[] data) {
        int rangeStart = content.getInt(offset);
        int rangeLength = content.getInt(offset + 4);
        int dataLength = content.getInt(offset + 12);
        int dataOffset = content.getInt(offset + 16);
        int maskOffset = content.getInt(offset + 20);

        for (int i = rangeStart; i <= rangeStart + rangeLength; i++) {
            boolean validMatch = true;
            if (i + dataLength > data.length) {
                return false;
            }
            if (maskOffset != 0) {
                for (int j = 0; j < dataLength; j++) {
                    if ((content.get(dataOffset + j) & content.get(maskOffset + j)) != (data[j + i] & content.get(maskOffset + j))) {
                        validMatch = false;
                        break;
                    }
                }
            } else {
                for (int j = 0; j < dataLength; j++) {
                    if (content.get(dataOffset + j) != data[j + i]) {
                        validMatch = false;
                        break;
                    }
                }
            }

            if (validMatch) {
                return true;
            }
        }
        return false;
    }

    private void lookupGlobLiteral(String fileName, Collection<WeightedMimeType> mimeTypes) {
        int listOffset = getLiteralListOffset();
        int numEntries = content.getInt(listOffset);

        int min = 0;
        int max = numEntries - 1;
        while (max >= min) {
            int mid = (min + max) / 2;
            String literal = getString(content.getInt((listOffset + 4) + (12 * mid)));
            int cmp = literal.compareTo(fileName);
            if (cmp < 0) {
                min = mid + 1;
            } else if (cmp > 0) {
                max = mid - 1;
            } else {
                String mimeType = getMimeType(content.getInt((listOffset + 4) + (12 * mid) + 4));
                int weight = content.getInt((listOffset + 4) + (12 * mid) + 8);
                mimeTypes.add(new WeightedMimeType(mimeType, literal, weight));
                return;
            }
        }
    }

    private void lookupGlobFileNameMatch(String fileName, Collection<WeightedMimeType> mimeTypes) {
        final int listOffset = getGlobListOffset();
        final int numEntries = content.getInt(listOffset);
        final int entriesOffset = listOffset + 4;

        for (int i = 0; i < numEntries; i++) {
            final int entryOffset = entriesOffset + 12 * 1;
            final int offset = content.getInt(entryOffset);
            final int mimeTypeOffset = content.getInt(entryOffset + 4);
            final int weightNFlags = content.getInt(entryOffset + 8);
            final int weight = weightNFlags & 0xFF;
            final boolean cs = (weightNFlags & 0x100) != 0;

            final Pattern pattern = Pattern.compile(getString(offset, true), !cs ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0);
            if (pattern.matcher(fileName).matches()) {
                final String mimeType = getMimeType(mimeTypeOffset);
                final String globPattern = getString(offset, false);
                mimeTypes.add(new WeightedMimeType(mimeType, globPattern, weight));
            }
        }
    }

    private Collection<String> normalizeWeightedMimeList(Collection<WeightedMimeType> weightedMimeTypes) {
        if (weightedMimeTypes.isEmpty())
            return Collections.emptySet();
        Collection<WeightedMimeType> mimeTypes = new LinkedHashSet<WeightedMimeType>();

        // Sort the weightedMimeTypes
        Collections.sort((List<WeightedMimeType>) weightedMimeTypes, new Comparator<WeightedMimeType>() {
            public int compare(WeightedMimeType obj1, WeightedMimeType obj2) {
                return obj1.weight - obj2.weight;
            }
        });

        // Keep only globs with the biggest weight. They are in weight order at
        // this point
        int weight = 0;
        int patternLen = 0;
        for (final WeightedMimeType mw : weightedMimeTypes) {
            if (weight < mw.weight) {
                weight = mw.weight;
            }
            if (weight >= mw.weight) {
                if (mw.pattern.length() > patternLen) {
                    patternLen = mw.pattern.length();
                }
                mimeTypes.add(mw);
            }
        }

        // Now keep only the longest patterns
        for (final WeightedMimeType mw : weightedMimeTypes) {
            if (mw.pattern.length() < patternLen) {
                mimeTypes.remove(mw);
            }
        }

        // Could possibly have multiple mimeTypes here with the same weight and
        // pattern length. Can even have multiple entries for the same type so
        // lets remove
        // any duplicates by copying entries to a HashSet that can only have a
        // single instance
        // of each type
        Collection<String> _mimeTypes = new HashSet<String>();
        for (final WeightedMimeType mw : mimeTypes) {
            _mimeTypes.add(mw.toString());
        }
        return _mimeTypes;
    }

    private void lookupMimeTypesForGlobFileName(String fileName, Collection<WeightedMimeType> mimeTypes) {
        if (fileName == null) {
            return;
        }

        lookupGlobLiteral(fileName, mimeTypes);
        if (!mimeTypes.isEmpty()) {
            return;
        }

        int len = fileName.length();
        lookupGlobSuffix(fileName, false, len, mimeTypes);
        if (mimeTypes.isEmpty()) {
            lookupGlobSuffix(fileName, true, len, mimeTypes);
        }

        if (mimeTypes.isEmpty()) {
            lookupGlobFileNameMatch(fileName, mimeTypes);
        }
    }

    private void lookupGlobSuffix(String fileName, boolean ignoreCase, int len, Collection<WeightedMimeType> mimeTypes) {
        int listOffset = getReverseSuffixTreeOffset();
        int numEntries = content.getInt(listOffset);
        int offset = content.getInt(listOffset + 4);

        lookupGlobNodeSuffix(fileName, numEntries, offset, ignoreCase, len, mimeTypes, new StringBuffer());
    }

    private void lookupGlobNodeSuffix(final String fileName, final int numEntries, final int offset, final boolean ignoreCase, int len, Collection<WeightedMimeType> mimeTypes, StringBuffer pattern) {
        final char character = ignoreCase ? fileName.toLowerCase().charAt(len - 1) : fileName.charAt(len - 1);

        if (character == 0) {
            return;
        }

        int min = 0;
        int max = numEntries - 1;
        while (max >= min && len >= 0) {
            int mid = (min + max) / 2;

            char matchChar = (char) content.getInt(offset + (12 * mid));
            if (ignoreCase)
                matchChar = Character.toLowerCase(matchChar);
            if (matchChar < character) {
                min = mid + 1;
            } else if (matchChar > character) {
                max = mid - 1;
            } else {
                len--;
                // first leaf nodes (matchChar==0) then tree nodes
                final int numChildren = content.getInt(offset + (12 * mid) + 4);
                final int firstChildOffset = content.getInt(offset + (12 * mid) + 8);
                if (len > 0) {
                    pattern.append(matchChar);
                    lookupGlobNodeSuffix(fileName, numChildren, firstChildOffset, ignoreCase, len, mimeTypes, pattern);
                }
                // if the name did not match a longer pattern, try to match this one
                if (mimeTypes.isEmpty()) {
                    for (int i = 0; i < numChildren; i++) {
                        final int childOffset = firstChildOffset + (12 * i);
                        if (content.getInt(childOffset) != 0) {
                            // not a leaf node anymore
                            break;
                        }

                        final int mimeOffset = content.getInt(childOffset + 4);
                        final int weightNFlags = content.getInt(childOffset + 8);
                        final int weight = weightNFlags & 0xFF;
                        final boolean cs = (weightNFlags & 0x100) != 0;

                        if (!(cs && ignoreCase))
                            mimeTypes.add(new WeightedMimeType(getMimeType(mimeOffset), pattern.toString(), weight));
                    }
                }
                return;
            }
        }
    }

    static class WeightedMimeType extends MimeType {

        private static final long serialVersionUID = 1L;
        String pattern;
        int weight;

        WeightedMimeType(String mimeType, String pattern, int weight) {
            super(mimeType);
            this.pattern = pattern;
            this.weight = weight;
        }
    }

    private int getMaxExtents() {
        return content.getInt(getMagicListOffset() + 4);
    }

    private String aliasLookup(String alias) {
        int aliasListOffset = getAliasListOffset();
        int min = 0;
        int max = content.getInt(aliasListOffset) - 1;

        while (max >= min) {
            int mid = (min + max) / 2;
            // content.position((aliasListOffset + 4) + (mid * 8));

            int aliasOffset = content.getInt((aliasListOffset + 4) + (mid * 8));
            int mimeOffset = content.getInt((aliasListOffset + 4) + (mid * 8) + 4);

            int cmp = getMimeType(aliasOffset).compareTo(alias);
            if (cmp < 0) {
                min = mid + 1;
            } else if (cmp > 0) {
                max = mid - 1;
            } else {
                return getMimeType(mimeOffset);
            }
        }
        return null;
    }

    private String unaliasMimeType(String mimeType) {
        String lookup = aliasLookup(mimeType);
        return lookup == null ? mimeType : lookup;
    }

    private boolean isMimeTypeSubclass(String mimeType, String subClass) {
        String umimeType = unaliasMimeType(mimeType);
        String usubClass = unaliasMimeType(subClass);
        MimeType _mimeType = new MimeType(umimeType);
        MimeType _subClass = new MimeType(usubClass);

        if (umimeType.compareTo(usubClass) == 0) {
            return true;
        }

        if (isSuperType(usubClass) && (_mimeType.getMediaType().equals(_subClass.getMediaType()))) {
            return true;
        }

        // Handle special cases text/plain and application/octet-stream
        if (usubClass.equals("text/plain") && _mimeType.getMediaType().equals("text")) {
            return true;
        }

        if (usubClass.equals("application/octet-stream")) {
            return true;
        }
        int parentListOffset = getParentListOffset();
        int numParents = content.getInt(parentListOffset);
        int min = 0;
        int max = numParents - 1;
        while (max >= min) {
            int med = (min + max) / 2;
            int offset = content.getInt((parentListOffset + 4) + (8 * med));
            String parentMime = getMimeType(offset);
            int cmp = parentMime.compareTo(umimeType);
            if (cmp < 0) {
                min = med + 1;
            } else if (cmp > 0) {
                max = med - 1;
            } else {
                offset = content.getInt((parentListOffset + 4) + (8 * med) + 4);
                int _numParents = content.getInt(offset);
                for (int i = 0; i < _numParents; i++) {
                    int parentOffset = content.getInt((offset + 4) + (4 * i));
                    if (isMimeTypeSubclass(getMimeType(parentOffset), usubClass)) {
                        return true;
                    }
                }
                break;
            }
        }
        return false;
    }

    private boolean isSuperType(String mimeType) {
        return mimeType.endsWith("/*");
    }

    private int getGenericIconListOffset() {
        return content.getInt(36);
    }

    private int getIconListOffset() {
        return content.getInt(32);
    }

    private int getNameSpaceListOffset() {
        return content.getInt(28);
    }

    private int getMagicListOffset() {
        return content.getInt(24);
    }

    private int getGlobListOffset() {
        return content.getInt(20);
    }

    private int getReverseSuffixTreeOffset() {
        return content.getInt(16);
    }

    private int getLiteralListOffset() {
        return content.getInt(12);
    }

    private int getParentListOffset() {
        return content.getInt(8);
    }

    private int getAliasListOffset() {
        return content.getInt(4);
    }

    private short getMinorVersion() {
        return content.getShort(2);
    }

    private short getMajorVersion() {
        return content.getShort(0);
    }

    private String getMimeType(int offset) {
        return getString(offset);
    }

    private String getString(int offset) {
        return getString(offset, false);
    }

    private String getString(int offset, boolean regularExpression) {
        int position = content.position();
        content.position(offset);
        StringBuffer buf = new StringBuffer();
        char c = 0;
        while ((c = (char) content.get()) != 0) {
            if (regularExpression) {
                switch (c) {
                case '.':
                    buf.append("\\");
                    break;
                case '*':
                case '+':
                case '?':
                    buf.append(".");
                }
            }
            buf.append(c);
        }
        // Reset position
        content.position(position);

        if (regularExpression) {
            buf.insert(0, '^');
            buf.append('$');
        }
        return buf.toString();
    }

    private Collection<String> _getMimeTypes(Collection<String> mimeTypes, final InputStream in) throws IOException {
        if (mimeTypes.isEmpty() || mimeTypes.size() > 1) {
            Collection<String> _mimeTypes = getMimeTypesInputStream(in);

            if (!_mimeTypes.isEmpty()) {
                if (!mimeTypes.isEmpty()) {
                    // more than one glob matched

                    // Check for same mime type
                    for (final String mimeType : mimeTypes) {
                        if (_mimeTypes.contains(mimeType)) {
                            // mimeTypes = new ArrayList();
                            mimeTypes.add(mimeType);
                            // return mimeTypes;
                        }
                        // Check for mime type subtype
                        for (final String _mimeType : _mimeTypes) {
                            if (isMimeTypeSubclass(mimeType, _mimeType)) {
                                // mimeTypes = new ArrayList();
                                mimeTypes.add(mimeType);
                                // return mimeTypes;
                            }
                        }
                    }
                } else {
                    // No globs matched but we have magic matches
                    return _mimeTypes;
                }
            }
        }

        return mimeTypes;

    }
}