1/*
2 * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.nio.fs;
27
28import java.net.FileNameMap;
29import java.net.URLConnection;
30import java.nio.file.Path;
31import java.nio.file.spi.FileTypeDetector;
32import java.util.Locale;
33import java.io.IOException;
34
35/**
36 * Base implementation of FileTypeDetector
37 */
38
39public abstract class AbstractFileTypeDetector
40    extends FileTypeDetector
41{
42    protected AbstractFileTypeDetector() {
43        super();
44    }
45
46    /**
47     * Returns the extension of a file name, specifically the portion of the
48     * parameter string after the first dot. If the parameter is {@code null},
49     * empty, does not contain a dot, or the dot is the last character, then an
50     * empty string is returned, otherwise the characters after the dot are
51     * returned.
52     *
53     * @param name A file name
54     * @return The characters after the first dot or an empty string.
55     */
56    protected final String getExtension(String name) {
57        String ext = "";
58        if (name != null && !name.isEmpty()) {
59            int dot = name.indexOf('.');
60            if ((dot >= 0) && (dot < name.length() - 1)) {
61                ext = name.substring(dot + 1);
62            }
63        }
64        return ext;
65    }
66
67    /**
68     * Invokes the appropriate probe method to guess a file's content type,
69     * and checks that the content type's syntax is valid.
70     */
71    @Override
72    public final String probeContentType(Path file) throws IOException {
73        if (file == null)
74            throw new NullPointerException("'file' is null");
75        String result = implProbeContentType(file);
76
77        // Fall back to content types property.
78        if (result == null) {
79            Path fileName = file.getFileName();
80            if (fileName != null) {
81                FileNameMap fileNameMap = URLConnection.getFileNameMap();
82                result = fileNameMap.getContentTypeFor(fileName.toString());
83            }
84        }
85
86        return (result == null) ? null : parse(result);
87    }
88
89    /**
90     * Probes the given file to guess its content type.
91     */
92    protected abstract String implProbeContentType(Path file)
93        throws IOException;
94
95    /**
96     * Parses a candidate content type into its type and subtype, returning
97     * null if either token is invalid.
98     */
99    private static String parse(String s) {
100        int slash = s.indexOf('/');
101        int semicolon = s.indexOf(';');
102        if (slash < 0)
103            return null;  // no subtype
104        String type = s.substring(0, slash).trim().toLowerCase(Locale.ENGLISH);
105        if (!isValidToken(type))
106            return null;  // invalid type
107        String subtype = (semicolon < 0) ? s.substring(slash + 1) :
108            s.substring(slash + 1, semicolon);
109        subtype = subtype.trim().toLowerCase(Locale.ENGLISH);
110        if (!isValidToken(subtype))
111            return null;  // invalid subtype
112        StringBuilder sb = new StringBuilder(type.length() + subtype.length() + 1);
113        sb.append(type);
114        sb.append('/');
115        sb.append(subtype);
116        return sb.toString();
117    }
118
119    /**
120     * Special characters
121     */
122    private static final String TSPECIALS = "()<>@,;:/[]?=\\\"";
123
124    /**
125     * Returns true if the character is a valid token character.
126     */
127    private static boolean isTokenChar(char c) {
128        return (c > 040) && (c < 0177) && (TSPECIALS.indexOf(c) < 0);
129    }
130
131    /**
132     * Returns true if the given string is a legal type or subtype.
133     */
134    private static boolean isValidToken(String s) {
135        int len = s.length();
136        if (len == 0)
137            return false;
138        for (int i = 0; i < len; i++) {
139            if (!isTokenChar(s.charAt(i)))
140                return false;
141        }
142        return true;
143    }
144}
145