1/* 2 * Copyright (C) 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 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 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "config.h" 30#import "WebCoreURLResponse.h" 31 32#import "FoundationExtras.h" 33#import "MIMETypeRegistry.h" 34#import <objc/objc-class.h> 35#import <wtf/Assertions.h> 36#import <wtf/RetainPtr.h> 37 38#ifndef BUILDING_ON_TIGER 39// <rdar://problem/5321972> Plain text document from HTTP server detected as application/octet-stream 40// When we sniff a resource as application/octet-stream but the http response headers had "text/plain", 41// we have a hard decision to make about which of the two generic MIME types to go with. 42// When the URL's extension is a known binary type, we'll go with application/octet-stream. 43// Otherwise, we'll trust the server. 44static NSSet *createBinaryExtensionsSet() 45{ 46 return [[NSSet alloc] initWithObjects: 47 @"3g2", 48 @"3gp", 49 @"ai", 50 @"aif", 51 @"aifc", 52 @"aiff", 53 @"au", 54 @"avi", 55 @"bcpio", 56 @"bin", 57 @"bmp", 58 @"boz", 59 @"bpk", 60 @"bz", 61 @"bz2", 62 @"chm", 63 @"class", 64 @"com", 65 @"cpio", 66 @"dcr", 67 @"dir", 68 @"dist", 69 @"distz", 70 @"dll", 71 @"dmg", 72 @"dms", 73 @"doc", 74 @"dot", 75 @"dump", 76 @"dv", 77 @"dvi", 78 @"dxr", 79 @"elc", 80 @"eot", 81 @"eps", 82 @"exe", 83 @"fgd", 84 @"gif", 85 @"gtar", 86 @"h261", 87 @"h263", 88 @"h264", 89 @"ico", 90 @"ims", 91 @"indd", 92 @"iso", 93 @"jp2", 94 @"jpe", 95 @"jpeg", 96 @"jpg", 97 @"jpgm", 98 @"jpgv", 99 @"jpm", 100 @"kar", 101 @"kmz", 102 @"lha", 103 @"lrm", 104 @"lzh", 105 @"m1v", 106 @"m2a", 107 @"m2v", 108 @"m3a", 109 @"m3u", 110 @"m4a", 111 @"m4p", 112 @"m4v", 113 @"mdb", 114 @"mid", 115 @"midi", 116 @"mj2", 117 @"mjp2", 118 @"mov", 119 @"movie", 120 @"mp2", 121 @"mp2a", 122 @"mp3", 123 @"mp4", 124 @"mp4a", 125 @"mp4s", 126 @"mp4v", 127 @"mpe", 128 @"mpeg", 129 @"mpg", 130 @"mpg4", 131 @"mpga", 132 @"mpp", 133 @"mpt", 134 @"msi", 135 @"ogg", 136 @"otf", 137 @"pct", 138 @"pdf", 139 @"pfa", 140 @"pfb", 141 @"pic", 142 @"pict", 143 @"pkg", 144 @"png", 145 @"pot", 146 @"pps", 147 @"ppt", 148 @"ps", 149 @"psd", 150 @"qt", 151 @"qti", 152 @"qtif", 153 @"qwd", 154 @"qwt", 155 @"qxb", 156 @"qxd", 157 @"qxl", 158 @"qxp", 159 @"qxt", 160 @"ra", 161 @"ram", 162 @"rm", 163 @"rmi", 164 @"rmp", 165 @"scpt", 166 @"sit", 167 @"sitx", 168 @"snd", 169 @"so", 170 @"swf", 171 @"tar", 172 @"tif", 173 @"tiff", 174 @"ttf", 175 @"wav", 176 @"wcm", 177 @"wdb", 178 @"wks", 179 @"wm", 180 @"wma", 181 @"wmd", 182 @"wmf", 183 @"wmv", 184 @"wmx", 185 @"wmz", 186 @"wpd", 187 @"wpl", 188 @"wps", 189 @"wvx", 190 @"xla", 191 @"xlc", 192 @"xlm", 193 @"xls", 194 @"xlt", 195 @"xlw", 196 @"xps", 197 @"zip", 198 nil 199 ]; 200} 201#endif 202 203// <rdar://problem/7007389> CoreTypes UTI map is missing 100+ file extensions that GateKeeper knew about 204// When we disabled content sniffing for file URLs we caused problems with these 100+ extensions that CoreTypes 205// doesn't know about. 206// If CoreTypes is ever brought up to speed we can remove this table and associated code. 207static NSDictionary *createExtensionToMIMETypeMap() 208{ 209 return [[NSDictionary alloc] initWithObjectsAndKeys: 210 @"application/postscript", @"ai", 211 @"text/plain", @"asc", 212 @"application/x-bcpio", @"bcpio", 213 @"image/bmp", @"bmp", 214 @"application/x-netcdf", @"cdf", 215 @"application/octet-stream", @"class", 216 @"application/x-gzip", @"cpgz", 217 @"application/x-cpio", @"cpio", 218 @"application/mac-compactpro", @"cpt", 219 @"application/x-csh", @"csh", 220 @"text/css", @"css", 221 @"application/x-director", @"dcr", 222 @"application/x-director", @"dir", 223 @"application/x-diskcopy", @"dmg", 224 @"application/octet-stream", @"dms", 225 @"application/x-dvi", @"dvi", 226 @"application/x-director", @"dxr", 227 @"application/postscript", @"eps", 228 @"text/x-setext", @"etx", 229 @"application/andrew-inset", @"ez", 230 @"application/vnd.fdf", @"fdf", 231 @"application/octet-stream", @"fla", 232 @"application/x-filemaker", @"fp", 233 @"application/x-filemaker", @"fp2", 234 @"application/x-filemaker", @"fp3", 235 @"application/x-filemaker", @"fp4", 236 @"application/x-filemaker", @"fp5", 237 @"application/x-filemaker", @"fp6", 238 @"application/x-hdf", @"hdf", 239 @"x-conference/x-cooltalk", @"ice", 240 @"image/x-icon", @"ico", 241 @"text/calendar", @"ics", 242 @"image/ief", @"ief", 243 @"model/iges", @"iges", 244 @"model/iges", @"igs", 245 @"application/octet-stream", @"iso", 246 @"text/html", @"jhtml", 247 @"application/x-latex", @"latex", 248 @"application/octet-stream", @"lha", 249 @"application/octet-stream", @"lzh", 250 @"audio/x-mpegurl", @"m3u", 251 @"audio/x-m4p", @"m4p", 252 @"image/x-macpaint", @"mac", 253 @"application/x-troff-man", @"man", 254 @"application/x-troff-me", @"me", 255 @"model/mesh", @"mesh", 256 @"application/vnd.mif", @"mif", 257 @"video/x-sgi-movie", @"movie", 258 @"audio/mpeg", @"mp2", 259 @"audio/mpeg", @"mpga", 260 @"application/x-troff-ms", @"ms", 261 @"model/mesh", @"msh", 262 @"video/vnd.mpegurl", @"mxu", 263 @"application/x-netcdf", @"nc", 264 @"application/oda", @"oda", 265 @"image/x-portable-bitmap", @"pbm", 266 @"image/x-pcx", @"pcx", 267 @"chemical/x-pdb", @"pdb", 268 @"image/x-portable-graymap", @"pgm", 269 @"application/x-chess-pgn", @"pgn", 270 @"audio/scpls", @"pls", 271 @"image/x-portable-anymap", @"pnm", 272 @"image/x-macpaint", @"pnt", 273 @"image/x-macpaint", @"pntg", 274 @"image/x-portable-pixmap", @"ppm", 275 @"image/x-cmu-raster", @"ras", 276 @"image/x-rgb", @"rgb", 277 @"application/x-troff", @"roff", 278 @"audio/x-pn-realaudio-plugin", @"rpm", 279 @"text/richtext", @"rtx", 280 @"text/sgml", @"sgm", 281 @"text/sgml", @"sgml", 282 @"application/x-sh", @"sh", 283 @"application/x-shar", @"shar", 284 @"model/mesh", @"silo", 285 @"application/x-koan", @"skd", 286 @"application/x-koan", @"skm", 287 @"application/x-koan", @"skp", 288 @"application/x-koan", @"skt", 289 @"application/x-diskcopy", @"smi", 290 @"application/octet-stream", @"so", 291 @"application/x-futuresplash", @"spl", 292 @"application/x-wais-source", @"src", 293 @"application/x-sv4cpio", @"sv4cpio", 294 @"application/x-sv4crc", @"sv4crc", 295 @"application/x-shockwave-flash", @"swf", 296 @"application/x-troff", @"t", 297 @"image/x-targa", @"targa", 298 @"application/x-tcl", @"tcl", 299 @"application/x-tex", @"tex", 300 @"application/x-texinfo", @"texi", 301 @"application/x-texinfo", @"texinfo", 302 @"application/x-gzip", @"tgz", 303 @"application/x-bittorrent", @"torrent", 304 @"application/x-troff", @"tr", 305 @"text/tab-separated-values", @"tsv", 306 @"application/x-ustar", @"ustar", 307 @"application/x-cdlink", @"vcd", 308 @"model/vrml", @"vrml", 309 @"image/vnd.wap.wbmp", @"wbmp", 310 @"application/vnd.wap.wbxml", @"wbxml", 311 @"application/x-webarchive", @"webarchive", 312 @"application/x-ms-wmd", @"wmd", 313 @"text/vnd.wap.wml", @"wml", 314 @"application/vnd.wap.wmlc", @"wmlc", 315 @"text/vnd.wap.wmlscript", @"wmls", 316 @"application/vnd.wap.wmlscriptc", @"wmlsc", 317 @"model/vrml", @"wrl", 318 @"application/vnd.adobe.xdp+xml", @"xdp", 319 @"application/vnd.adobe.xfd+xml", @"xfd", 320 @"application/vnd.adobe.xfdf", @"xfdf", 321 @"image/x-xpixmap", @"xpm", 322 @"text/xml", @"xsl", 323 @"image/x-xwindowdump", @"xwd", 324 @"chemical/x-xyz", @"xyz", 325 @"application/x-compress", @"z", 326 nil 327 ]; 328} 329 330static IMP oldNSURLResponseMIMETypeIMP = 0; 331static NSString *webNSURLResponseMIMEType(id, SEL); 332 333void swizzleMIMETypeMethodIfNecessary() 334{ 335 if (!oldNSURLResponseMIMETypeIMP) { 336 Method nsURLResponseMIMETypeMethod = class_getInstanceMethod([NSURLResponse class], @selector(MIMEType)); 337 ASSERT(nsURLResponseMIMETypeMethod); 338#ifdef BUILDING_ON_TIGER 339 oldNSURLResponseMIMETypeIMP = nsURLResponseMIMETypeMethod->method_imp; 340 nsURLResponseMIMETypeMethod->method_imp = (IMP)webNSURLResponseMIMEType; 341#else 342 oldNSURLResponseMIMETypeIMP = method_setImplementation(nsURLResponseMIMETypeMethod, (IMP)webNSURLResponseMIMEType); 343#endif 344 } 345} 346 347static NSString *mimeTypeFromUTITree(CFStringRef uti) 348{ 349 // Check if this UTI has a MIME type. 350 RetainPtr<CFStringRef> mimeType(AdoptCF, UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); 351 if (mimeType) 352 return (NSString *)HardAutorelease(mimeType.releaseRef()); 353 354 // If not, walk the ancestory of this UTI via its "ConformsTo" tags and return the first MIME type we find. 355 RetainPtr<CFDictionaryRef> decl(AdoptCF, UTTypeCopyDeclaration(uti)); 356 if (!decl) 357 return nil; 358 CFTypeRef value = CFDictionaryGetValue(decl.get(), kUTTypeConformsToKey); 359 if (!value) 360 return nil; 361 CFTypeID typeID = CFGetTypeID(value); 362 363 if (typeID == CFStringGetTypeID()) 364 return mimeTypeFromUTITree((CFStringRef)value); 365 366 if (typeID == CFArrayGetTypeID()) { 367 CFArrayRef newTypes = (CFArrayRef)value; 368 CFIndex count = CFArrayGetCount(newTypes); 369 for (CFIndex i = 0; i < count; ++i) { 370 CFTypeRef object = CFArrayGetValueAtIndex(newTypes, i); 371 if (CFGetTypeID(object) != CFStringGetTypeID()) 372 continue; 373 374 if (NSString *mimeType = mimeTypeFromUTITree((CFStringRef)object)) 375 return mimeType; 376 } 377 } 378 379 return nil; 380} 381 382static NSString *webNSURLResponseMIMEType(id self, SEL _cmd) 383{ 384 ASSERT(oldNSURLResponseMIMETypeIMP); 385 NSString *result = oldNSURLResponseMIMETypeIMP(self, _cmd); 386 387#ifdef BUILDING_ON_TIGER 388 // When content sniffing is disabled, Tiger's CFNetwork automatically returns application/octet-stream for certain 389 // extensions even when scouring the UTI maps would end up with a better result, so we'll give a chance for that to happen. 390 if ([[self URL] isFileURL] && [result caseInsensitiveCompare:@"application/octet-stream"] == NSOrderedSame) 391 result = nil; 392#endif 393 394 if (!result) { 395 NSURL *url = [self URL]; 396 if ([url isFileURL]) { 397 if (NSString *extension = [[url path] pathExtension]) { 398 // <rdar://problem/7007389> CoreTypes UTI map is missing 100+ file extensions that GateKeeper knew about 399 // When this radar is resolved, we can remove this file:// url specific code. 400 static NSDictionary *extensionMap = createExtensionToMIMETypeMap(); 401 result = [extensionMap objectForKey:[extension lowercaseString]]; 402 403 if (!result) { 404 // If the Gatekeeper-based map doesn't have a MIME type, we'll try to figure out what it should be by 405 // looking up the file extension in the UTI maps. 406 RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, 0)); 407 result = mimeTypeFromUTITree(uti.get()); 408 } 409 } 410 } 411 } 412 413 if (!result) { 414 static NSString *defaultMIMETypeString = [(NSString *)WebCore::defaultMIMEType() retain]; 415 result = defaultMIMETypeString; 416 } 417 418#ifndef BUILDING_ON_TIGER 419 // <rdar://problem/5321972> Plain text document from HTTP server detected as application/octet-stream 420 // Make the best guess when deciding between "generic binary" and "generic text" using a table of known binary MIME types. 421 if ([result isEqualToString:@"application/octet-stream"] && [self respondsToSelector:@selector(allHeaderFields)] && [[[self allHeaderFields] objectForKey:@"Content-Type"] hasPrefix:@"text/plain"]) { 422 static NSSet *binaryExtensions = createBinaryExtensionsSet(); 423 if (![binaryExtensions containsObject:[[[self suggestedFilename] pathExtension] lowercaseString]]) 424 result = @"text/plain"; 425 } 426 427#endif 428 429#ifdef BUILDING_ON_LEOPARD 430 // Workaround for <rdar://problem/5539824> 431 if ([result isEqualToString:@"text/xml"]) 432 result = @"application/xml"; 433#endif 434 435 return result; 436} 437 438@implementation NSURLResponse (WebCoreURLResponse) 439 440-(NSString *)_webcore_reportedMIMEType 441{ 442 swizzleMIMETypeMethodIfNecessary(); 443 return oldNSURLResponseMIMETypeIMP(self, @selector(_webcore_realMIMEType)); 444} 445 446@end 447