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
convertMIMEType(CFMutableStringRef mimeType)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
convertWebResourceDataToString(CFMutableDictionaryRef resource)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
normalizeHTTPResponseHeaderFields(CFMutableDictionaryRef fields)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
normalizeWebResourceURL(CFMutableStringRef webResourceURL)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
convertWebResourceResponseToDictionary(CFMutableDictionaryRef propertyList)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
compareResourceURLs(const void * val1,const void * val2,void * context)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
createXMLStringFromWebArchiveData(CFDataRef webArchiveData)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