Drop ResourceLoadPriorityUnresolved resource load priority and use Optional<> instead
[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 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 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 "HTTPHeaderNames.h"
30 #include "ResourceRequest.h"
31 #include <wtf/PassOwnPtr.h>
32
33 #if ENABLE(PUBLIC_SUFFIX_LIST)
34 #include "PublicSuffix.h"
35 #endif
36
37 #if USE(CFNETWORK)
38 #include "FormDataStreamCFNet.h"
39 #include <CFNetwork/CFURLRequestPriv.h>
40 #include <wtf/text/CString.h>
41 #endif
42
43 #if PLATFORM(IOS)
44 #include "CFNetworkConnectionCacheSPI.h"
45 #endif
46
47 #if PLATFORM(COCOA)
48 #include "ResourceLoadPriority.h"
49 #include "WebCoreSystemInterface.h"
50 #include <dlfcn.h>
51 #endif
52
53 #if PLATFORM(WIN)
54 #include <WebKitSystemInterface/WebKitSystemInterface.h>
55 #endif
56
57 namespace WebCore {
58
59 // FIXME: Make this a NetworkingContext property.
60 #if PLATFORM(IOS)
61 bool ResourceRequest::s_httpPipeliningEnabled = true;
62 #else
63 bool ResourceRequest::s_httpPipeliningEnabled = false;
64 #endif
65
66 #if USE(CFNETWORK)
67
68 typedef void (*CFURLRequestSetContentDispositionEncodingFallbackArrayFunction)(CFMutableURLRequestRef, CFArrayRef);
69 typedef CFArrayRef (*CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction)(CFURLRequestRef);
70
71 #if PLATFORM(WIN)
72 static HMODULE findCFNetworkModule()
73 {
74 #ifndef DEBUG_ALL
75     return GetModuleHandleA("CFNetwork");
76 #else
77     return GetModuleHandleA("CFNetwork_debug");
78 #endif
79 }
80
81 static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction()
82 {
83     return reinterpret_cast<CFURLRequestSetContentDispositionEncodingFallbackArrayFunction>(GetProcAddress(findCFNetworkModule(), "_CFURLRequestSetContentDispositionEncodingFallbackArray"));
84 }
85
86 static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction()
87 {
88     return reinterpret_cast<CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction>(GetProcAddress(findCFNetworkModule(), "_CFURLRequestCopyContentDispositionEncodingFallbackArray"));
89 }
90 #elif PLATFORM(COCOA)
91 static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction()
92 {
93     return reinterpret_cast<CFURLRequestSetContentDispositionEncodingFallbackArrayFunction>(dlsym(RTLD_DEFAULT, "_CFURLRequestSetContentDispositionEncodingFallbackArray"));
94 }
95
96 static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction()
97 {
98     return reinterpret_cast<CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction>(dlsym(RTLD_DEFAULT, "_CFURLRequestCopyContentDispositionEncodingFallbackArray"));
99 }
100 #endif
101
102 static void setContentDispositionEncodingFallbackArray(CFMutableURLRequestRef request, CFArrayRef fallbackArray)
103 {
104     static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction function = findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction();
105     if (function)
106         function(request, fallbackArray);
107 }
108
109 static CFArrayRef copyContentDispositionEncodingFallbackArray(CFURLRequestRef request)
110 {
111     static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction function = findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction();
112     if (!function)
113         return 0;
114     return function(request);
115 }
116
117 CFURLRequestRef ResourceRequest::cfURLRequest(HTTPBodyUpdatePolicy bodyPolicy) const
118 {
119     updatePlatformRequest(bodyPolicy);
120
121     return m_cfRequest.get();
122 }
123
124 static inline void setHeaderFields(CFMutableURLRequestRef request, const HTTPHeaderMap& requestHeaders) 
125 {
126     // Remove existing headers first, as some of them may no longer be present in the map.
127     RetainPtr<CFDictionaryRef> oldHeaderFields = adoptCF(CFURLRequestCopyAllHTTPHeaderFields(request));
128     CFIndex oldHeaderFieldCount = CFDictionaryGetCount(oldHeaderFields.get());
129     if (oldHeaderFieldCount) {
130         Vector<CFStringRef> oldHeaderFieldNames(oldHeaderFieldCount);
131         CFDictionaryGetKeysAndValues(oldHeaderFields.get(), reinterpret_cast<const void**>(&oldHeaderFieldNames[0]), 0);
132         for (CFIndex i = 0; i < oldHeaderFieldCount; ++i)
133             CFURLRequestSetHTTPHeaderFieldValue(request, oldHeaderFieldNames[i], 0);
134     }
135
136     for (const auto& header : requestHeaders)
137         CFURLRequestSetHTTPHeaderFieldValue(request, header.key.createCFString().get(), header.value.createCFString().get());
138 }
139
140 void ResourceRequest::doUpdatePlatformRequest()
141 {
142     CFMutableURLRequestRef cfRequest;
143
144     RetainPtr<CFURLRef> url = ResourceRequest::url().createCFURL();
145     RetainPtr<CFURLRef> firstPartyForCookies = ResourceRequest::firstPartyForCookies().createCFURL();
146     if (m_cfRequest) {
147         cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
148         CFURLRequestSetURL(cfRequest, url.get());
149         CFURLRequestSetMainDocumentURL(cfRequest, firstPartyForCookies.get());
150         CFURLRequestSetCachePolicy(cfRequest, (CFURLRequestCachePolicy)cachePolicy());
151         CFURLRequestSetTimeoutInterval(cfRequest, timeoutInterval());
152     } else
153         cfRequest = CFURLRequestCreateMutable(0, url.get(), (CFURLRequestCachePolicy)cachePolicy(), timeoutInterval(), firstPartyForCookies.get());
154
155     CFURLRequestSetHTTPRequestMethod(cfRequest, httpMethod().createCFString().get());
156
157     if (httpPipeliningEnabled())
158         wkHTTPRequestEnablePipelining(cfRequest);
159
160     if (resourcePrioritiesEnabled())
161         wkSetHTTPRequestPriority(cfRequest, toPlatformRequestPriority(priority()));
162
163 #if !PLATFORM(WIN)
164     wkCFURLRequestAllowAllPostCaching(cfRequest);
165 #endif
166
167     setHeaderFields(cfRequest, httpHeaderFields());
168
169     CFURLRequestSetShouldHandleHTTPCookies(cfRequest, allowCookies());
170
171     unsigned fallbackCount = m_responseContentDispositionEncodingFallbackArray.size();
172     RetainPtr<CFMutableArrayRef> encodingFallbacks = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, fallbackCount, 0));
173     for (unsigned i = 0; i != fallbackCount; ++i) {
174         RetainPtr<CFStringRef> encodingName = m_responseContentDispositionEncodingFallbackArray[i].createCFString();
175         CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding(encodingName.get());
176         if (encoding != kCFStringEncodingInvalidId)
177             CFArrayAppendValue(encodingFallbacks.get(), reinterpret_cast<const void*>(encoding));
178     }
179     setContentDispositionEncodingFallbackArray(cfRequest, encodingFallbacks.get());
180
181 #if ENABLE(CACHE_PARTITIONING)
182     String partition = cachePartition();
183     if (!partition.isNull() && !partition.isEmpty()) {
184         CString utf8String = partition.utf8();
185         RetainPtr<CFStringRef> partitionValue = adoptCF(CFStringCreateWithBytes(0, reinterpret_cast<const UInt8*>(utf8String.data()), utf8String.length(), kCFStringEncodingUTF8, false));
186         _CFURLRequestSetProtocolProperty(cfRequest, wkCachePartitionKey(), partitionValue.get());
187     }
188 #endif
189
190     m_cfRequest = adoptCF(cfRequest);
191 #if PLATFORM(COCOA)
192     clearOrUpdateNSURLRequest();
193 #endif
194 }
195
196 void ResourceRequest::doUpdatePlatformHTTPBody()
197 {
198     CFMutableURLRequestRef cfRequest;
199
200     RetainPtr<CFURLRef> url = ResourceRequest::url().createCFURL();
201     RetainPtr<CFURLRef> firstPartyForCookies = ResourceRequest::firstPartyForCookies().createCFURL();
202     if (m_cfRequest) {
203         cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
204         CFURLRequestSetURL(cfRequest, url.get());
205         CFURLRequestSetMainDocumentURL(cfRequest, firstPartyForCookies.get());
206         CFURLRequestSetCachePolicy(cfRequest, (CFURLRequestCachePolicy)cachePolicy());
207         CFURLRequestSetTimeoutInterval(cfRequest, timeoutInterval());
208     } else
209         cfRequest = CFURLRequestCreateMutable(0, url.get(), (CFURLRequestCachePolicy)cachePolicy(), timeoutInterval(), firstPartyForCookies.get());
210
211     RefPtr<FormData> formData = httpBody();
212     if (formData && !formData->isEmpty())
213         WebCore::setHTTPBody(cfRequest, formData);
214
215     if (RetainPtr<CFReadStreamRef> bodyStream = adoptCF(CFURLRequestCopyHTTPRequestBodyStream(cfRequest))) {
216         // For streams, provide a Content-Length to avoid using chunked encoding, and to get accurate total length in callbacks.
217         if (RetainPtr<CFStringRef> lengthString = adoptCF(static_cast<CFStringRef>(CFReadStreamCopyProperty(bodyStream.get(), formDataStreamLengthPropertyName())))) {
218             CFURLRequestSetHTTPHeaderFieldValue(cfRequest, CFSTR("Content-Length"), lengthString.get());
219             // Since resource request is already marked updated, we need to keep it up to date too.
220             ASSERT(m_resourceRequestUpdated);
221             m_httpHeaderFields.set(HTTPHeaderName::ContentLength, lengthString.get());
222         }
223     }
224
225     m_cfRequest = adoptCF(cfRequest);
226 #if PLATFORM(COCOA)
227     clearOrUpdateNSURLRequest();
228 #endif
229 }
230
231 void ResourceRequest::updateFromDelegatePreservingOldProperties(const ResourceRequest& delegateProvidedRequest)
232 {
233     ResourceLoadPriority oldPriority = priority();
234     RefPtr<FormData> oldHTTPBody = httpBody();
235     bool isHiddenFromInspector = hiddenFromInspector();
236
237     *this = delegateProvidedRequest;
238
239     setPriority(oldPriority);
240     setHTTPBody(oldHTTPBody.release());
241     setHiddenFromInspector(isHiddenFromInspector);
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     if (resourcePrioritiesEnabled())
278         m_priority = toResourceLoadPriority(wkGetHTTPRequestPriority(m_cfRequest.get()));
279
280     m_httpHeaderFields.clear();
281     if (CFDictionaryRef headers = CFURLRequestCopyAllHTTPHeaderFields(m_cfRequest.get())) {
282         CFIndex headerCount = CFDictionaryGetCount(headers);
283         Vector<const void*, 128> keys(headerCount);
284         Vector<const void*, 128> values(headerCount);
285         CFDictionaryGetKeysAndValues(headers, keys.data(), values.data());
286         for (int i = 0; i < headerCount; ++i)
287             m_httpHeaderFields.set((CFStringRef)keys[i], (CFStringRef)values[i]);
288         CFRelease(headers);
289     }
290
291     m_responseContentDispositionEncodingFallbackArray.clear();
292     RetainPtr<CFArrayRef> encodingFallbacks = adoptCF(copyContentDispositionEncodingFallbackArray(m_cfRequest.get()));
293     if (encodingFallbacks) {
294         CFIndex count = CFArrayGetCount(encodingFallbacks.get());
295         for (CFIndex i = 0; i < count; ++i) {
296             CFStringEncoding encoding = reinterpret_cast<CFIndex>(CFArrayGetValueAtIndex(encodingFallbacks.get(), i));
297             if (encoding != kCFStringEncodingInvalidId)
298                 m_responseContentDispositionEncodingFallbackArray.append(CFStringConvertEncodingToIANACharSetName(encoding));
299         }
300     }
301
302 #if ENABLE(CACHE_PARTITIONING)
303     RetainPtr<CFStringRef> cachePartition = adoptCF(static_cast<CFStringRef>(_CFURLRequestCopyProtocolPropertyForKey(m_cfRequest.get(), wkCachePartitionKey())));
304     if (cachePartition)
305         m_cachePartition = cachePartition.get();
306 #endif
307 }
308
309 void ResourceRequest::doUpdateResourceHTTPBody()
310 {
311     if (!m_cfRequest) {
312         m_httpBody = 0;
313         return;
314     }
315
316     if (RetainPtr<CFDataRef> bodyData = adoptCF(CFURLRequestCopyHTTPRequestBody(m_cfRequest.get())))
317         m_httpBody = FormData::create(CFDataGetBytePtr(bodyData.get()), CFDataGetLength(bodyData.get()));
318     else if (RetainPtr<CFReadStreamRef> bodyStream = adoptCF(CFURLRequestCopyHTTPRequestBodyStream(m_cfRequest.get()))) {
319         FormData* formData = httpBodyFromStream(bodyStream.get());
320         // There is no FormData object if a client provided a custom data stream.
321         // We shouldn't be looking at http body after client callbacks.
322         ASSERT(formData);
323         if (formData)
324             m_httpBody = formData;
325     }
326 }
327
328
329 void ResourceRequest::setStorageSession(CFURLStorageSessionRef storageSession)
330 {
331     updatePlatformRequest();
332
333     CFMutableURLRequestRef cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
334     wkSetRequestStorageSession(storageSession, cfRequest);
335     m_cfRequest = adoptCF(cfRequest);
336 #if PLATFORM(COCOA)
337     clearOrUpdateNSURLRequest();
338 #endif
339 }
340
341 #endif // USE(CFNETWORK)
342
343 bool ResourceRequest::httpPipeliningEnabled()
344 {
345     return s_httpPipeliningEnabled;
346 }
347
348 void ResourceRequest::setHTTPPipeliningEnabled(bool flag)
349 {
350     s_httpPipeliningEnabled = flag;
351 }
352
353 #if ENABLE(CACHE_PARTITIONING)
354 String ResourceRequest::partitionName(const String& domain)
355 {
356     if (domain.isNull())
357         return emptyString();
358 #if ENABLE(PUBLIC_SUFFIX_LIST)
359     String highLevel = topPrivatelyControlledDomain(domain);
360     if (highLevel.isNull())
361         return emptyString();
362     return highLevel;
363 #else
364     return domain;
365 #endif
366 }
367 #endif
368
369 PassOwnPtr<CrossThreadResourceRequestData> ResourceRequest::doPlatformCopyData(PassOwnPtr<CrossThreadResourceRequestData> data) const
370 {
371 #if ENABLE(CACHE_PARTITIONING)
372     data->m_cachePartition = m_cachePartition;
373 #endif
374     return data;
375 }
376
377 void ResourceRequest::doPlatformAdopt(PassOwnPtr<CrossThreadResourceRequestData> data)
378 {
379 #if ENABLE(CACHE_PARTITIONING)
380     m_cachePartition = data->m_cachePartition;
381 #else
382     UNUSED_PARAM(data);
383 #endif
384 }
385
386 // FIXME: It is confusing that this function both sets connection count and determines maximum request count at network layer. This can and should be done separately.
387 unsigned initializeMaximumHTTPConnectionCountPerHost()
388 {
389     static const unsigned preferredConnectionCount = 6;
390     static const unsigned unlimitedRequestCount = 10000;
391
392     unsigned maximumHTTPConnectionCountPerHost = wkInitializeMaximumHTTPConnectionCountPerHost(preferredConnectionCount);
393
394     Boolean keyExistsAndHasValidFormat = false;
395     Boolean prefValue = CFPreferencesGetAppBooleanValue(CFSTR("WebKitEnableHTTPPipelining"), kCFPreferencesCurrentApplication, &keyExistsAndHasValidFormat);
396     if (keyExistsAndHasValidFormat)
397         ResourceRequest::setHTTPPipeliningEnabled(prefValue);
398
399     // Use WebCore scheduler when we can't use request priorities with CFNetwork.
400     if (!ResourceRequest::resourcePrioritiesEnabled())
401         return maximumHTTPConnectionCountPerHost;
402
403     wkSetHTTPRequestMaximumPriority(toPlatformRequestPriority(ResourceLoadPriorityHighest));
404 #if !PLATFORM(WIN)
405     // FIXME: <rdar://problem/9375609> Implement minimum fast lane priority setting on Windows
406     wkSetHTTPRequestMinimumFastLanePriority(toPlatformRequestPriority(ResourceLoadPriorityMedium));
407 #endif
408
409     return unlimitedRequestCount;
410 }
411
412 #if PLATFORM(IOS)
413 void initializeHTTPConnectionSettingsOnStartup()
414 {
415     // This need to be called from WebKitInitialize so the calls happen early enough, before any requests are made. <rdar://problem/9691871>
416     // Desktop doesn't have early initialization so it is not clear how this should be done there. The CFNetwork SPI probably
417     // needs to become more forgiving.
418     // We can't read settings here as this is called too early for that. All values need to be constants.
419     static const unsigned preferredConnectionCount = 6;
420     static const unsigned fastLaneConnectionCount = 1;
421     wkInitializeMaximumHTTPConnectionCountPerHost(preferredConnectionCount);
422     wkSetHTTPRequestMaximumPriority(ResourceLoadPriorityHighest);
423     wkSetHTTPRequestMinimumFastLanePriority(ResourceLoadPriorityMedium);
424     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPNumFastLanes, fastLaneConnectionCount);
425 }
426 #endif
427
428 } // namespace WebCore