Home | History | Annotate | Download | only in cf
      1 /*
      2  * Copyright (C) 2007, 2008, 2009, 2010 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  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "WebArchiveDumpSupport.h"
     28 
     29 #include <CoreFoundation/CoreFoundation.h>
     30 #include <CFNetwork/CFNetwork.h>
     31 #include <wtf/RetainPtr.h>
     32 
     33 extern "C" {
     34 
     35 CFURLRef CFURLResponseGetURL(CFURLResponseRef response);
     36 CFStringRef CFURLResponseGetMIMEType(CFURLResponseRef response);
     37 CFStringRef CFURLResponseGetTextEncodingName(CFURLResponseRef response);
     38 SInt64 CFURLResponseGetExpectedContentLength(CFURLResponseRef response);
     39 CFHTTPMessageRef CFURLResponseGetHTTPResponse(CFURLResponseRef response);
     40 
     41 CFTypeID CFURLResponseGetTypeID(void);
     42 
     43 }
     44 
     45 static void convertMIMEType(CFMutableStringRef mimeType)
     46 {
     47 #ifdef BUILDING_ON_LEOPARD
     48     // Workaround for <rdar://problem/5539824> on Leopard
     49     if (CFStringCompare(mimeType, CFSTR("text/xml"), kCFCompareAnchored | kCFCompareCaseInsensitive) == kCFCompareEqualTo)
     50         CFStringReplaceAll(mimeType, CFSTR("application/xml"));
     51 #endif
     52     // Workaround for <rdar://problem/6234318> with Dashcode 2.0
     53     if (CFStringCompare(mimeType, CFSTR("application/x-javascript"), kCFCompareAnchored | kCFCompareCaseInsensitive) == kCFCompareEqualTo)
     54         CFStringReplaceAll(mimeType, CFSTR("text/javascript"));
     55 }
     56 
     57 static void convertWebResourceDataToString(CFMutableDictionaryRef resource)
     58 {
     59     CFMutableStringRef mimeType = (CFMutableStringRef)CFDictionaryGetValue(resource, CFSTR("WebResourceMIMEType"));
     60     CFStringLowercase(mimeType, CFLocaleGetSystem());
     61     convertMIMEType(mimeType);
     62 
     63     CFArrayRef supportedMIMETypes = supportedNonImageMIMETypes();
     64     if (CFStringHasPrefix(mimeType, CFSTR("text/")) || CFArrayContainsValue(supportedMIMETypes, CFRangeMake(0, CFArrayGetCount(supportedMIMETypes)), mimeType)) {
     65         CFStringRef textEncodingName = static_cast<CFStringRef>(CFDictionaryGetValue(resource, CFSTR("WebResourceTextEncodingName")));
     66         CFStringEncoding stringEncoding;
     67         if (textEncodingName && CFStringGetLength(textEncodingName))
     68             stringEncoding = CFStringConvertIANACharSetNameToEncoding(textEncodingName);
     69         else
     70             stringEncoding = kCFStringEncodingUTF8;
     71 
     72         CFDataRef data = static_cast<CFDataRef>(CFDictionaryGetValue(resource, CFSTR("WebResourceData")));
     73         RetainPtr<CFStringRef> dataAsString(AdoptCF, CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, stringEncoding));
     74         if (dataAsString)
     75             CFDictionarySetValue(resource, CFSTR("WebResourceData"), dataAsString.get());
     76     }
     77 }
     78 
     79 static void normalizeHTTPResponseHeaderFields(CFMutableDictionaryRef fields)
     80 {
     81     // Normalize headers
     82     if (CFDictionaryContainsKey(fields, CFSTR("Date")))
     83         CFDictionarySetValue(fields, CFSTR("Date"), CFSTR("Sun, 16 Nov 2008 17:00:00 GMT"));
     84     if (CFDictionaryContainsKey(fields, CFSTR("Last-Modified")))
     85         CFDictionarySetValue(fields, CFSTR("Last-Modified"), CFSTR("Sun, 16 Nov 2008 16:55:00 GMT"));
     86     if (CFDictionaryContainsKey(fields, CFSTR("Etag")))
     87         CFDictionarySetValue(fields, CFSTR("Etag"), CFSTR("\"301925-21-45c7d72d3e780\""));
     88     if (CFDictionaryContainsKey(fields, CFSTR("Server")))
     89         CFDictionarySetValue(fields, CFSTR("Server"), CFSTR("Apache/2.2.9 (Unix) mod_ssl/2.2.9 OpenSSL/0.9.7l PHP/5.2.6"));
     90 
     91     // Remove headers
     92     CFDictionaryRemoveValue(fields, CFSTR("Connection"));
     93     CFDictionaryRemoveValue(fields, CFSTR("Keep-Alive"));
     94 }
     95 
     96 static void normalizeWebResourceURL(CFMutableStringRef webResourceURL)
     97 {
     98     static CFIndex fileUrlLength = CFStringGetLength(CFSTR("file://"));
     99     CFRange layoutTestsWebArchivePathRange = CFStringFind(webResourceURL, CFSTR("/LayoutTests/"), kCFCompareBackwards);
    100     if (layoutTestsWebArchivePathRange.location == kCFNotFound)
    101         return;
    102     CFRange currentWorkingDirectoryRange = CFRangeMake(fileUrlLength, layoutTestsWebArchivePathRange.location - fileUrlLength);
    103     CFStringReplace(webResourceURL, currentWorkingDirectoryRange, CFSTR(""));
    104 }
    105 
    106 static void convertWebResourceResponseToDictionary(CFMutableDictionaryRef propertyList)
    107 {
    108     CFDataRef responseData = static_cast<CFDataRef>(CFDictionaryGetValue(propertyList, CFSTR("WebResourceResponse"))); // WebResourceResponseKey in WebResource.m
    109     if (CFGetTypeID(responseData) != CFDataGetTypeID())
    110         return;
    111 
    112     RetainPtr<CFURLResponseRef> response(AdoptCF, createCFURLResponseFromResponseData(responseData));
    113     if (!response)
    114         return;
    115 
    116     RetainPtr<CFMutableDictionaryRef> responseDictionary(AdoptCF, CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
    117 
    118     RetainPtr<CFMutableStringRef> urlString(AdoptCF, CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFURLGetString(CFURLResponseGetURL(response.get()))));
    119     normalizeWebResourceURL(urlString.get());
    120     CFDictionarySetValue(responseDictionary.get(), CFSTR("URL"), urlString.get());
    121 
    122     RetainPtr<CFMutableStringRef> mimeTypeString(AdoptCF, CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFURLResponseGetMIMEType(response.get())));
    123     convertMIMEType(mimeTypeString.get());
    124     CFDictionarySetValue(responseDictionary.get(), CFSTR("MIMEType"), mimeTypeString.get());
    125 
    126     CFStringRef textEncodingName = CFURLResponseGetTextEncodingName(response.get());
    127     if (textEncodingName)
    128         CFDictionarySetValue(responseDictionary.get(), CFSTR("textEncodingName"), textEncodingName);
    129 
    130     SInt64 expectedContentLength = CFURLResponseGetExpectedContentLength(response.get());
    131     RetainPtr<CFNumberRef> expectedContentLengthNumber(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &expectedContentLength));
    132     CFDictionarySetValue(responseDictionary.get(), CFSTR("expectedContentLength"), expectedContentLengthNumber.get());
    133 
    134     if (CFHTTPMessageRef httpMessage = CFURLResponseGetHTTPResponse(response.get())) {
    135         RetainPtr<CFDictionaryRef> allHeaders(AdoptCF, CFHTTPMessageCopyAllHeaderFields(httpMessage));
    136         RetainPtr<CFMutableDictionaryRef> allHeaderFields(AdoptCF, CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, allHeaders.get()));
    137         normalizeHTTPResponseHeaderFields(allHeaderFields.get());
    138         CFDictionarySetValue(responseDictionary.get(), CFSTR("allHeaderFields"), allHeaderFields.get());
    139 
    140         CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(httpMessage);
    141         RetainPtr<CFNumberRef> statusCodeNumber(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &statusCode));
    142         CFDictionarySetValue(responseDictionary.get(), CFSTR("statusCode"), statusCodeNumber.get());
    143     }
    144 
    145     CFDictionarySetValue(propertyList, CFSTR("WebResourceResponse"), responseDictionary.get());
    146 }
    147 
    148 static CFComparisonResult compareResourceURLs(const void *val1, const void *val2, void *context)
    149 {
    150     CFStringRef url1 = static_cast<CFStringRef>(CFDictionaryGetValue(static_cast<CFDictionaryRef>(val1), CFSTR("WebResourceURL")));
    151     CFStringRef url2 = static_cast<CFStringRef>(CFDictionaryGetValue(static_cast<CFDictionaryRef>(val2), CFSTR("WebResourceURL")));
    152 
    153     return CFStringCompare(url1, url2, kCFCompareAnchored);
    154 }
    155 
    156 CFStringRef createXMLStringFromWebArchiveData(CFDataRef webArchiveData)
    157 {
    158     CFErrorRef error = 0;
    159     CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0;
    160 
    161 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)
    162     CFIndex bytesCount = CFDataGetLength(webArchiveData);
    163     RetainPtr<CFReadStreamRef> readStream(AdoptCF, CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, CFDataGetBytePtr(webArchiveData), bytesCount, kCFAllocatorNull));
    164     CFReadStreamOpen(readStream.get());
    165     RetainPtr<CFMutableDictionaryRef> propertyList(AdoptCF, (CFMutableDictionaryRef)CFPropertyListCreateFromStream(kCFAllocatorDefault, readStream.get(), bytesCount, kCFPropertyListMutableContainersAndLeaves, &format, 0));
    166     CFReadStreamClose(readStream.get());
    167 #else
    168     RetainPtr<CFMutableDictionaryRef> propertyList(AdoptCF, (CFMutableDictionaryRef)CFPropertyListCreateWithData(kCFAllocatorDefault, webArchiveData, kCFPropertyListMutableContainersAndLeaves, &format, &error));
    169 #endif
    170 
    171     if (!propertyList) {
    172         if (error)
    173             return CFErrorCopyDescription(error);
    174         return static_cast<CFStringRef>(CFRetain(CFSTR("An unknown error occurred converting data to property list.")));
    175     }
    176 
    177     RetainPtr<CFMutableArrayRef> resources(AdoptCF, CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
    178     CFArrayAppendValue(resources.get(), propertyList.get());
    179 
    180     while (CFArrayGetCount(resources.get())) {
    181         RetainPtr<CFMutableDictionaryRef> resourcePropertyList = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(resources.get(), 0);
    182         CFArrayRemoveValueAtIndex(resources.get(), 0);
    183 
    184         CFMutableDictionaryRef mainResource = (CFMutableDictionaryRef)CFDictionaryGetValue(resourcePropertyList.get(), CFSTR("WebMainResource"));
    185         normalizeWebResourceURL((CFMutableStringRef)CFDictionaryGetValue(mainResource, CFSTR("WebResourceURL")));
    186         convertWebResourceDataToString(mainResource);
    187 
    188         // Add subframeArchives to list for processing
    189         CFMutableArrayRef subframeArchives = (CFMutableArrayRef)CFDictionaryGetValue(resourcePropertyList.get(), CFSTR("WebSubframeArchives")); // WebSubframeArchivesKey in WebArchive.m
    190         if (subframeArchives)
    191             CFArrayAppendArray(resources.get(), subframeArchives, CFRangeMake(0, CFArrayGetCount(subframeArchives)));
    192 
    193         CFMutableArrayRef subresources = (CFMutableArrayRef)CFDictionaryGetValue(resourcePropertyList.get(), CFSTR("WebSubresources")); // WebSubresourcesKey in WebArchive.m
    194         if (!subresources)
    195             continue;
    196 
    197         CFIndex subresourcesCount = CFArrayGetCount(subresources);
    198         for (CFIndex i = 0; i < subresourcesCount; ++i) {
    199             CFMutableDictionaryRef subresourcePropertyList = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(subresources, i);
    200             normalizeWebResourceURL((CFMutableStringRef)CFDictionaryGetValue(subresourcePropertyList, CFSTR("WebResourceURL")));
    201             convertWebResourceResponseToDictionary(subresourcePropertyList);
    202             convertWebResourceDataToString(subresourcePropertyList);
    203         }
    204 
    205         // Sort the subresources so they're always in a predictable order for the dump
    206         CFArraySortValues(subresources, CFRangeMake(0, CFArrayGetCount(subresources)), compareResourceURLs, 0);
    207     }
    208 
    209     error = 0;
    210 
    211 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)
    212     RetainPtr<CFDataRef> xmlData(AdoptCF, CFPropertyListCreateXMLData(kCFAllocatorDefault, propertyList.get()));
    213 #else
    214     RetainPtr<CFDataRef> xmlData(AdoptCF, CFPropertyListCreateData(kCFAllocatorDefault, propertyList.get(), kCFPropertyListXMLFormat_v1_0, 0, &error));
    215 #endif
    216 
    217     if (!xmlData) {
    218         if (error)
    219             return CFErrorCopyDescription(error);
    220         return static_cast<CFStringRef>(CFRetain(CFSTR("An unknown error occurred converting property list to data.")));
    221     }
    222 
    223     RetainPtr<CFStringRef> xmlString(AdoptCF, CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, xmlData.get(), kCFStringEncodingUTF8));
    224     RetainPtr<CFMutableStringRef> string(AdoptCF, CFStringCreateMutableCopy(kCFAllocatorDefault, 0, xmlString.get()));
    225 
    226     // Replace "Apple Computer" with "Apple" in the DTD declaration.
    227     CFStringFindAndReplace(string.get(), CFSTR("-//Apple Computer//"), CFSTR("-//Apple//"), CFRangeMake(0, CFStringGetLength(string.get())), 0);
    228 
    229     return string.releaseRef();
    230 }
    231