1 /* 2 * Copyright (C) 2006, 2008, 2009 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "MIMETypeRegistry.h" 29 30 #if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size 31 #include "ArchiveFactory.h" 32 #endif 33 #include "MediaPlayer.h" 34 #include "StringHash.h" 35 #include <wtf/HashMap.h> 36 #include <wtf/HashSet.h> 37 #include <wtf/StdLibExtras.h> 38 39 #if PLATFORM(CG) 40 #include "ImageSourceCG.h" 41 #include <ApplicationServices/ApplicationServices.h> 42 #include <wtf/RetainPtr.h> 43 #endif 44 #if PLATFORM(QT) 45 #include <qimagereader.h> 46 #include <qimagewriter.h> 47 #endif 48 49 namespace WebCore { 50 51 static HashSet<String>* supportedImageResourceMIMETypes; 52 static HashSet<String>* supportedImageMIMETypes; 53 static HashSet<String>* supportedImageMIMETypesForEncoding; 54 static HashSet<String>* supportedJavaScriptMIMETypes; 55 static HashSet<String>* supportedNonImageMIMETypes; 56 static HashSet<String>* supportedMediaMIMETypes; 57 static HashMap<String, String, CaseFoldingHash>* mediaMIMETypeForExtensionMap; 58 59 static void initializeSupportedImageMIMETypes() 60 { 61 #if PLATFORM(CG) 62 RetainPtr<CFArrayRef> supportedTypes(AdoptCF, CGImageSourceCopyTypeIdentifiers()); 63 CFIndex count = CFArrayGetCount(supportedTypes.get()); 64 for (CFIndex i = 0; i < count; i++) { 65 RetainPtr<CFStringRef> supportedType(AdoptCF, reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i))); 66 String mimeType = MIMETypeForImageSourceType(supportedType.get()); 67 if (!mimeType.isEmpty()) { 68 supportedImageMIMETypes->add(mimeType); 69 supportedImageResourceMIMETypes->add(mimeType); 70 } 71 } 72 73 // On Tiger and Leopard, com.microsoft.bmp doesn't have a MIME type in the registry. 74 supportedImageMIMETypes->add("image/bmp"); 75 supportedImageResourceMIMETypes->add("image/bmp"); 76 77 // Favicons don't have a MIME type in the registry either. 78 supportedImageMIMETypes->add("image/vnd.microsoft.icon"); 79 supportedImageMIMETypes->add("image/x-icon"); 80 supportedImageResourceMIMETypes->add("image/vnd.microsoft.icon"); 81 supportedImageResourceMIMETypes->add("image/x-icon"); 82 83 // We only get one MIME type per UTI, hence our need to add these manually 84 supportedImageMIMETypes->add("image/pjpeg"); 85 supportedImageResourceMIMETypes->add("image/pjpeg"); 86 87 // We don't want to try to treat all binary data as an image 88 supportedImageMIMETypes->remove("application/octet-stream"); 89 supportedImageResourceMIMETypes->remove("application/octet-stream"); 90 91 // Don't treat pdf/postscript as images directly 92 supportedImageMIMETypes->remove("application/pdf"); 93 supportedImageMIMETypes->remove("application/postscript"); 94 95 #elif PLATFORM(QT) 96 QList<QByteArray> formats = QImageReader::supportedImageFormats(); 97 for (size_t i = 0; i < static_cast<size_t>(formats.size()); ++i) { 98 #if ENABLE(SVG) 99 /* 100 * Qt has support for SVG, but we want to use KSVG2 101 */ 102 if (formats.at(i).toLower().startsWith("svg")) 103 continue; 104 #endif 105 String mimeType = MIMETypeRegistry::getMIMETypeForExtension(formats.at(i).constData()); 106 supportedImageMIMETypes->add(mimeType); 107 supportedImageResourceMIMETypes->add(mimeType); 108 } 109 110 supportedImageMIMETypes->remove("application/octet-stream"); 111 supportedImageResourceMIMETypes->remove("application/octet-stream"); 112 #elif PLATFORM(ANDROID) 113 static const char* types[] = { 114 "image/jpeg", 115 "image/png", 116 "image/gif", 117 "image/bmp", 118 "image/x-icon", // ico 119 "image/ico", 120 "image/x-xbitmap" // xbm 121 }; 122 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) { 123 supportedImageMIMETypes->add(types[i]); 124 supportedImageResourceMIMETypes->add(types[i]); 125 } 126 // Checked Safari impl, it seems that the HTTP stack returns 127 // multiple responses, the initial response, and then one for 128 // multipart segment. Each response is sent to the same ResourceLoader 129 // so for us to support this we would need to do the same. 130 supportedNonImageMIMETypes->remove("multipart/x-mixed-replace"); 131 #if !ENABLE(XSLT) 132 supportedNonImageMIMETypes->remove("text/xsl"); 133 #endif 134 #else 135 // assume that all implementations at least support the following standard 136 // image types: 137 static const char* types[] = { 138 "image/jpeg", 139 "image/png", 140 "image/gif", 141 "image/bmp", 142 "image/vnd.microsoft.icon", // ico 143 "image/x-icon", // ico 144 "image/x-xbitmap" // xbm 145 }; 146 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) { 147 supportedImageMIMETypes->add(types[i]); 148 supportedImageResourceMIMETypes->add(types[i]); 149 } 150 #endif 151 } 152 153 static void initializeSupportedImageMIMETypesForEncoding() 154 { 155 supportedImageMIMETypesForEncoding = new HashSet<String>; 156 157 #if PLATFORM(CG) 158 #if PLATFORM(MAC) 159 RetainPtr<CFArrayRef> supportedTypes(AdoptCF, CGImageDestinationCopyTypeIdentifiers()); 160 CFIndex count = CFArrayGetCount(supportedTypes.get()); 161 for (CFIndex i = 0; i < count; i++) { 162 RetainPtr<CFStringRef> supportedType(AdoptCF, reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i))); 163 String mimeType = MIMETypeForImageSourceType(supportedType.get()); 164 if (!mimeType.isEmpty()) 165 supportedImageMIMETypesForEncoding->add(mimeType); 166 } 167 #else 168 // FIXME: Add Windows support for all the supported UTI's when a way to convert from MIMEType to UTI reliably is found. 169 // For now, only support PNG, JPEG and GIF. See <rdar://problem/6095286>. 170 supportedImageMIMETypesForEncoding->add("image/png"); 171 supportedImageMIMETypesForEncoding->add("image/jpeg"); 172 supportedImageMIMETypesForEncoding->add("image/gif"); 173 #endif 174 #elif PLATFORM(QT) 175 QList<QByteArray> formats = QImageWriter::supportedImageFormats(); 176 for (int i = 0; i < formats.size(); ++i) { 177 String mimeType = MIMETypeRegistry::getMIMETypeForExtension(formats.at(i).constData()); 178 supportedImageMIMETypesForEncoding->add(mimeType); 179 } 180 181 supportedImageMIMETypesForEncoding->remove("application/octet-stream"); 182 #elif PLATFORM(CAIRO) 183 supportedImageMIMETypesForEncoding->add("image/png"); 184 #endif 185 } 186 187 static void initializeSupportedJavaScriptMIMETypes() 188 { 189 /* 190 Mozilla 1.8 and WinIE 7 both accept text/javascript and text/ecmascript. 191 Mozilla 1.8 accepts application/javascript, application/ecmascript, and application/x-javascript, but WinIE 7 doesn't. 192 WinIE 7 accepts text/javascript1.1 - text/javascript1.3, text/jscript, and text/livescript, but Mozilla 1.8 doesn't. 193 Mozilla 1.8 allows leading and trailing whitespace, but WinIE 7 doesn't. 194 Mozilla 1.8 and WinIE 7 both accept the empty string, but neither accept a whitespace-only string. 195 We want to accept all the values that either of these browsers accept, but not other values. 196 */ 197 static const char* types[] = { 198 "text/javascript", 199 "text/ecmascript", 200 "application/javascript", 201 "application/ecmascript", 202 "application/x-javascript", 203 "text/javascript1.1", 204 "text/javascript1.2", 205 "text/javascript1.3", 206 "text/jscript", 207 "text/livescript", 208 }; 209 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) 210 supportedJavaScriptMIMETypes->add(types[i]); 211 } 212 213 static void initializeSupportedNonImageMimeTypes() 214 { 215 static const char* types[] = { 216 #if ENABLE(WML) 217 "text/vnd.wap.wml", 218 "application/vnd.wap.wmlc", 219 #endif 220 "text/html", 221 "text/xml", 222 "text/xsl", 223 "text/plain", 224 "text/", 225 "application/xml", 226 "application/xhtml+xml", 227 #if ENABLE(XHTMLMP) 228 "application/vnd.wap.xhtml+xml", 229 #endif 230 "application/rss+xml", 231 "application/atom+xml", 232 #if ENABLE(SVG) 233 "image/svg+xml", 234 #endif 235 #if ENABLE(FTPDIR) 236 "application/x-ftp-directory", 237 #endif 238 "multipart/x-mixed-replace" 239 }; 240 for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); ++i) 241 supportedNonImageMIMETypes->add(types[i]); 242 243 #if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size 244 ArchiveFactory::registerKnownArchiveMIMETypes(); 245 #endif 246 } 247 248 static void initializeMediaTypeMaps() 249 { 250 struct TypeExtensionPair { 251 const char* type; 252 const char* extension; 253 }; 254 255 // A table of common media MIME types and file extenstions used when a platform's 256 // specific MIME type lookup doens't have a match for a media file extension. While some 257 // file extensions are claimed by multiple MIME types, this table only includes one 258 // for each because it is currently only used by getMediaMIMETypeForExtension. If we 259 // ever add a MIME type -> file extension mapping, the alternate MIME types will need 260 // to be added. 261 static const TypeExtensionPair pairs[] = { 262 263 // Ogg 264 { "application/ogg", "ogx" }, 265 { "audio/ogg", "ogg" }, 266 { "audio/ogg", "oga" }, 267 { "video/ogg", "ogv" }, 268 269 // Annodex 270 { "application/annodex", "anx" }, 271 { "audio/annodex", "axa" }, 272 { "video/annodex", "axv" }, 273 { "audio/speex", "spx" }, 274 275 // MPEG 276 { "audio/mpeg", "m1a" }, 277 { "audio/mpeg", "m2a" }, 278 { "audio/mpeg", "m1s" }, 279 { "audio/mpeg", "mpa" }, 280 { "video/mpeg", "mpg" }, 281 { "video/mpeg", "m15" }, 282 { "video/mpeg", "m1s" }, 283 { "video/mpeg", "m1v" }, 284 { "video/mpeg", "m75" }, 285 { "video/mpeg", "mpa" }, 286 { "video/mpeg", "mpeg" }, 287 { "video/mpeg", "mpm" }, 288 { "video/mpeg", "mpv" }, 289 290 // MPEG playlist 291 { "audio/x-mpegurl", "m3url" }, 292 { "application/x-mpegurl", "m3u8" }, 293 294 // MPEG-4 295 { "video/x-m4v", "m4v" }, 296 { "audio/x-m4a", "m4a" }, 297 { "audio/x-m4b", "m4b" }, 298 { "audio/x-m4p", "m4p" }, 299 300 // MP3 301 { "audio/mp3", "mp3" }, 302 303 // MPEG-2 304 { "video/x-mpeg2", "mp2" }, 305 { "video/mpeg2", "vob" }, 306 { "video/mpeg2", "mod" }, 307 { "video/m2ts", "m2ts" }, 308 { "video/x-m2ts", "m2t" }, 309 { "video/x-m2ts", "ts" }, 310 311 // 3GP/3GP2 312 { "audio/3gpp", "3gpp" }, 313 { "audio/3gpp2", "3g2" }, 314 { "application/x-mpeg", "amc" }, 315 316 // AAC 317 { "audio/aac", "aac" }, 318 { "audio/aac", "adts" }, 319 { "audio/x-aac", "m4r" }, 320 321 // CoreAudio File 322 { "audio/x-caf", "caf" }, 323 { "audio/x-gsm", "gsm" } 324 }; 325 326 mediaMIMETypeForExtensionMap = new HashMap<String, String, CaseFoldingHash>; 327 const unsigned numPairs = sizeof(pairs) / sizeof(pairs[0]); 328 for (unsigned ndx = 0; ndx < numPairs; ++ndx) 329 mediaMIMETypeForExtensionMap->set(pairs[ndx].extension, pairs[ndx].type); 330 } 331 332 String MIMETypeRegistry::getMediaMIMETypeForExtension(const String& ext) 333 { 334 // Check with system specific implementation first. 335 String mimeType = getMIMETypeForExtension(ext); 336 if (!mimeType.isEmpty()) 337 return mimeType; 338 339 // No match, look in the static mapping. 340 if (!mediaMIMETypeForExtensionMap) 341 initializeMediaTypeMaps(); 342 return mediaMIMETypeForExtensionMap->get(ext); 343 } 344 345 static void initializeSupportedMediaMIMETypes() 346 { 347 supportedMediaMIMETypes = new HashSet<String>; 348 #if ENABLE(VIDEO) 349 MediaPlayer::getSupportedTypes(*supportedMediaMIMETypes); 350 #endif 351 } 352 353 static void initializeMIMETypeRegistry() 354 { 355 supportedJavaScriptMIMETypes = new HashSet<String>; 356 initializeSupportedJavaScriptMIMETypes(); 357 358 supportedNonImageMIMETypes = new HashSet<String>(*supportedJavaScriptMIMETypes); 359 initializeSupportedNonImageMimeTypes(); 360 361 supportedImageResourceMIMETypes = new HashSet<String>; 362 supportedImageMIMETypes = new HashSet<String>; 363 initializeSupportedImageMIMETypes(); 364 } 365 366 String MIMETypeRegistry::getMIMETypeForPath(const String& path) 367 { 368 int pos = path.reverseFind('.'); 369 if (pos >= 0) { 370 String extension = path.substring(pos + 1); 371 String result = getMIMETypeForExtension(extension); 372 if (result.length()) 373 return result; 374 } 375 return "application/octet-stream"; 376 } 377 378 bool MIMETypeRegistry::isSupportedImageMIMEType(const String& mimeType) 379 { 380 if (mimeType.isEmpty()) 381 return false; 382 if (!supportedImageMIMETypes) 383 initializeMIMETypeRegistry(); 384 return supportedImageMIMETypes->contains(mimeType); 385 } 386 387 bool MIMETypeRegistry::isSupportedImageResourceMIMEType(const String& mimeType) 388 { 389 if (mimeType.isEmpty()) 390 return false; 391 if (!supportedImageResourceMIMETypes) 392 initializeMIMETypeRegistry(); 393 return supportedImageResourceMIMETypes->contains(mimeType); 394 } 395 396 bool MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(const String& mimeType) 397 { 398 if (mimeType.isEmpty()) 399 return false; 400 if (!supportedImageMIMETypesForEncoding) 401 initializeSupportedImageMIMETypesForEncoding(); 402 return supportedImageMIMETypesForEncoding->contains(mimeType); 403 } 404 405 bool MIMETypeRegistry::isSupportedJavaScriptMIMEType(const String& mimeType) 406 { 407 if (mimeType.isEmpty()) 408 return false; 409 if (!supportedJavaScriptMIMETypes) 410 initializeMIMETypeRegistry(); 411 return supportedJavaScriptMIMETypes->contains(mimeType); 412 } 413 414 bool MIMETypeRegistry::isSupportedNonImageMIMEType(const String& mimeType) 415 { 416 if (mimeType.isEmpty()) 417 return false; 418 if (!supportedNonImageMIMETypes) 419 initializeMIMETypeRegistry(); 420 return supportedNonImageMIMETypes->contains(mimeType); 421 } 422 423 bool MIMETypeRegistry::isSupportedMediaMIMEType(const String& mimeType) 424 { 425 if (mimeType.isEmpty()) 426 return false; 427 if (!supportedMediaMIMETypes) 428 initializeSupportedMediaMIMETypes(); 429 return supportedMediaMIMETypes->contains(mimeType); 430 } 431 432 bool MIMETypeRegistry::isJavaAppletMIMEType(const String& mimeType) 433 { 434 // Since this set is very limited and is likely to remain so we won't bother with the overhead 435 // of using a hash set. 436 // Any of the MIME types below may be followed by any number of specific versions of the JVM, 437 // which is why we use startsWith() 438 return mimeType.startsWith("application/x-java-applet", false) 439 || mimeType.startsWith("application/x-java-bean", false) 440 || mimeType.startsWith("application/x-java-vm", false); 441 } 442 443 HashSet<String>& MIMETypeRegistry::getSupportedImageMIMETypes() 444 { 445 if (!supportedImageMIMETypes) 446 initializeMIMETypeRegistry(); 447 return *supportedImageMIMETypes; 448 } 449 450 HashSet<String>& MIMETypeRegistry::getSupportedImageResourceMIMETypes() 451 { 452 if (!supportedImageResourceMIMETypes) 453 initializeMIMETypeRegistry(); 454 return *supportedImageResourceMIMETypes; 455 } 456 457 HashSet<String>& MIMETypeRegistry::getSupportedImageMIMETypesForEncoding() 458 { 459 if (!supportedImageMIMETypesForEncoding) 460 initializeSupportedImageMIMETypesForEncoding(); 461 return *supportedImageMIMETypesForEncoding; 462 } 463 464 HashSet<String>& MIMETypeRegistry::getSupportedNonImageMIMETypes() 465 { 466 if (!supportedNonImageMIMETypes) 467 initializeMIMETypeRegistry(); 468 return *supportedNonImageMIMETypes; 469 } 470 471 HashSet<String>& MIMETypeRegistry::getSupportedMediaMIMETypes() 472 { 473 if (!supportedMediaMIMETypes) 474 initializeSupportedMediaMIMETypes(); 475 return *supportedMediaMIMETypes; 476 } 477 478 const String& defaultMIMEType() 479 { 480 DEFINE_STATIC_LOCAL(const String, defaultMIMEType, ("application/octet-stream")); 481 return defaultMIMEType; 482 } 483 484 } // namespace WebCore 485