0b57aa4302570c6148689c0e09625c90d1c8a623
[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 <pal/spi/cf/CFNetworkSPI.h>
32
33 #if ENABLE(PUBLIC_SUFFIX_LIST)
34 #include "PublicSuffix.h"
35 #endif
36
37 #if USE(CFURLCONNECTION)
38 #include "FormDataStreamCFNet.h"
39 #include <CFNetwork/CFURLRequestPriv.h>
40 #include <wtf/text/CString.h>
41 #endif
42
43 #if PLATFORM(COCOA)
44 #include "ResourceLoadPriority.h"
45 #include "WebCoreSystemInterface.h"
46 #include <dlfcn.h>
47 #endif
48
49 #if PLATFORM(WIN)
50 #include <WebKitSystemInterface/WebKitSystemInterface.h>
51 #endif
52
53 namespace WebCore {
54
55 // FIXME: Make this a NetworkingContext property.
56 #if PLATFORM(IOS)
57 bool ResourceRequest::s_httpPipeliningEnabled = true;
58 #else
59 bool ResourceRequest::s_httpPipeliningEnabled = false;
60 #endif
61
62 #if USE(CFURLCONNECTION)
63
64 typedef void (*CFURLRequestSetContentDispositionEncodingFallbackArrayFunction)(CFMutableURLRequestRef, CFArrayRef);
65 typedef CFArrayRef (*CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction)(CFURLRequestRef);
66
67 #if PLATFORM(WIN)
68 static HMODULE findCFNetworkModule()
69 {
70 #ifndef DEBUG_ALL
71     return GetModuleHandleA("CFNetwork");
72 #else
73     return GetModuleHandleA("CFNetwork_debug");
74 #endif
75 }
76
77 static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction()
78 {
79     return reinterpret_cast<CFURLRequestSetContentDispositionEncodingFallbackArrayFunction>(GetProcAddress(findCFNetworkModule(), "_CFURLRequestSetContentDispositionEncodingFallbackArray"));
80 }
81
82 static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction()
83 {
84     return reinterpret_cast<CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction>(GetProcAddress(findCFNetworkModule(), "_CFURLRequestCopyContentDispositionEncodingFallbackArray"));
85 }
86 #elif PLATFORM(COCOA)
87 static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction()
88 {
89     return reinterpret_cast<CFURLRequestSetContentDispositionEncodingFallbackArrayFunction>(dlsym(RTLD_DEFAULT, "_CFURLRequestSetContentDispositionEncodingFallbackArray"));
90 }
91
92 static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction()
93 {
94     return reinterpret_cast<CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction>(dlsym(RTLD_DEFAULT, "_CFURLRequestCopyContentDispositionEncodingFallbackArray"));
95 }
96 #endif
97
98 static void setContentDispositionEncodingFallbackArray(CFMutableURLRequestRef request, CFArrayRef fallbackArray)
99 {
100     static CFURLRequestSetContentDispositionEncodingFallbackArrayFunction function = findCFURLRequestSetContentDispositionEncodingFallbackArrayFunction();
101     if (function)
102         function(request, fallbackArray);
103 }
104
105 static CFArrayRef copyContentDispositionEncodingFallbackArray(CFURLRequestRef request)
106 {
107     static CFURLRequestCopyContentDispositionEncodingFallbackArrayFunction function = findCFURLRequestCopyContentDispositionEncodingFallbackArrayFunction();
108     if (!function)
109         return 0;
110     return function(request);
111 }
112
113 CFURLRequestRef ResourceRequest::cfURLRequest(HTTPBodyUpdatePolicy bodyPolicy) const
114 {
115     updatePlatformRequest(bodyPolicy);
116
117     return m_cfRequest.get();
118 }
119
120 static inline void setHeaderFields(CFMutableURLRequestRef request, const HTTPHeaderMap& requestHeaders) 
121 {
122     // Remove existing headers first, as some of them may no longer be present in the map.
123     RetainPtr<CFDictionaryRef> oldHeaderFields = adoptCF(CFURLRequestCopyAllHTTPHeaderFields(request));
124     CFIndex oldHeaderFieldCount = CFDictionaryGetCount(oldHeaderFields.get());
125     if (oldHeaderFieldCount) {
126         Vector<CFStringRef> oldHeaderFieldNames(oldHeaderFieldCount);
127         CFDictionaryGetKeysAndValues(oldHeaderFields.get(), reinterpret_cast<const void**>(&oldHeaderFieldNames[0]), 0);
128         for (CFIndex i = 0; i < oldHeaderFieldCount; ++i)
129             CFURLRequestSetHTTPHeaderFieldValue(request, oldHeaderFieldNames[i], 0);
130     }
131
132     for (const auto& header : requestHeaders)
133         CFURLRequestSetHTTPHeaderFieldValue(request, header.key.createCFString().get(), header.value.createCFString().get());
134 }
135
136 void ResourceRequest::doUpdatePlatformRequest()
137 {
138     CFMutableURLRequestRef cfRequest;
139
140     RetainPtr<CFURLRef> url = ResourceRequest::url().createCFURL();
141     RetainPtr<CFURLRef> firstPartyForCookies = ResourceRequest::firstPartyForCookies().createCFURL();
142     double timeoutInterval = ResourceRequestBase::timeoutInterval() ? ResourceRequestBase::timeoutInterval() : ResourceRequestBase::defaultTimeoutInterval();
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         CFURLRequestSetShouldPipelineHTTP(cfRequest, true, true);
156
157     if (resourcePrioritiesEnabled())
158         CFURLRequestSetRequestPriority(cfRequest, toPlatformRequestPriority(priority()));
159
160 #if !PLATFORM(WIN)
161     _CFURLRequestSetProtocolProperty(cfRequest, kCFURLRequestAllowAllPOSTCaching, kCFBooleanTrue);
162 #endif
163
164     setHeaderFields(cfRequest, httpHeaderFields());
165
166     CFURLRequestSetShouldHandleHTTPCookies(cfRequest, allowCookies());
167
168     unsigned fallbackCount = m_responseContentDispositionEncodingFallbackArray.size();
169     RetainPtr<CFMutableArrayRef> encodingFallbacks = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, fallbackCount, 0));
170     for (unsigned i = 0; i != fallbackCount; ++i) {
171         RetainPtr<CFStringRef> encodingName = m_responseContentDispositionEncodingFallbackArray[i].createCFString();
172         CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding(encodingName.get());
173         if (encoding != kCFStringEncodingInvalidId)
174             CFArrayAppendValue(encodingFallbacks.get(), reinterpret_cast<const void*>(encoding));
175     }
176     setContentDispositionEncodingFallbackArray(cfRequest, encodingFallbacks.get());
177
178 #if ENABLE(CACHE_PARTITIONING)
179     String partition = cachePartition();
180     if (!partition.isNull() && !partition.isEmpty()) {
181         CString utf8String = partition.utf8();
182         RetainPtr<CFStringRef> partitionValue = adoptCF(CFStringCreateWithBytes(0, reinterpret_cast<const UInt8*>(utf8String.data()), utf8String.length(), kCFStringEncodingUTF8, false));
183         _CFURLRequestSetProtocolProperty(cfRequest, wkCachePartitionKey(), partitionValue.get());
184     }
185 #endif
186
187     m_cfRequest = adoptCF(cfRequest);
188 #if PLATFORM(COCOA)
189     clearOrUpdateNSURLRequest();
190 #endif
191 }
192
193 // FIXME: We should use a switch based on ResourceRequestCachePolicy parameter
194 static inline CFURLRequestCachePolicy toPlatformRequestCachePolicy(ResourceRequestCachePolicy policy)
195 {
196     return static_cast<CFURLRequestCachePolicy>((policy <= ReturnCacheDataDontLoad) ? policy : ReloadIgnoringCacheData);
197 }
198
199 void ResourceRequest::doUpdatePlatformHTTPBody()
200 {
201     CFMutableURLRequestRef cfRequest;
202
203     RetainPtr<CFURLRef> url = ResourceRequest::url().createCFURL();
204     RetainPtr<CFURLRef> firstPartyForCookies = ResourceRequest::firstPartyForCookies().createCFURL();
205     double timeoutInterval = ResourceRequestBase::timeoutInterval() ? ResourceRequestBase::timeoutInterval() : ResourceRequestBase::defaultTimeoutInterval();
206     if (m_cfRequest) {
207         cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
208         CFURLRequestSetURL(cfRequest, url.get());
209         CFURLRequestSetMainDocumentURL(cfRequest, firstPartyForCookies.get());
210         CFURLRequestSetCachePolicy(cfRequest, toPlatformRequestCachePolicy(cachePolicy()));
211         CFURLRequestSetTimeoutInterval(cfRequest, timeoutInterval);
212     } else
213         cfRequest = CFURLRequestCreateMutable(0, url.get(), (CFURLRequestCachePolicy)cachePolicy(), timeoutInterval, firstPartyForCookies.get());
214
215     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         if (RetainPtr<CFStringRef> lengthString = adoptCF(static_cast<CFStringRef>(CFReadStreamCopyProperty(bodyStream.get(), formDataStreamLengthPropertyName())))) {
222             CFURLRequestSetHTTPHeaderFieldValue(cfRequest, CFSTR("Content-Length"), lengthString.get());
223             // Since resource request is already marked updated, we need to keep it up to date too.
224             ASSERT(m_resourceRequestUpdated);
225             m_httpHeaderFields.set(HTTPHeaderName::ContentLength, lengthString.get());
226         }
227     }
228
229     m_cfRequest = adoptCF(cfRequest);
230 #if PLATFORM(COCOA)
231     clearOrUpdateNSURLRequest();
232 #endif
233 }
234
235 void ResourceRequest::doUpdateResourceRequest()
236 {
237     if (!m_cfRequest) {
238 #if PLATFORM(IOS)
239         // <rdar://problem/9913526>
240         // This is a hack to mimic the subtle behaviour of the Foundation based ResourceRequest
241         // code. That code does not reset m_httpMethod if the NSURLRequest is nil. I filed
242         // <https://bugs.webkit.org/show_bug.cgi?id=66336> to track that.
243         // Another related bug is <https://bugs.webkit.org/show_bug.cgi?id=66350>. Fixing that
244         // would, ideally, allow us to not have this hack. But unfortunately that caused layout test
245         // failures.
246         // Removal of this hack is tracked by <rdar://problem/9970499>.
247
248         String httpMethod = m_httpMethod;
249         *this = ResourceRequest();
250         m_httpMethod = httpMethod;
251 #else
252         *this = ResourceRequest();
253 #endif
254         return;
255     }
256
257     m_url = CFURLRequestGetURL(m_cfRequest.get());
258
259     if (!m_cachePolicy)
260         m_cachePolicy = (ResourceRequestCachePolicy)CFURLRequestGetCachePolicy(m_cfRequest.get());
261     m_timeoutInterval = CFURLRequestGetTimeoutInterval(m_cfRequest.get());
262     m_firstPartyForCookies = CFURLRequestGetMainDocumentURL(m_cfRequest.get());
263     if (CFStringRef method = CFURLRequestCopyHTTPRequestMethod(m_cfRequest.get())) {
264         m_httpMethod = method;
265         CFRelease(method);
266     }
267     m_allowCookies = CFURLRequestShouldHandleHTTPCookies(m_cfRequest.get());
268
269     if (resourcePrioritiesEnabled())
270         m_priority = toResourceLoadPriority(CFURLRequestGetRequestPriority(m_cfRequest.get()));
271
272     m_httpHeaderFields.clear();
273     if (CFDictionaryRef headers = CFURLRequestCopyAllHTTPHeaderFields(m_cfRequest.get())) {
274         CFIndex headerCount = CFDictionaryGetCount(headers);
275         Vector<const void*, 128> keys(headerCount);
276         Vector<const void*, 128> values(headerCount);
277         CFDictionaryGetKeysAndValues(headers, keys.data(), values.data());
278         for (int i = 0; i < headerCount; ++i)
279             m_httpHeaderFields.set((CFStringRef)keys[i], (CFStringRef)values[i]);
280         CFRelease(headers);
281     }
282
283     m_responseContentDispositionEncodingFallbackArray.clear();
284     RetainPtr<CFArrayRef> encodingFallbacks = adoptCF(copyContentDispositionEncodingFallbackArray(m_cfRequest.get()));
285     if (encodingFallbacks) {
286         CFIndex count = CFArrayGetCount(encodingFallbacks.get());
287         for (CFIndex i = 0; i < count; ++i) {
288             CFStringEncoding encoding = reinterpret_cast<CFIndex>(CFArrayGetValueAtIndex(encodingFallbacks.get(), i));
289             if (encoding != kCFStringEncodingInvalidId)
290                 m_responseContentDispositionEncodingFallbackArray.append(CFStringConvertEncodingToIANACharSetName(encoding));
291         }
292     }
293
294 #if ENABLE(CACHE_PARTITIONING)
295     RetainPtr<CFStringRef> cachePartition = adoptCF(static_cast<CFStringRef>(_CFURLRequestCopyProtocolPropertyForKey(m_cfRequest.get(), wkCachePartitionKey())));
296     if (cachePartition)
297         m_cachePartition = cachePartition.get();
298 #endif
299 }
300
301 void ResourceRequest::doUpdateResourceHTTPBody()
302 {
303     if (!m_cfRequest) {
304         m_httpBody = nullptr;
305         return;
306     }
307
308     if (RetainPtr<CFDataRef> bodyData = adoptCF(CFURLRequestCopyHTTPRequestBody(m_cfRequest.get())))
309         m_httpBody = FormData::create(CFDataGetBytePtr(bodyData.get()), CFDataGetLength(bodyData.get()));
310     else if (RetainPtr<CFReadStreamRef> bodyStream = adoptCF(CFURLRequestCopyHTTPRequestBodyStream(m_cfRequest.get()))) {
311         FormData* formData = httpBodyFromStream(bodyStream.get());
312         // There is no FormData object if a client provided a custom data stream.
313         // We shouldn't be looking at http body after client callbacks.
314         ASSERT(formData);
315         if (formData)
316             m_httpBody = formData;
317     }
318 }
319
320
321 void ResourceRequest::setStorageSession(CFURLStorageSessionRef storageSession)
322 {
323     updatePlatformRequest();
324
325     auto cfRequest = CFURLRequestCreateMutableCopy(0, m_cfRequest.get());
326     if (storageSession)
327         _CFURLRequestSetStorageSession(cfRequest, storageSession);
328     m_cfRequest = adoptCF(cfRequest);
329 #if PLATFORM(COCOA)
330     clearOrUpdateNSURLRequest();
331 #endif
332 }
333
334 #endif // USE(CFURLCONNECTION)
335
336 void ResourceRequest::updateFromDelegatePreservingOldProperties(const ResourceRequest& delegateProvidedRequest)
337 {
338     // These are things we don't want willSendRequest delegate to mutate or reset.
339     ResourceLoadPriority oldPriority = priority();
340     RefPtr<FormData> oldHTTPBody = httpBody();
341     bool isHiddenFromInspector = hiddenFromInspector();
342     auto oldRequester = requester();
343     auto oldInitiatorIdentifier = initiatorIdentifier();
344
345     *this = delegateProvidedRequest;
346
347     setPriority(oldPriority);
348     setHTTPBody(WTFMove(oldHTTPBody));
349     setHiddenFromInspector(isHiddenFromInspector);
350     setRequester(oldRequester);
351     setInitiatorIdentifier(oldInitiatorIdentifier);
352 }
353
354 bool ResourceRequest::httpPipeliningEnabled()
355 {
356     return s_httpPipeliningEnabled;
357 }
358
359 void ResourceRequest::setHTTPPipeliningEnabled(bool flag)
360 {
361     s_httpPipeliningEnabled = flag;
362 }
363
364 // 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.
365 unsigned initializeMaximumHTTPConnectionCountPerHost()
366 {
367     static const unsigned preferredConnectionCount = 6;
368     static const unsigned unlimitedRequestCount = 10000;
369
370     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPLoadWidth, preferredConnectionCount);
371     unsigned maximumHTTPConnectionCountPerHost = _CFNetworkHTTPConnectionCacheGetLimit(kHTTPLoadWidth);
372
373     Boolean keyExistsAndHasValidFormat = false;
374     Boolean prefValue = CFPreferencesGetAppBooleanValue(CFSTR("WebKitEnableHTTPPipelining"), kCFPreferencesCurrentApplication, &keyExistsAndHasValidFormat);
375     if (keyExistsAndHasValidFormat)
376         ResourceRequest::setHTTPPipeliningEnabled(prefValue);
377
378     // Use WebCore scheduler when we can't use request priorities with CFNetwork.
379     if (!ResourceRequest::resourcePrioritiesEnabled())
380         return maximumHTTPConnectionCountPerHost;
381
382     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPPriorityNumLevels, toPlatformRequestPriority(ResourceLoadPriority::Highest));
383 #if !PLATFORM(WIN)
384     // FIXME: <rdar://problem/9375609> Implement minimum fast lane priority setting on Windows
385     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPMinimumFastLanePriority, toPlatformRequestPriority(ResourceLoadPriority::Medium));
386 #endif
387
388     return unlimitedRequestCount;
389 }
390
391 #if PLATFORM(IOS)
392 void initializeHTTPConnectionSettingsOnStartup()
393 {
394     // This need to be called from WebKitInitialize so the calls happen early enough, before any requests are made. <rdar://problem/9691871>
395     // Desktop doesn't have early initialization so it is not clear how this should be done there. The CFNetwork SPI probably
396     // needs to become more forgiving.
397     // We can't read settings here as this is called too early for that. All values need to be constants.
398     static const unsigned preferredConnectionCount = 6;
399     static const unsigned fastLaneConnectionCount = 1;
400     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPLoadWidth, preferredConnectionCount);
401     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPPriorityNumLevels, toPlatformRequestPriority(ResourceLoadPriority::Highest));
402     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPMinimumFastLanePriority, toPlatformRequestPriority(ResourceLoadPriority::Medium));
403     _CFNetworkHTTPConnectionCacheSetLimit(kHTTPNumFastLanes, fastLaneConnectionCount);
404 }
405 #endif
406
407 #if PLATFORM(COCOA)
408 CFStringRef ResourceRequest::isUserInitiatedKey()
409 {
410     static CFStringRef key = CFSTR("ResourceRequestIsUserInitiatedKey");
411     return key;
412 }
413 #endif
414
415 } // namespace WebCore