Modernize HTTPHeaderMap iteration
[WebKit-https.git] / Source / WebCore / platform / network / cf / ResourceRequestCFNet.cpp
1 /*
2  * Copyright (C) 2006, 2007, 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "ResourceRequestCFNet.h"
28
29 #include "ResourceRequest.h"
30
31 #if ENABLE(PUBLIC_SUFFIX_LIST)
32 #include "PublicSuffix.h"
33 #endif
34
35 #if USE(CFNETWORK)
36 #include "FormDataStreamCFNet.h"
37 #include <CFNetwork/CFURLRequestPriv.h>
38 #include <wtf/text/CString.h>
39 #if PLATFORM(IOS)
40 #include <CFNetwork/CFNetworkConnectionCachePriv.h>
41 #endif
42 #endif
43
44 #if PLATFORM(MAC)
45 #include "ResourceLoadPriority.h"
46 #include "WebCoreSystemInterface.h"
47 #include <dlfcn.h>
48 #endif
49
50 #if PLATFORM(WIN)
51 #include <WebKitSystemInterface/WebKitSystemInterface.h>
52 #endif
53
54 namespace WebCore {
55
56 // FIXME: Make this a NetworkingContext property.
57 #if PLATFORM(IOS)
58 bool ResourceRequest::s_httpPipeliningEnabled = true;
59 #else
60 bool ResourceRequest::s_httpPipeliningEnabled = false;
61 #endif
62
63 #if USE(CFNETWORK)
64
65 typedef void (*CFURLRequestSetContentDispositionEncodingFallbackArrayFunction)(CFMutableURLRequestRef, CFArrayRef);
66 typedef CFArrayRef (*CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction)(CFURLRequestRef);
67
68 #if PLATFORM(WIN)
69 static HMODULE findCFNetworkModule()
70 {
71 #ifndef DEBUG_ALL
72     return GetModuleHandleA("CFNetwork");
73 #else
74     return GetModuleHandleA("CFNetwork_debug");
75 #endif
76 }
77
78 static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction()
79 {
80     return reinterpret_cast<CFURLRequestSetContentDispositionEncodingFallbackArrayFunction>(GetProcAddress(findCFNetworkModule(), "_CFURLRequestSetContentDispositionEncodingFallbackArray"));
81 }
82
83 static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction()
84 {
85     return reinterpret_cast<CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction>(GetProcAddress(findCFNetworkModule(), "_CFURLRequestCopyContentDispositionEncodingFallbackArray"));
86 }
87 #elif PLATFORM(MAC)
88 static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction()
89 {
90     return reinterpret_cast<CFURLRequestSetContentDispositionEncodingFallbackArrayFunction>(dlsym(RTLD_DEFAULT, "_CFURLRequestSetContentDispositionEncodingFallbackArray"));
91 }
92
93 static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction()
94 {
95     return reinterpret_cast<CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction>(dlsym(RTLD_DEFAULT, "_CFURLRequestCopyContentDispositionEncodingFallbackArray"));
96 }
97 #endif
98
99 static void setContentDispositionEncodingFallbackArray(CFMutableURLRequestRef request, CFArrayRef fallbackArray)
100 {
101     static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction function = findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction();
102     if (function)
103         function(request, fallbackArray);
104 }
105
106 static CFArrayRef copyContentDispositionEncodingFallbackArray(CFURLRequestRef request)
107 {
108     static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction function = findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction();
109     if (!function)
110         return 0;
111     return function(request);
112 }
113
114 CFURLRequestRef ResourceRequest::cfURLRequest(HTTPBodyUpdatePolicy bodyPolicy) const
115 {
116     updatePlatformRequest(bodyPolicy);
117
118     return m_cfRequest.get();
119 }
120
121 static inline void setHeaderFields(CFMutableURLRequestRef request, const HTTPHeaderMap& requestHeaders) 
122 {
123     // Remove existing headers first, as some of them may no longer be present in the map.
124     RetainPtr<CFDictionaryRef> oldHeaderFields = adoptCF(CFURLRequestCopyAllHTTPHeaderFields(request));
125     CFIndex oldHeaderFieldCount = CFDictionaryGetCount(oldHeaderFields.get());
126     if (oldHeaderFieldCount) {
127         Vector<CFStringRef> oldHeaderFieldNames(oldHeaderFieldCount);
128         CFDictionaryGetKeysAndValues(oldHeaderFields.get(), reinterpret_cast<const void**>(&oldHeaderFieldNames[0]), 0);
129         for (CFIndex i = 0; i < oldHeaderFieldCount; ++i)
130             CFURLRequestSetHTTPHeaderFieldValue(request, oldHeaderFieldNames[i], 0);
131     }
132
133     for (const auto& header : requestHeaders)
134         CFURLRequestSetHTTPHeaderFieldValue(request, header.key.string().createCFString().get(), header.value.createCFString().get());
135 }
136
137 void ResourceRequest::doUpdatePlatformRequest()
138 {
139     CFMutableURLRequestRef cfRequest;
140
141     RetainPtr<CFURLRef> url = ResourceRequest::url().createCFURL();
142     RetainPtr<CFURLRef> firstPartyForCookies = ResourceRequest::firstPartyForCookies().createCFURL();
143     if (m_cfRequest) {
144         cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
145         CFURLRequestSetURL(cfRequest, url.get());
146         CFURLRequestSetMainDocumentURL(cfRequest, firstPartyForCookies.get());
147         CFURLRequestSetCachePolicy(cfRequest, (CFURLRequestCachePolicy)cachePolicy());
148         CFURLRequestSetTimeoutInterval(cfRequest, timeoutInterval());
149     } else
150         cfRequest = CFURLRequestCreateMutable(0, url.get(), (CFURLRequestCachePolicy)cachePolicy(), timeoutInterval(), firstPartyForCookies.get());
151
152     CFURLRequestSetHTTPRequestMethod(cfRequest, httpMethod().createCFString().get());
153
154     if (httpPipeliningEnabled())
155         wkHTTPRequestEnablePipelining(cfRequest);
156
157     wkSetHTTPRequestPriority(cfRequest, toPlatformRequestPriority(m_priority));
158
159 #if !PLATFORM(WIN)
160     wkCFURLRequestAllowAllPostCaching(cfRequest);
161 #endif
162
163     setHeaderFields(cfRequest, httpHeaderFields());
164
165     CFURLRequestSetShouldHandleHTTPCookies(cfRequest, allowCookies());
166
167     unsigned fallbackCount = m_responseContentDispositionEncodingFallbackArray.size();
168     RetainPtr<CFMutableArrayRef> encodingFallbacks = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, fallbackCount, 0));
169     for (unsigned i = 0; i != fallbackCount; ++i) {
170         RetainPtr<CFStringRef> encodingName = m_responseContentDispositionEncodingFallbackArray[i].createCFString();
171         CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding(encodingName.get());
172         if (encoding != kCFStringEncodingInvalidId)
173             CFArrayAppendValue(encodingFallbacks.get(), reinterpret_cast<const void*>(encoding));
174     }
175     setContentDispositionEncodingFallbackArray(cfRequest, encodingFallbacks.get());
176
177     if (m_cfRequest) {
178         RetainPtr<CFHTTPCookieStorageRef> cookieStorage = adoptCF(CFURLRequestCopyHTTPCookieStorage(m_cfRequest.get()));
179         if (cookieStorage)
180             CFURLRequestSetHTTPCookieStorage(cfRequest, cookieStorage.get());
181         CFURLRequestSetHTTPCookieStorageAcceptPolicy(cfRequest, CFURLRequestGetHTTPCookieStorageAcceptPolicy(m_cfRequest.get()));
182         CFURLRequestSetSSLProperties(cfRequest, CFURLRequestGetSSLProperties(m_cfRequest.get()));
183     }
184
185 #if ENABLE(CACHE_PARTITIONING)
186     String partition = cachePartition();
187     if (!partition.isNull() && !partition.isEmpty()) {
188         CString utf8String = partition.utf8();
189         RetainPtr<CFStringRef> partitionValue = adoptCF(CFStringCreateWithBytes(0, reinterpret_cast<const UInt8*>(utf8String.data()), utf8String.length(), kCFStringEncodingUTF8, false));
190         _CFURLRequestSetProtocolProperty(cfRequest, wkCachePartitionKey(), partitionValue.get());
191     }
192 #endif
193
194     m_cfRequest = adoptCF(cfRequest);
195 #if PLATFORM(MAC)
196     updateNSURLRequest();
197 #endif
198 }
199
200 void ResourceRequest::doUpdatePlatformHTTPBody()
201 {
202     CFMutableURLRequestRef cfRequest;
203
204     RetainPtr<CFURLRef> url = ResourceRequest::url().createCFURL();
205     RetainPtr<CFURLRef> firstPartyForCookies = ResourceRequest::firstPartyForCookies().createCFURL();
206     if (m_cfRequest) {
207         cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
208         CFURLRequestSetURL(cfRequest, url.get());
209         CFURLRequestSetMainDocumentURL(cfRequest, firstPartyForCookies.get());
210         CFURLRequestSetCachePolicy(cfRequest, (CFURLRequestCachePolicy)cachePolicy());
211         CFURLRequestSetTimeoutInterval(cfRequest, timeoutInterval());
212     } else
213         cfRequest = CFURLRequestCreateMutable(0, url.get(), (CFURLRequestCachePolicy)cachePolicy(), timeoutInterval(), firstPartyForCookies.get());
214
215     RefPtr<FormData> formData = httpBody();
216     if (formData && !formData->isEmpty())
217         WebCore::setHTTPBody(cfRequest, formData);
218
219     if (RetainPtr<CFReadStreamRef> bodyStream = adoptCF(CFURLRequestCopyHTTPRequestBodyStream(cfRequest))) {
220         // For streams, provide a Content-Length to avoid using chunked encoding, and to get accurate total length in callbacks.
221         RetainPtr<CFStringRef> lengthString = adoptCF(static_cast<CFStringRef>(CFReadStreamCopyProperty(bodyStream.get(), formDataStreamLengthPropertyName())));
222         if (lengthString) {
223             CFURLRequestSetHTTPHeaderFieldValue(cfRequest, CFSTR("Content-Length"), lengthString.get());
224             // Since resource request is already marked updated, we need to keep it up to date too.
225             ASSERT(m_resourceRequestUpdated);
226             m_httpHeaderFields.set("Content-Length", lengthString.get());
227         }
228     }
229
230     m_cfRequest = adoptCF(cfRequest);
231 #if PLATFORM(MAC)
232     updateNSURLRequest();
233 #endif
234 }
235
236 void ResourceRequest::updateFromDelegatePreservingOldHTTPBody(const ResourceRequest& delegateProvidedRequest)
237 {
238     RefPtr<FormData> oldHTTPBody = httpBody();
239
240     *this = delegateProvidedRequest;
241     setHTTPBody(oldHTTPBody.release());
242 }
243
244 void ResourceRequest::doUpdateResourceRequest()
245 {
246     if (!m_cfRequest) {
247 #if PLATFORM(IOS)
248         // <rdar://problem/9913526>
249         // This is a hack to mimic the subtle behaviour of the Foundation based ResourceRequest
250         // code. That code does not reset m_httpMethod if the NSURLRequest is nil. I filed
251         // <https://bugs.webkit.org/show_bug.cgi?id=66336> to track that.
252         // Another related bug is <https://bugs.webkit.org/show_bug.cgi?id=66350>. Fixing that
253         // would, ideally, allow us to not have this hack. But unfortunately that caused layout test
254         // failures.
255         // Removal of this hack is tracked by <rdar://problem/9970499>.
256
257         String httpMethod = m_httpMethod;
258         *this = ResourceRequest();
259         m_httpMethod = httpMethod;
260 #else
261         *this = ResourceRequest();
262 #endif
263         return;
264     }
265
266     m_url = CFURLRequestGetURL(m_cfRequest.get());
267
268     m_cachePolicy = (ResourceRequestCachePolicy)CFURLRequestGetCachePolicy(m_cfRequest.get());
269     m_timeoutInterval = CFURLRequestGetTimeoutInterval(m_cfRequest.get());
270     m_firstPartyForCookies = CFURLRequestGetMainDocumentURL(m_cfRequest.get());
271     if (CFStringRef method = CFURLRequestCopyHTTPRequestMethod(m_cfRequest.get())) {
272         m_httpMethod = method;
273         CFRelease(method);
274     }
275     m_allowCookies = CFURLRequestShouldHandleHTTPCookies(m_cfRequest.get());
276
277     m_priority = toResourceLoadPriority(wkGetHTTPRequestPriority(m_cfRequest.get()));
278
279     m_httpHeaderFields.clear();
280     if (CFDictionaryRef headers = CFURLRequestCopyAllHTTPHeaderFields(m_cfRequest.get())) {
281         CFIndex headerCount = CFDictionaryGetCount(headers);
282         Vector<const void*, 128> keys(headerCount);
283         Vector<const void*, 128> values(headerCount);
284         CFDictionaryGetKeysAndValues(headers, keys.data(), values.data());
285         for (int i = 0; i < headerCount; ++i)
286             m_httpHeaderFields.set((CFStringRef)keys[i], (CFStringRef)values[i]);
287         CFRelease(headers);
288     }
289
290     m_responseContentDispositionEncodingFallbackArray.clear();
291     RetainPtr<CFArrayRef> encodingFallbacks = adoptCF(copyContentDispositionEncodingFallbackArray(m_cfRequest.get()));
292     if (encodingFallbacks) {
293         CFIndex count = CFArrayGetCount(encodingFallbacks.get());
294         for (CFIndex i = 0; i < count; ++i) {
295             CFStringEncoding encoding = reinterpret_cast<CFIndex>(CFArrayGetValueAtIndex(encodingFallbacks.get(), i));
296             if (encoding != kCFStringEncodingInvalidId)
297                 m_responseContentDispositionEncodingFallbackArray.append(CFStringConvertEncodingToIANACharSetName(encoding));
298         }
299     }
300
301 #if ENABLE(CACHE_PARTITIONING)
302     RetainPtr<CFStringRef> cachePartition = adoptCF(static_cast<CFStringRef>(_CFURLRequestCopyProtocolPropertyForKey(m_cfRequest.get(), wkCachePartitionKey())));
303     if (cachePartition)
304         m_cachePartition = cachePartition.get();
305 #endif
306 }
307
308 void ResourceRequest::doUpdateResourceHTTPBody()
309 {
310     if (!m_cfRequest) {
311         m_httpBody = 0;
312         return;
313     }
314
315     if (RetainPtr<CFDataRef> bodyData = adoptCF(CFURLRequestCopyHTTPRequestBody(m_cfRequest.get())))
316         m_httpBody = FormData::create(CFDataGetBytePtr(bodyData.get()), CFDataGetLength(bodyData.get()));
317     else if (RetainPtr<CFReadStreamRef> bodyStream = adoptCF(CFURLRequestCopyHTTPRequestBodyStream(m_cfRequest.get()))) {
318         FormData* formData = httpBodyFromStream(bodyStream.get());
319         // There is no FormData object if a client provided a custom data stream.
320         // We shouldn't be looking at http body after client callbacks.
321         ASSERT(formData);
322         if (formData)
323             m_httpBody = formData;
324     }
325 }
326
327
328 void ResourceRequest::setStorageSession(CFURLStorageSessionRef storageSession)
329 {
330     updatePlatformRequest();
331
332     CFMutableURLRequestRef cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
333     wkSetRequestStorageSession(storageSession, cfRequest);
334     m_cfRequest = adoptCF(cfRequest);
335 #if PLATFORM(MAC)
336     updateNSURLRequest();
337 #endif
338 }
339
340 #if PLATFORM(MAC)
341 void ResourceRequest::applyWebArchiveHackForMail()
342 {
343 #if !PLATFORM(IOS)
344     // Hack because Mail checks for this property to detect data / archive loads
345     _CFURLRequestSetProtocolProperty(cfURLRequest(DoNotUpdateHTTPBody), CFSTR("WebDataRequest"), CFSTR(""));
346 #endif
347 }
348 #endif
349
350 #endif // USE(CFNETWORK)
351
352 bool ResourceRequest::httpPipeliningEnabled()
353 {
354     return s_httpPipeliningEnabled;
355 }
356
357 void ResourceRequest::setHTTPPipeliningEnabled(bool flag)
358 {
359     s_httpPipeliningEnabled = flag;
360 }
361
362 #if ENABLE(CACHE_PARTITIONING)
363 String ResourceRequest::partitionName(const String& domain)
364 {
365     if (domain.isNull())
366         return emptyString();
367 #if ENABLE(PUBLIC_SUFFIX_LIST)
368     String highLevel = topPrivatelyControlledDomain(domain);
369     if (highLevel.isNull())
370         return emptyString();
371     return highLevel;
372 #else
373     return domain;
374 #endif
375 }
376 #endif
377
378 PassOwnPtr<CrossThreadResourceRequestData> ResourceRequest::doPlatformCopyData(PassOwnPtr<CrossThreadResourceRequestData> data) const
379 {
380 #if ENABLE(CACHE_PARTITIONING)
381     data->m_cachePartition = m_cachePartition;
382 #endif
383     return data;
384 }
385
386 void ResourceRequest::doPlatformAdopt(PassOwnPtr<CrossThreadResourceRequestData> data)
387 {
388 #if ENABLE(CACHE_PARTITIONING)
389     m_cachePartition = data->m_cachePartition;
390 #else
391     UNUSED_PARAM(data);
392 #endif
393 }
394
395 unsigned initializeMaximumHTTPConnectionCountPerHost()
396 {
397     static const unsigned preferredConnectionCount = 6;
398     static const unsigned unlimitedConnectionCount = 10000;
399
400     wkInitializeMaximumHTTPConnectionCountPerHost(preferredConnectionCount);
401
402     Boolean keyExistsAndHasValidFormat = false;
403     Boolean prefValue = CFPreferencesGetAppBooleanValue(CFSTR("WebKitEnableHTTPPipelining"), kCFPreferencesCurrentApplication, &keyExistsAndHasValidFormat);
404     if (keyExistsAndHasValidFormat)
405         ResourceRequest::setHTTPPipeliningEnabled(prefValue);
406
407     wkSetHTTPRequestMaximumPriority(toPlatformRequestPriority(ResourceLoadPriorityHighest));
408 #if !PLATFORM(WIN)
409     // FIXME: <rdar://problem/9375609> Implement minimum fast lane priority setting on Windows
410     wkSetHTTPRequestMinimumFastLanePriority(toPlatformRequestPriority(ResourceLoadPriorityMedium));
411 #endif
412
413     return unlimitedConnectionCount;
414 }
415     
416 #if PLATFORM(IOS)
417 void initializeHTTPConnectionSettingsOnStartup()
418 {
419     // This need to be called from WebKitInitialize so the calls happen early enough, before any requests are made. <rdar://problem/9691871>
420     // Desktop doesn't have early initialization so it is not clear how this should be done there. The CFNetwork SPI probably
421     // needs to become more forgiving.
422     // We can't read settings here as this is called too early for that. All values need to be constants.
423     static const unsigned preferredConnectionCount = 6;
424     static const unsigned fastLaneConnectionCount = 1;
425     wkInitializeMaximumHTTPConnectionCountPerHost(preferredConnectionCount);
426     wkSetHTTPRequestMaximumPriority(ResourceLoadPriorityHighest);
427     wkSetHTTPRequestMinimumFastLanePriority(ResourceLoadPriorityMedium);
428     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPNumFastLanes, fastLaneConnectionCount);
429 }
430 #endif
431
432 } // namespace WebCore