1 /* 2 * Copyright (c) 2012, 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 26 package sun.nio.fs; 27 28 import java.io.IOException; 29 import java.nio.charset.Charset; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.security.AccessController; 33 import java.security.PrivilegedAction; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.regex.Matcher; 39 import java.util.regex.Pattern; 40 41 /** 42 * File type detector that uses a file extension to look up its MIME type 43 * based on a mime.types file. 44 */ 45 46 class MimeTypesFileTypeDetector extends AbstractFileTypeDetector { 47 48 // path to mime.types file 49 private final Path mimeTypesFile; 50 51 // map of extension to MIME type 52 private Map<String,String> mimeTypeMap; 53 54 // set to true when file loaded 55 private volatile boolean loaded = false; 56 57 public MimeTypesFileTypeDetector(Path filePath) { 58 mimeTypesFile = filePath; 59 } 60 61 @Override 62 protected String implProbeContentType(Path path) { 63 Path fn = path.getFileName(); 64 if (fn == null) 65 return null; // no file name 66 67 String ext = getExtension(fn.toString()); 68 if (ext.isEmpty()) 69 return null; // no extension 70 71 loadMimeTypes(); 72 if (mimeTypeMap == null || mimeTypeMap.isEmpty()) 73 return null; 74 75 // Case-sensitive search 76 String mimeType; 77 do { 78 mimeType = mimeTypeMap.get(ext); 79 if (mimeType == null) 80 ext = getExtension(ext); 81 } while (mimeType == null && !ext.isEmpty()); 82 83 return mimeType; 84 } 85 86 // Get the extension of a file name. 87 private static String getExtension(String name) { 88 String ext = ""; 89 if (name != null && !name.isEmpty()) { 90 int dot = name.indexOf('.'); 91 if ((dot >= 0) && (dot < name.length() - 1)) { 92 ext = name.substring(dot + 1); 93 } 94 } 95 return ext; 96 } 97 98 /** 99 * Parse the mime types file, and store the type-extension mappings into 100 * mimeTypeMap. The mime types file is not loaded until the first probe 101 * to achieve the lazy initialization. It adopts double-checked locking 102 * optimization to reduce the locking overhead. 103 */ 104 private void loadMimeTypes() { 105 if (!loaded) { 106 synchronized (this) { 107 if (!loaded) { 108 List<String> lines = AccessController.doPrivileged( 109 new PrivilegedAction<List<String>>() { 110 @Override 111 public List<String> run() { 112 try { 113 return Files.readAllLines(mimeTypesFile, 114 Charset.defaultCharset()); 115 } catch (IOException ignore) { 116 return Collections.emptyList(); 117 } 118 } 119 }); 120 121 mimeTypeMap = new HashMap<>(lines.size()); 122 String entry = ""; 123 for (String line : lines) { 124 entry += line; 125 if (entry.endsWith("\\")) { 126 entry = entry.substring(0, entry.length() - 1); 127 continue; 128 } 129 parseMimeEntry(entry); 130 entry = ""; 131 } 132 if (!entry.isEmpty()) { 133 parseMimeEntry(entry); 134 } 135 loaded = true; 136 } 137 } 138 } 139 } 140 141 /** 142 * Parse a mime-types entry, which can have the following formats. 143 * 1) Simple space-delimited format 144 * image/jpeg jpeg jpg jpe JPG 145 * 146 * 2) Netscape key-value pair format 147 * type=application/x-java-jnlp-file desc="Java Web Start" exts="jnlp" 148 * or 149 * type=text/html exts=htm,html 150 */ 151 private void parseMimeEntry(String entry) { 152 entry = entry.trim(); 153 if (entry.isEmpty() || entry.charAt(0) == '#') 154 return; 155 156 entry = entry.replaceAll("\\s*#.*", ""); 157 int equalIdx = entry.indexOf('='); 158 if (equalIdx > 0) { 159 // Parse a mime-types command having the key-value pair format 160 final String TYPEEQUAL = "type="; 161 String typeRegex = "\\b" + TYPEEQUAL + 162 "(\"\\p{Graph}+?/\\p{Graph}+?\"|\\p{Graph}+/\\p{Graph}+\\b)"; 163 Pattern typePattern = Pattern.compile(typeRegex); 164 Matcher typeMatcher = typePattern.matcher(entry); 165 166 if (typeMatcher.find()) { 167 String type = typeMatcher.group().substring(TYPEEQUAL.length()); 168 if (type.charAt(0) == '"') { 169 type = type.substring(1, type.length() - 1); 170 } 171 172 final String EXTEQUAL = "exts="; 173 String extRegex = "\\b" + EXTEQUAL + 174 "(\"[\\p{Graph}|\\p{Blank}]+?\"|\\p{Graph}+\\b)"; 175 Pattern extPattern = Pattern.compile(extRegex); 176 Matcher extMatcher = extPattern.matcher(entry); 177 178 if (extMatcher.find()) { 179 String exts = 180 extMatcher.group().substring(EXTEQUAL.length()); 181 if (exts.charAt(0) == '"') { 182 exts = exts.substring(1, exts.length() - 1); 183 } 184 String[] extList = exts.split("[\\p{Blank}|\\p{Punct}]+"); 185 for (String ext : extList) { 186 putIfAbsent(ext, type); 187 } 188 } 189 } 190 } else { 191 // Parse a mime-types command having the space-delimited format 192 String[] elements = entry.split("\\s+"); 193 int i = 1; 194 while (i < elements.length) { 195 putIfAbsent(elements[i++], elements[0]); 196 } 197 } 198 } 199 200 private void putIfAbsent(String key, String value) { 201 if (key != null && !key.isEmpty() && 202 value != null && !value.isEmpty() && 203 !mimeTypeMap.containsKey(key)) 204 { 205 mimeTypeMap.put(key, value); 206 } 207 } 208 }