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 #include "MediaPlayer.h" 31 #include <wtf/HashMap.h> 32 #include <wtf/HashSet.h> 33 #include <wtf/StdLibExtras.h> 34 #include <wtf/text/StringHash.h> 35 36 #if USE(CG) 37 #include "ImageSourceCG.h" 38 #include <ApplicationServices/ApplicationServices.h> 39 #include <wtf/RetainPtr.h> 40 #endif 41 #if PLATFORM(QT) 42 #include <qimagereader.h> 43 #include <qimagewriter.h> 44 #endif 45 46 #if ENABLE(WEB_ARCHIVE) 47 #include "ArchiveFactory.h" 48 #endif 49 50 namespace WebCore { 51 52 static HashSet<String>* supportedImageResourceMIMETypes; 53 static HashSet<String>* supportedImageMIMETypes; 54 static HashSet<String>* supportedImageMIMETypesForEncoding; 55 static HashSet<String>* supportedJavaScriptMIMETypes; 56 static HashSet<String>* supportedNonImageMIMETypes; 57 static HashSet<String>* supportedMediaMIMETypes; 58 static HashSet<String>* unsupportedTextMIMETypes; 59 60 typedef HashMap<String, Vector<String>*, CaseFoldingHash> MediaMIMETypeMap; 61 62 static void initializeSupportedImageMIMETypes() 63 { 64 #if USE(CG) 65 RetainPtr<CFArrayRef> supportedTypes(AdoptCF, CGImageSourceCopyTypeIdentifiers()); 66 CFIndex count = CFArrayGetCount(supportedTypes.get()); 67 for (CFIndex i = 0; i < count; i++) { 68 RetainPtr<CFStringRef> supportedType(AdoptCF, reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i))); 69 String mimeType = MIMETypeForImageSourceType(supportedType.get()); 70 if (!mimeType.isEmpty()) { 71 supportedImageMIMETypes->add(mimeType); 72 supportedImageResourceMIMETypes->add(mimeType); 73 } 74 } 75 76 // On Tiger and Leopard, com.microsoft.bmp doesn't have a MIME type in the registry. 77 supportedImageMIMETypes->add("image/bmp"); 78 supportedImageResourceMIMETypes->add("image/bmp"); 79 80 // Favicons don't have a MIME type in the registry either. 81 supportedImageMIMETypes->add("image/vnd.microsoft.icon"); 82 supportedImageMIMETypes->add("image/x-icon"); 83 supportedImageResourceMIMETypes->add("image/vnd.microsoft.icon"); 84 supportedImageResourceMIMETypes->add("image/x-icon"); 85 86 // We only get one MIME type per UTI, hence our need to add these manually 87 supportedImageMIMETypes->add("image/pjpeg"); 88 supportedImageResourceMIMETypes->add("image/pjpeg"); 89 90 // We don't want to try to treat all binary data as an image 91 supportedImageMIMETypes->remove("application/octet-stream"); 92 supportedImageResourceMIMETypes->remove("application/octet-stream"); 93 94 // Don't treat pdf/postscript as images directly 95 supportedImageMIMETypes->remove("application/pdf"); 96 supportedImageMIMETypes->remove("application/postscript"); 97 98 #elif PLATFORM(QT) 99 QList<QByteArray> formats = QImageReader::supportedImageFormats(); 100 for (size_t i = 0; i < static_cast<size_t>(formats.size()); ++i) { 101 #if ENABLE(SVG) 102 /* 103 * Qt has support for SVG, but we want to use KSVG2 104 */ 105 if (formats.at(i).toLower().startsWith("svg")) 106 continue; 107 #endif 108 String mimeType = MIMETypeRegistry::getMIMETypeForExtension(formats.at(i).constData()); 109 if (!mimeType.isEmpty()) { 110 supportedImageMIMETypes->add(mimeType); 111 supportedImageResourceMIMETypes->add(mimeType); 112 } 113 } 114 #elif PLATFORM(ANDROID) 115 static const char* types[] = { 116 "image/jpeg", 117 "image/webp", 118 "image/png", 119 "image/gif", 120 "image/bmp", 121 "image/x-icon", // ico 122 "image/ico", 123 "image/x-xbitmap" // xbm 124 }; 125 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) { 126 supportedImageMIMETypes->add(types[i]); 127 supportedImageResourceMIMETypes->add(types[i]); 128 } 129 // Checked Safari impl, it seems that the HTTP stack returns 130 // multiple responses, the initial response, and then one for 131 // multipart segment. Each response is sent to the same ResourceLoader 132 // so for us to support this we would need to do the same. 133 supportedNonImageMIMETypes->remove("multipart/x-mixed-replace"); 134 #if !ENABLE(XSLT) 135 supportedNonImageMIMETypes->remove("text/xsl"); 136 #endif 137 #else 138 // assume that all implementations at least support the following standard 139 // image types: 140 static const char* types[] = { 141 "image/jpeg", 142 "image/png", 143 "image/gif", 144 "image/bmp", 145 "image/vnd.microsoft.icon", // ico 146 "image/x-icon", // ico 147 "image/x-xbitmap" // xbm 148 }; 149 for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 150 supportedImageMIMETypes->add(types[i]); 151 supportedImageResourceMIMETypes->add(types[i]); 152 } 153 #endif 154 } 155 156 static void initializeSupportedImageMIMETypesForEncoding() 157 { 158 supportedImageMIMETypesForEncoding = new HashSet<String>; 159 160 #if USE(CG) 161 #if PLATFORM(MAC) 162 RetainPtr<CFArrayRef> supportedTypes(AdoptCF, CGImageDestinationCopyTypeIdentifiers()); 163 CFIndex count = CFArrayGetCount(supportedTypes.get()); 164 for (CFIndex i = 0; i < count; i++) { 165 RetainPtr<CFStringRef> supportedType(AdoptCF, reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i))); 166 String mimeType = MIMETypeForImageSourceType(supportedType.get()); 167 if (!mimeType.isEmpty()) 168 supportedImageMIMETypesForEncoding->add(mimeType); 169 } 170 #else 171 // FIXME: Add Windows support for all the supported UTI's when a way to convert from MIMEType to UTI reliably is found. 172 // For now, only support PNG, JPEG and GIF. See <rdar://problem/6095286>. 173 supportedImageMIMETypesForEncoding->add("image/png"); 174 supportedImageMIMETypesForEncoding->add("image/jpeg"); 175 supportedImageMIMETypesForEncoding->add("image/gif"); 176 #endif 177 #elif PLATFORM(QT) 178 QList<QByteArray> formats = QImageWriter::supportedImageFormats(); 179 for (int i = 0; i < formats.size(); ++i) { 180 String mimeType = MIMETypeRegistry::getMIMETypeForExtension(formats.at(i).constData()); 181 if (!mimeType.isEmpty()) 182 supportedImageMIMETypesForEncoding->add(mimeType); 183 } 184 #elif PLATFORM(GTK) 185 supportedImageMIMETypesForEncoding->add("image/png"); 186 supportedImageMIMETypesForEncoding->add("image/jpeg"); 187 supportedImageMIMETypesForEncoding->add("image/tiff"); 188 supportedImageMIMETypesForEncoding->add("image/bmp"); 189 supportedImageMIMETypesForEncoding->add("image/ico"); 190 #elif USE(CAIRO) 191 supportedImageMIMETypesForEncoding->add("image/png"); 192 #endif 193 } 194 195 static void initializeSupportedJavaScriptMIMETypes() 196 { 197 /* 198 Mozilla 1.8 and WinIE 7 both accept text/javascript and text/ecmascript. 199 Mozilla 1.8 accepts application/javascript, application/ecmascript, and application/x-javascript, but WinIE 7 doesn't. 200 WinIE 7 accepts text/javascript1.1 - text/javascript1.3, text/jscript, and text/livescript, but Mozilla 1.8 doesn't. 201 Mozilla 1.8 allows leading and trailing whitespace, but WinIE 7 doesn't. 202 Mozilla 1.8 and WinIE 7 both accept the empty string, but neither accept a whitespace-only string. 203 We want to accept all the values that either of these browsers accept, but not other values. 204 */ 205 static const char* types[] = { 206 "text/javascript", 207 "text/ecmascript", 208 "application/javascript", 209 "application/ecmascript", 210 "application/x-javascript", 211 "text/javascript1.1", 212 "text/javascript1.2", 213 "text/javascript1.3", 214 "text/jscript", 215 "text/livescript", 216 }; 217 for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i) 218 supportedJavaScriptMIMETypes->add(types[i]); 219 } 220 221 static void initializeSupportedNonImageMimeTypes() 222 { 223 static const char* types[] = { 224 #if ENABLE(WML) 225 "text/vnd.wap.wml", 226 "application/vnd.wap.wmlc", 227 #endif 228 "text/html", 229 "text/xml", 230 "text/xsl", 231 "text/plain", 232 "text/", 233 "application/xml", 234 "application/xhtml+xml", 235 "application/vnd.wap.xhtml+xml", 236 "application/rss+xml", 237 "application/atom+xml", 238 "application/json", 239 #if ENABLE(SVG) 240 "image/svg+xml", 241 #endif 242 #if ENABLE(FTPDIR) 243 "application/x-ftp-directory", 244 #endif 245 "multipart/x-mixed-replace" 246 // Note: ADDING a new type here will probably render it as HTML. This can 247 // result in cross-site scripting. 248 }; 249 COMPILE_ASSERT(sizeof(types) / sizeof(types[0]) <= 16, 250 nonimage_mime_types_must_be_less_than_or_equal_to_16); 251 252 for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i) 253 supportedNonImageMIMETypes->add(types[i]); 254 255 #if ENABLE(WEB_ARCHIVE) 256 ArchiveFactory::registerKnownArchiveMIMETypes(); 257 #endif 258 } 259 260 static MediaMIMETypeMap& mediaMIMETypeMap() 261 { 262 struct TypeExtensionPair { 263 const char* type; 264 const char* extension; 265 }; 266 267 // A table of common media MIME types and file extenstions used when a platform's 268 // specific MIME type lookup doesn't have a match for a media file extension. 269 static const TypeExtensionPair pairs[] = { 270 271 // Ogg 272 { "application/ogg", "ogx" }, 273 { "audio/ogg", "ogg" }, 274 { "audio/ogg", "oga" }, 275 { "video/ogg", "ogv" }, 276 277 // Annodex 278 { "application/annodex", "anx" }, 279 { "audio/annodex", "axa" }, 280 { "video/annodex", "axv" }, 281 { "audio/speex", "spx" }, 282 283 // WebM 284 { "video/webm", "webm" }, 285 { "audio/webm", "webm" }, 286 287 // MPEG 288 { "audio/mpeg", "m1a" }, 289 { "audio/mpeg", "m2a" }, 290 { "audio/mpeg", "m1s" }, 291 { "audio/mpeg", "mpa" }, 292 { "video/mpeg", "mpg" }, 293 { "video/mpeg", "m15" }, 294 { "video/mpeg", "m1s" }, 295 { "video/mpeg", "m1v" }, 296 { "video/mpeg", "m75" }, 297 { "video/mpeg", "mpa" }, 298 { "video/mpeg", "mpeg" }, 299 { "video/mpeg", "mpm" }, 300 { "video/mpeg", "mpv" }, 301 302 // MPEG playlist 303 { "application/vnd.apple.mpegurl", "m3u8" }, 304 { "application/mpegurl", "m3u8" }, 305 { "application/x-mpegurl", "m3u8" }, 306 { "audio/mpegurl", "m3url" }, 307 { "audio/x-mpegurl", "m3url" }, 308 { "audio/mpegurl", "m3u" }, 309 { "audio/x-mpegurl", "m3u" }, 310 311 // MPEG-4 312 { "video/x-m4v", "m4v" }, 313 { "audio/x-m4a", "m4a" }, 314 { "audio/x-m4b", "m4b" }, 315 { "audio/x-m4p", "m4p" }, 316 { "audio/mp4", "m4a" }, 317 318 // MP3 319 { "audio/mp3", "mp3" }, 320 { "audio/x-mp3", "mp3" }, 321 { "audio/x-mpeg", "mp3" }, 322 323 // MPEG-2 324 { "video/x-mpeg2", "mp2" }, 325 { "video/mpeg2", "vob" }, 326 { "video/mpeg2", "mod" }, 327 { "video/m2ts", "m2ts" }, 328 { "video/x-m2ts", "m2t" }, 329 { "video/x-m2ts", "ts" }, 330 331 // 3GP/3GP2 332 { "audio/3gpp", "3gpp" }, 333 { "audio/3gpp2", "3g2" }, 334 { "application/x-mpeg", "amc" }, 335 336 // AAC 337 { "audio/aac", "aac" }, 338 { "audio/aac", "adts" }, 339 { "audio/x-aac", "m4r" }, 340 341 // CoreAudio File 342 { "audio/x-caf", "caf" }, 343 { "audio/x-gsm", "gsm" }, 344 345 // ADPCM 346 { "audio/x-wav", "wav" } 347 }; 348 349 DEFINE_STATIC_LOCAL(MediaMIMETypeMap, mediaMIMETypeForExtensionMap, ()); 350 351 if (!mediaMIMETypeForExtensionMap.isEmpty()) 352 return mediaMIMETypeForExtensionMap; 353 354 const unsigned numPairs = sizeof(pairs) / sizeof(pairs[0]); 355 for (unsigned ndx = 0; ndx < numPairs; ++ndx) { 356 357 if (mediaMIMETypeForExtensionMap.contains(pairs[ndx].extension)) 358 mediaMIMETypeForExtensionMap.get(pairs[ndx].extension)->append(pairs[ndx].type); 359 else { 360 Vector<String>* synonyms = new Vector<String>; 361 362 // If there is a system specific type for this extension, add it as the first type so 363 // getMediaMIMETypeForExtension will always return it. 364 String systemType = MIMETypeRegistry::getMIMETypeForExtension(pairs[ndx].extension); 365 if (!systemType.isEmpty() && pairs[ndx].type != systemType) 366 synonyms->append(systemType); 367 synonyms->append(pairs[ndx].type); 368 mediaMIMETypeForExtensionMap.add(pairs[ndx].extension, synonyms); 369 } 370 } 371 372 return mediaMIMETypeForExtensionMap; 373 } 374 375 #if ENABLE(FILE_SYSTEM) && ENABLE(WORKERS) 376 String MIMETypeRegistry::getMIMETypeForExtension(const String& extension) 377 { 378 return getMIMETypeForExtensionThreadSafe(extension); 379 } 380 #endif 381 382 String MIMETypeRegistry::getMediaMIMETypeForExtension(const String& ext) 383 { 384 // Look in the system-specific registry first. 385 String type = getMIMETypeForExtension(ext); 386 if (!type.isEmpty()) 387 return type; 388 389 Vector<String>* typeList = mediaMIMETypeMap().get(ext); 390 if (typeList) 391 return (*typeList)[0]; 392 393 return String(); 394 } 395 396 Vector<String> MIMETypeRegistry::getMediaMIMETypesForExtension(const String& ext) 397 { 398 Vector<String>* typeList = mediaMIMETypeMap().get(ext); 399 if (typeList) 400 return *typeList; 401 402 // Only need to look in the system-specific registry if mediaMIMETypeMap() doesn't contain 403 // the extension at all, because it always contains the system-specific type if the 404 // extension is in the static mapping table. 405 String type = getMIMETypeForExtension(ext); 406 if (!type.isEmpty()) { 407 Vector<String> typeList; 408 typeList.append(type); 409 return typeList; 410 } 411 412 return Vector<String>(); 413 } 414 415 static void initializeSupportedMediaMIMETypes() 416 { 417 supportedMediaMIMETypes = new HashSet<String>; 418 #if ENABLE(VIDEO) 419 MediaPlayer::getSupportedTypes(*supportedMediaMIMETypes); 420 #endif 421 } 422 423 static void initializeUnsupportedTextMIMETypes() 424 { 425 static const char* types[] = { 426 "text/calendar", 427 "text/x-calendar", 428 "text/x-vcalendar", 429 "text/vcalendar", 430 "text/vcard", 431 "text/x-vcard", 432 "text/directory", 433 "text/ldif", 434 "text/qif", 435 "text/x-qif", 436 "text/x-csv", 437 "text/x-vcf", 438 "text/rtf", 439 }; 440 for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i) 441 unsupportedTextMIMETypes->add(types[i]); 442 } 443 444 static void initializeMIMETypeRegistry() 445 { 446 supportedJavaScriptMIMETypes = new HashSet<String>; 447 initializeSupportedJavaScriptMIMETypes(); 448 449 supportedNonImageMIMETypes = new HashSet<String>(*supportedJavaScriptMIMETypes); 450 initializeSupportedNonImageMimeTypes(); 451 452 supportedImageResourceMIMETypes = new HashSet<String>; 453 supportedImageMIMETypes = new HashSet<String>; 454 initializeSupportedImageMIMETypes(); 455 456 unsupportedTextMIMETypes = new HashSet<String>; 457 initializeUnsupportedTextMIMETypes(); 458 } 459 460 String MIMETypeRegistry::getMIMETypeForPath(const String& path) 461 { 462 size_t pos = path.reverseFind('.'); 463 if (pos != notFound) { 464 String extension = path.substring(pos + 1); 465 String result = getMIMETypeForExtension(extension); 466 if (result.length()) 467 return result; 468 } 469 return "application/octet-stream"; 470 } 471 472 bool MIMETypeRegistry::isSupportedImageMIMEType(const String& mimeType) 473 { 474 if (mimeType.isEmpty()) 475 return false; 476 if (!supportedImageMIMETypes) 477 initializeMIMETypeRegistry(); 478 return supportedImageMIMETypes->contains(mimeType); 479 } 480 481 bool MIMETypeRegistry::isSupportedImageResourceMIMEType(const String& mimeType) 482 { 483 if (mimeType.isEmpty()) 484 return false; 485 if (!supportedImageResourceMIMETypes) 486 initializeMIMETypeRegistry(); 487 return supportedImageResourceMIMETypes->contains(mimeType); 488 } 489 490 bool MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(const String& mimeType) 491 { 492 ASSERT(isMainThread()); 493 494 if (mimeType.isEmpty()) 495 return false; 496 if (!supportedImageMIMETypesForEncoding) 497 initializeSupportedImageMIMETypesForEncoding(); 498 return supportedImageMIMETypesForEncoding->contains(mimeType); 499 } 500 501 bool MIMETypeRegistry::isSupportedJavaScriptMIMEType(const String& mimeType) 502 { 503 if (mimeType.isEmpty()) 504 return false; 505 if (!supportedJavaScriptMIMETypes) 506 initializeMIMETypeRegistry(); 507 return supportedJavaScriptMIMETypes->contains(mimeType); 508 } 509 510 bool MIMETypeRegistry::isSupportedNonImageMIMEType(const String& mimeType) 511 { 512 if (mimeType.isEmpty()) 513 return false; 514 if (!supportedNonImageMIMETypes) 515 initializeMIMETypeRegistry(); 516 return supportedNonImageMIMETypes->contains(mimeType); 517 } 518 519 bool MIMETypeRegistry::isSupportedMediaMIMEType(const String& mimeType) 520 { 521 if (mimeType.isEmpty()) 522 return false; 523 if (!supportedMediaMIMETypes) 524 initializeSupportedMediaMIMETypes(); 525 return supportedMediaMIMETypes->contains(mimeType); 526 } 527 528 bool MIMETypeRegistry::isUnsupportedTextMIMEType(const String& mimeType) 529 { 530 if (mimeType.isEmpty()) 531 return false; 532 if (!unsupportedTextMIMETypes) 533 initializeMIMETypeRegistry(); 534 return unsupportedTextMIMETypes->contains(mimeType); 535 } 536 537 bool MIMETypeRegistry::isJavaAppletMIMEType(const String& mimeType) 538 { 539 // Since this set is very limited and is likely to remain so we won't bother with the overhead 540 // of using a hash set. 541 // Any of the MIME types below may be followed by any number of specific versions of the JVM, 542 // which is why we use startsWith() 543 return mimeType.startsWith("application/x-java-applet", false) 544 || mimeType.startsWith("application/x-java-bean", false) 545 || mimeType.startsWith("application/x-java-vm", false); 546 } 547 548 HashSet<String>& MIMETypeRegistry::getSupportedImageMIMETypes() 549 { 550 if (!supportedImageMIMETypes) 551 initializeMIMETypeRegistry(); 552 return *supportedImageMIMETypes; 553 } 554 555 HashSet<String>& MIMETypeRegistry::getSupportedImageResourceMIMETypes() 556 { 557 if (!supportedImageResourceMIMETypes) 558 initializeMIMETypeRegistry(); 559 return *supportedImageResourceMIMETypes; 560 } 561 562 HashSet<String>& MIMETypeRegistry::getSupportedImageMIMETypesForEncoding() 563 { 564 if (!supportedImageMIMETypesForEncoding) 565 initializeSupportedImageMIMETypesForEncoding(); 566 return *supportedImageMIMETypesForEncoding; 567 } 568 569 HashSet<String>& MIMETypeRegistry::getSupportedNonImageMIMETypes() 570 { 571 if (!supportedNonImageMIMETypes) 572 initializeMIMETypeRegistry(); 573 return *supportedNonImageMIMETypes; 574 } 575 576 HashSet<String>& MIMETypeRegistry::getSupportedMediaMIMETypes() 577 { 578 if (!supportedMediaMIMETypes) 579 initializeSupportedMediaMIMETypes(); 580 return *supportedMediaMIMETypes; 581 } 582 583 HashSet<String>& MIMETypeRegistry::getUnsupportedTextMIMETypes() 584 { 585 if (!unsupportedTextMIMETypes) 586 initializeMIMETypeRegistry(); 587 return *unsupportedTextMIMETypes; 588 } 589 590 const String& defaultMIMEType() 591 { 592 DEFINE_STATIC_LOCAL(const String, defaultMIMEType, ("application/octet-stream")); 593 return defaultMIMEType; 594 } 595 596 } // namespace WebCore 597