1 /** 2 * Detecting MIME type of file using MIME cache. 3 * 4 * Authors: 5 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 6 * License: 7 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 * Copyright: 9 * Roman Chistokhodov, 2016 10 */ 11 12 module mime.detectors.cache; 13 14 import mime.detector; 15 16 private { 17 import mime.cache; 18 19 import std.algorithm; 20 import std.array; 21 import std.file; 22 import std.path; 23 import std.range; 24 import std.traits; 25 } 26 27 /** 28 * Implementation of $(D mime.detector.IMimeDetector) interface using mmappable mime.cache files. 29 */ 30 final class MimeDetectorFromCache : IMimeDetector 31 { 32 /** 33 * Constructor based on existing MIME cache objects. 34 * Params: 35 * mimeCaches = Range of mime.cache.MimeCache objects sorted in order of preference from the mort preferred to the least. All must be non-null. 36 */ 37 this(Range)(Range mimeCaches) if (isInputRange!Range && is(ElementType!Range : const(MimeCache))) 38 { 39 _mimeCaches = mimeCaches.array; 40 } 41 42 /** 43 * Constructor based on MIME paths. It automatically load mime.cache files from given paths. 44 * Params: 45 * mimePaths = Range of paths to base mime/ directories in order from more preferable to less preferable. 46 * Throws: 47 * FileException if some existing mime.cache could not be memory mapped. 48 * $(D mime.cache.MimeCacheException) if some existing mime.cache file is invalid. 49 * See_Also: $(D mime.paths.mimePaths) 50 */ 51 this(Range)(Range mimePaths) if (isInputRange!Range && is(ElementType!Range : string)) 52 { 53 foreach(mimePath; mimePaths) { 54 string path = buildPath(mimePath, "mime.cache"); 55 if (path.exists) { 56 auto mimeCache = new MimeCache(path); 57 _mimeCaches ~= mimeCache; 58 } 59 } 60 } 61 62 const(char)[] mimeTypeForFileName(const(char)[] fileName) 63 { 64 const(char)[] mimeType; 65 uint weight; 66 foreach(i, mimeCache; _mimeCaches) { 67 foreach(alternative; mimeCache.findMimeTypesByLiteral(fileName)) { 68 if (shouldDiscardGlob(alternative.mimeType, _mimeCaches[0..i])) { 69 continue; 70 } 71 if (mimeType.empty || alternative.weight > weight) { 72 mimeType = alternative.mimeType; 73 weight = alternative.weight; 74 } 75 } 76 } 77 if (mimeType.length) { 78 return mimeType; 79 } 80 81 size_t lastPatternLength; 82 foreach(i, mimeCache; _mimeCaches) { 83 foreach(alternative; mimeCache.findMimeTypesByGlob(fileName)) { 84 if (shouldDiscardGlob(alternative.mimeType, _mimeCaches[0..i])) { 85 continue; 86 } 87 if (mimeType.empty || 88 weight < alternative.weight || 89 (weight == alternative.weight && lastPatternLength < alternative.pattern.length)) 90 { 91 mimeType = alternative.mimeType; 92 weight = alternative.weight; 93 lastPatternLength = alternative.pattern.length; 94 } 95 } 96 } 97 98 if (mimeType.length) { 99 return mimeType; 100 } 101 102 size_t mimeCacheIndex; 103 void exchangeAlternative(MimeTypeAlternativeByName alternative) 104 { 105 if (shouldDiscardGlob(alternative.mimeType, _mimeCaches[0..mimeCacheIndex])) { 106 return; 107 } 108 if (mimeType.empty || 109 weight < alternative.weight || 110 (weight == alternative.weight && lastPatternLength < alternative.pattern.length)) 111 { 112 mimeType = alternative.mimeType; 113 weight = alternative.weight; 114 lastPatternLength = alternative.pattern.length; 115 } 116 } 117 118 foreach(mimeCache; _mimeCaches) { 119 mimeCache.findMimeTypesBySuffix(fileName, &exchangeAlternative); 120 mimeCacheIndex++; 121 } 122 123 return mimeType; 124 } 125 126 const(char[])[] mimeTypesForFileName(const(char)[] fileName) 127 { 128 const(char)[][] conflicts; 129 const(char)[] mimeType; 130 uint weight; 131 132 foreach(i, mimeCache; _mimeCaches) { 133 foreach(alternative; mimeCache.findMimeTypesByLiteral(fileName)) { 134 if (shouldDiscardGlob(alternative.mimeType, _mimeCaches[0..i])) { 135 continue; 136 } 137 if (mimeType.empty || alternative.weight > weight) { 138 mimeType = alternative.mimeType; 139 weight = alternative.weight; 140 conflicts = null; 141 } else if (weight == alternative.weight && 142 mimeType != alternative.mimeType && 143 conflicts.find(alternative.mimeType).empty) 144 { 145 conflicts ~= alternative.mimeType; 146 } 147 } 148 } 149 150 if (mimeType.length) { 151 return mimeType ~ conflicts; 152 } 153 154 foreach(i, mimeCache; _mimeCaches) { 155 foreach(alternative; mimeCache.findMimeTypesByGlob(fileName)) { 156 if (shouldDiscardGlob(alternative.mimeType, _mimeCaches[0..i])) { 157 continue; 158 } 159 if (mimeType.empty || weight < alternative.weight) { 160 mimeType = alternative.mimeType; 161 weight = alternative.weight; 162 conflicts = null; 163 } else if (weight == alternative.weight && 164 mimeType != alternative.mimeType && 165 conflicts.find(alternative.mimeType).empty) 166 { 167 conflicts ~= alternative.mimeType; 168 } 169 } 170 } 171 172 if (mimeType.length) { 173 return mimeType ~ conflicts; 174 } 175 176 size_t mimeCacheIndex; 177 void exchangeAlternative(MimeTypeAlternativeByName alternative) 178 { 179 if (shouldDiscardGlob(alternative.mimeType, _mimeCaches[0..mimeCacheIndex])) { 180 return; 181 } 182 if (mimeType.empty || weight < alternative.weight) { 183 mimeType = alternative.mimeType; 184 weight = alternative.weight; 185 conflicts = null; 186 } else if (weight == alternative.weight && 187 mimeType != alternative.mimeType && 188 conflicts.find(alternative.mimeType).empty) 189 { 190 conflicts ~= alternative.mimeType; 191 } 192 } 193 194 foreach(mimeCache; _mimeCaches) { 195 mimeCache.findMimeTypesBySuffix(fileName, &exchangeAlternative); 196 mimeCacheIndex++; 197 } 198 199 if (mimeType.length) { 200 return mimeType ~ conflicts; 201 } 202 203 return null; 204 } 205 206 private bool shouldDiscardGlob(const(char)[] mimeType, const(MimeCache)[] mimeCaches) 207 { 208 foreach(mimeCache; mimeCaches) { 209 auto range = mimeCache 210 .findMimeTypesByLiteral("__NOGLOBS__") 211 .map!(alternative => alternative.mimeType) 212 .find(mimeType); 213 214 if (!range.empty) { 215 return true; 216 } 217 } 218 return false; 219 } 220 221 const(char)[] mimeTypeForData(const(void)[] data) 222 { 223 const(char)[] mimeType; 224 uint weight; 225 226 foreach(i, mimeCache; _mimeCaches) { 227 foreach(alternative; mimeCache.findMimeTypesByData(data)) { 228 bool shouldDiscard = shouldDiscardMagic(alternative.mimeType, _mimeCaches[0..i]); 229 if (shouldDiscard) { 230 continue; 231 } 232 233 if (mimeType.empty || alternative.weight > weight) { 234 mimeType = alternative.mimeType; 235 weight = alternative.weight; 236 } 237 break;//checking only the first is enough because matches are sorted. 238 } 239 } 240 241 return mimeType; 242 } 243 244 const(char[])[] mimeTypesForData(const(void)[] data) 245 { 246 const(char)[][] conflicts; 247 const(char)[] mimeType; 248 uint weight; 249 250 foreach(i, mimeCache; _mimeCaches) { 251 foreach(alternative; mimeCache.findMimeTypesByData(data)) { 252 bool shouldDiscard = shouldDiscardMagic(alternative.mimeType, _mimeCaches[0..i]); 253 if (shouldDiscard) { 254 continue; 255 } 256 257 if (mimeType.empty || alternative.weight > weight) { 258 mimeType = alternative.mimeType; 259 weight = alternative.weight; 260 conflicts = null; 261 } else if (weight == alternative.weight && 262 mimeType != alternative.mimeType && 263 conflicts.find(alternative.mimeType).empty) 264 { 265 conflicts ~= alternative.mimeType; 266 } else if (weight > alternative.weight) { 267 break; //stop since there're no alternatives with equal or better weight left. 268 } 269 } 270 } 271 272 if (mimeType.length) { 273 return mimeType ~ conflicts; 274 } 275 return null; 276 } 277 278 private bool shouldDiscardMagic(const(char)[] mimeType, const(MimeCache)[] mimeCaches) 279 { 280 foreach(mimeCache; mimeCaches) { 281 if (!mimeCache.magicToDelete().equalRange(mimeType).empty) { 282 return true; 283 } 284 } 285 return false; 286 } 287 288 const(char)[] mimeTypeForNamespaceURI(const(char)[] namespaceURI) 289 { 290 foreach(mimeCache; _mimeCaches) { 291 const(char)[] mimeType = mimeCache.findMimeTypeByNamespaceURI(namespaceURI); 292 if (mimeType.length) { 293 return mimeType; 294 } 295 } 296 return null; 297 } 298 299 const(char)[] resolveAlias(const(char)[] aliasName) 300 { 301 foreach(mimeCache; _mimeCaches) { 302 const(char)[] mimeType = mimeCache.resolveAlias(aliasName); 303 if (mimeType.length) { 304 return mimeType; 305 } 306 } 307 return null; 308 } 309 310 bool isSubclassOf(const(char)[] mimeType, const(char)[] parent) 311 { 312 foreach(mimeCache; _mimeCaches) { 313 if (mimeCache.isSubclassOf(mimeType, parent)) { 314 return true; 315 } 316 } 317 return false; 318 } 319 320 /** 321 * Get $(D mime.cache.MimeCache) objects. 322 * Returns: All loaded $(D mime.cache.MimeCache) objects. 323 */ 324 const(MimeCache[]) mimeCaches() 325 { 326 return _mimeCaches; 327 } 328 329 private: 330 const(MimeCache)[] _mimeCaches; 331 }