[Resource Timing] Gather timing information with reliable responseEnd time
[WebKit-https.git] / Source / WebCore / platform / network / cf / SynchronousResourceHandleCFURLConnectionDelegate.cpp
1 /*
2  * Copyright (C) 2004-2017 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 "SynchronousResourceHandleCFURLConnectionDelegate.h"
28
29 #if USE(CFURLCONNECTION)
30
31 #include "AuthenticationCF.h"
32 #include "AuthenticationChallenge.h"
33 #include "LoaderRunLoopCF.h"
34 #include "Logging.h"
35 #include "ResourceHandle.h"
36 #include "ResourceHandleClient.h"
37 #include "ResourceResponse.h"
38 #include "SharedBuffer.h"
39 #include <wtf/RetainPtr.h>
40 #include <wtf/text/CString.h>
41 #include <wtf/text/WTFString.h>
42
43 #if PLATFORM(COCOA)
44 #include "CFNetworkSPI.h"
45 #include "WebCoreSystemInterface.h"
46 #include "WebCoreURLResponse.h"
47 #endif // PLATFORM(COCOA)
48
49 #if PLATFORM(IOS)
50 #include "WebCoreThreadInternal.h"
51 #endif // PLATFORM(IOS)
52
53 #if PLATFORM(WIN)
54 #include "MIMETypeRegistry.h"
55 #endif // PLATFORM(WIN)
56
57 namespace WebCore {
58
59 SynchronousResourceHandleCFURLConnectionDelegate::SynchronousResourceHandleCFURLConnectionDelegate(ResourceHandle* handle)
60     : ResourceHandleCFURLConnectionDelegate(handle)
61 {
62 }
63
64 void SynchronousResourceHandleCFURLConnectionDelegate::setupRequest(CFMutableURLRequestRef request)
65 {
66 #if PLATFORM(IOS)
67     CFURLRequestSetShouldStartSynchronously(request, 1);
68 #else
69     UNUSED_PARAM(request);
70 #endif
71 }
72
73 void SynchronousResourceHandleCFURLConnectionDelegate::setupConnectionScheduling(CFURLConnectionRef connection)
74 {
75 #if PLATFORM(WIN)
76     CFURLConnectionScheduleWithCurrentMessageQueue(connection);
77 #elif PLATFORM(IOS)
78     CFURLConnectionScheduleWithRunLoop(connection, WebThreadRunLoop(), kCFRunLoopDefaultMode);
79 #else
80     CFURLConnectionScheduleWithRunLoop(connection, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
81 #endif
82     CFURLConnectionScheduleDownloadWithRunLoop(connection, loaderRunLoop(), kCFRunLoopDefaultMode);
83 }
84
85 CFURLRequestRef SynchronousResourceHandleCFURLConnectionDelegate::willSendRequest(CFURLRequestRef cfRequest, CFURLResponseRef originalRedirectResponse)
86 {
87     RetainPtr<CFURLResponseRef> redirectResponse = synthesizeRedirectResponseIfNecessary(cfRequest, originalRedirectResponse);
88
89     if (!redirectResponse) {
90         CFRetain(cfRequest);
91         return cfRequest;
92     }
93
94     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::willSendRequest(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
95
96     ResourceRequest request = createResourceRequest(cfRequest, redirectResponse.get());
97     auto newRequest = m_handle->willSendRequest(WTFMove(request), redirectResponse.get());
98
99     if (newRequest.isNull())
100         return nullptr;
101
102     auto newCFRequest = newRequest.cfURLRequest(UpdateHTTPBody);
103
104     CFRetain(newCFRequest);
105     return newCFRequest;
106 }
107
108 #if !PLATFORM(COCOA)
109 static void setDefaultMIMEType(CFURLResponseRef response)
110 {
111     static CFStringRef defaultMIMETypeString = defaultMIMEType().createCFString().leakRef();
112     
113     CFURLResponseSetMIMEType(response, defaultMIMETypeString);
114 }
115
116 static void adjustMIMETypeIfNecessary(CFURLResponseRef cfResponse)
117 {
118     RetainPtr<CFStringRef> result = CFURLResponseGetMIMEType(cfResponse);
119     RetainPtr<CFStringRef> originalResult = result;
120
121     if (!result) {
122         CFURLRef cfURL = CFURLResponseGetURL(cfResponse);
123         URL url(cfURL);
124         if (url.isLocalFile()) {
125             String mimeType = mimeTypeFromURL(url);
126             result = mimeType.createCFString().leakRef();
127         }
128     }
129
130     if (!result) {
131         static CFStringRef defaultMIMETypeString = WebCore::defaultMIMEType().createCFString().leakRef();
132         result = defaultMIMETypeString;
133     }
134
135     if (result != originalResult)
136         CFURLResponseSetMIMEType(cfResponse, result.get());
137 }
138 #endif // !PLATFORM(COCOA)
139
140 void SynchronousResourceHandleCFURLConnectionDelegate::didReceiveResponse(CFURLConnectionRef connection, CFURLResponseRef cfResponse)
141 {
142     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::didReceiveResponse(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
143
144     if (!m_handle->client())
145         return;
146
147 #if PLATFORM(COCOA)
148     // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
149     auto msg = CFURLResponseGetHTTPResponse(cfResponse);
150     int statusCode = msg ? CFHTTPMessageGetResponseStatusCode(msg) : 0;
151
152     if (statusCode != 304) {
153         bool isMainResourceLoad = m_handle->firstRequest().requester() == ResourceRequest::Requester::Main;
154         adjustMIMETypeIfNecessary(cfResponse, isMainResourceLoad);
155     }
156
157 #if !PLATFORM(IOS)
158     if (_CFURLRequestCopyProtocolPropertyForKey(m_handle->firstRequest().cfURLRequest(DoNotUpdateHTTPBody), CFSTR("ForceHTMLMIMEType")))
159         CFURLResponseSetMIMEType(cfResponse, CFSTR("text/html"));
160 #endif // !PLATFORM(IOS)
161 #else
162     if (!CFURLResponseGetMIMEType(cfResponse))
163         adjustMIMETypeIfNecessary(cfResponse);
164
165     if (!CFURLResponseGetMIMEType(cfResponse)) {
166         // We should never be applying the default MIMEType if we told the networking layer to do content sniffing for handle.
167         ASSERT(!m_handle->shouldContentSniff());
168         setDefaultMIMEType(cfResponse);
169     }
170 #endif
171
172     ResourceResponse resourceResponse(cfResponse);
173 #if PLATFORM(COCOA) && ENABLE(WEB_TIMING)
174     ResourceHandle::getConnectionTimingData(connection, resourceResponse.deprecatedNetworkLoadMetrics());
175 #else
176     UNUSED_PARAM(connection);
177 #endif
178
179     m_handle->client()->didReceiveResponse(m_handle, WTFMove(resourceResponse));
180 }
181
182 void SynchronousResourceHandleCFURLConnectionDelegate::didReceiveData(CFDataRef data, CFIndex originalLength)
183 {
184     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::didReceiveData(handle=%p, bytes=%ld) (%s)", m_handle, CFDataGetLength(data), m_handle->firstRequest().url().string().utf8().data());
185
186     if (ResourceHandleClient* client = m_handle->client())
187         client->didReceiveBuffer(m_handle, SharedBuffer::wrapCFData(data), originalLength);
188 }
189
190 void SynchronousResourceHandleCFURLConnectionDelegate::didFinishLoading()
191 {
192     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::didFinishLoading(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
193
194     if (ResourceHandleClient* client = m_handle->client())
195         client->didFinishLoading(m_handle);
196 }
197
198 void SynchronousResourceHandleCFURLConnectionDelegate::didFail(CFErrorRef error)
199 {
200     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::didFail(handle=%p, error = %p) (%s)", m_handle, error, m_handle->firstRequest().url().string().utf8().data());
201
202     if (ResourceHandleClient* client = m_handle->client())
203         client->didFail(m_handle, ResourceError(error));
204 }
205
206 CFCachedURLResponseRef SynchronousResourceHandleCFURLConnectionDelegate::willCacheResponse(CFCachedURLResponseRef cachedResponse)
207 {
208 #if PLATFORM(WIN)
209     // Workaround for <rdar://problem/6300990> Caching does not respect Vary HTTP header.
210     // FIXME: WebCore cache has issues with Vary, too (bug 58797, bug 71509).
211     CFURLResponseRef wrappedResponse = CFCachedURLResponseGetWrappedResponse(cachedResponse);
212     if (CFHTTPMessageRef httpResponse = CFURLResponseGetHTTPResponse(wrappedResponse)) {
213         ASSERT(CFHTTPMessageIsHeaderComplete(httpResponse));
214         RetainPtr<CFStringRef> varyValue = adoptCF(CFHTTPMessageCopyHeaderFieldValue(httpResponse, CFSTR("Vary")));
215         if (varyValue)
216             return 0;
217     }
218 #endif // PLATFORM(WIN)
219
220 #if PLATFORM(WIN)
221     if (m_handle->client() && !m_handle->client()->shouldCacheResponse(m_handle, cachedResponse))
222         return 0;
223 #else
224     CFCachedURLResponseRef newResponse = m_handle->client()->willCacheResponse(m_handle, cachedResponse);
225     if (newResponse != cachedResponse)
226         return newResponse;
227 #endif
228
229     CFRetain(cachedResponse);
230     return cachedResponse;
231 }
232
233 void SynchronousResourceHandleCFURLConnectionDelegate::didReceiveChallenge(CFURLAuthChallengeRef challenge)
234 {
235     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::didReceiveChallenge(handle=%p (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
236
237     m_handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(challenge, m_handle));
238 }
239
240 void SynchronousResourceHandleCFURLConnectionDelegate::didSendBodyData(CFIndex totalBytesWritten, CFIndex totalBytesExpectedToWrite)
241 {
242     if (!m_handle || !m_handle->client())
243         return;
244     m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
245 }
246
247 Boolean SynchronousResourceHandleCFURLConnectionDelegate::shouldUseCredentialStorage()
248 {
249     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::shouldUseCredentialStorage(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
250
251     if (!m_handle)
252         return false;
253
254     return m_handle->shouldUseCredentialStorage();
255 }
256
257 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
258 Boolean SynchronousResourceHandleCFURLConnectionDelegate::canRespondToProtectionSpace(CFURLProtectionSpaceRef protectionSpace)
259 {
260     ASSERT(m_handle);
261
262     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::canRespondToProtectionSpace(handle=%p (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
263
264     ProtectionSpace coreProtectionSpace = ProtectionSpace(protectionSpace);
265 #if PLATFORM(IOS)
266     if (coreProtectionSpace.authenticationScheme() == ProtectionSpaceAuthenticationSchemeUnknown)
267         return false;
268     return m_handle->canAuthenticateAgainstProtectionSpace(coreProtectionSpace);
269 #else
270     return m_handle->canAuthenticateAgainstProtectionSpace(coreProtectionSpace);
271 #endif
272 }
273 #endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
274
275 #if USE(NETWORK_CFDATA_ARRAY_CALLBACK)
276 void SynchronousResourceHandleCFURLConnectionDelegate::didReceiveDataArray(CFArrayRef dataArray)
277 {
278     if (!m_handle->client())
279         return;
280
281     LOG(Network, "CFNet - SynchronousResourceHandleCFURLConnectionDelegate::didReceiveDataArray(handle=%p, arrayLength=%ld) (%s)", m_handle, CFArrayGetCount(dataArray), m_handle->firstRequest().url().string().utf8().data());
282
283     if (ResourceHandleClient* client = m_handle->client())
284         client->didReceiveBuffer(m_handle, SharedBuffer::wrapCFDataArray(dataArray), -1);
285 }
286 #endif // USE(NETWORK_CFDATA_ARRAY_CALLBACK)
287
288 void SynchronousResourceHandleCFURLConnectionDelegate::continueWillSendRequest(CFURLRequestRef)
289 {
290     ASSERT_NOT_REACHED();
291 }
292
293 void SynchronousResourceHandleCFURLConnectionDelegate::continueDidReceiveResponse()
294 {
295     ASSERT_NOT_REACHED();
296 }
297
298 void SynchronousResourceHandleCFURLConnectionDelegate::continueWillCacheResponse(CFCachedURLResponseRef)
299 {
300     ASSERT_NOT_REACHED();
301 }
302
303 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
304 void SynchronousResourceHandleCFURLConnectionDelegate::continueCanAuthenticateAgainstProtectionSpace(bool)
305 {
306     ASSERT_NOT_REACHED();
307 }
308 #endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
309
310 } // namespace WebCore.
311
312 #endif