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