2 * Copyright (C) 2004, 2006 Apple Inc. All rights reserved.
3 * Copyright (C) 2005, 2006 Michael Emmel mike.emmel@gmail.com
4 * Copyright (C) 2017 Sony Interactive Entertainment Inc.
6 * Copyright (C) 2017 NAVER Corp. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "ResourceHandleCurlDelegate.h"
35 #include "AuthenticationChallenge.h"
36 #include "CredentialStorage.h"
37 #include "CurlCacheManager.h"
38 #include "CurlRequest.h"
39 #include "HTTPParsers.h"
40 #include "MultipartHandle.h"
41 #include "ResourceHandleInternal.h"
42 #include "SharedBuffer.h"
43 #include "TextEncoding.h"
44 #include <wtf/CompletionHandler.h>
45 #include <wtf/text/Base64.h>
49 ResourceHandleCurlDelegate::ResourceHandleCurlDelegate(ResourceHandle* handle)
51 , m_firstRequest(handle->firstRequest().isolatedCopy())
52 , m_currentRequest(handle->firstRequest().isolatedCopy())
53 , m_shouldUseCredentialStorage(handle->shouldUseCredentialStorage())
54 , m_user(handle->getInternal()->m_user.isolatedCopy())
55 , m_pass(handle->getInternal()->m_pass.isolatedCopy())
56 , m_initialCredential(handle->getInternal()->m_initialCredential)
57 , m_defersLoading(handle->getInternal()->m_defersLoading)
62 ResourceHandleCurlDelegate::~ResourceHandleCurlDelegate()
65 m_curlRequest->setClient(nullptr);
68 bool ResourceHandleCurlDelegate::hasHandle() const
73 void ResourceHandleCurlDelegate::releaseHandle()
78 void ResourceHandleCurlDelegate::start()
80 ASSERT(isMainThread());
82 auto credential = getCredential(m_currentRequest, false);
84 m_curlRequest = createCurlRequest(m_currentRequest);
85 m_curlRequest->setUserPass(credential.first, credential.second);
86 m_curlRequest->start();
89 void ResourceHandleCurlDelegate::cancel()
91 ASSERT(isMainThread());
96 m_curlRequest->cancel();
99 void ResourceHandleCurlDelegate::setDefersLoading(bool defers)
101 ASSERT(isMainThread());
103 if (defers == m_defersLoading)
106 m_defersLoading = defers;
112 m_curlRequest->suspend();
114 m_curlRequest->resume();
117 void ResourceHandleCurlDelegate::setAuthentication(const String& user, const String& password)
119 ASSERT(isMainThread());
124 bool isSyncRequest = m_curlRequest->isSyncRequest();
125 m_curlRequest->cancel();
126 m_curlRequest->setClient(nullptr);
128 m_curlRequest = createCurlRequest(m_currentRequest);
129 m_curlRequest->setUserPass(user, password);
130 m_curlRequest->start(isSyncRequest);
133 void ResourceHandleCurlDelegate::dispatchSynchronousJob()
135 if (m_currentRequest.url().protocolIsData()) {
140 // If defersLoading is true and we call curl_easy_perform
141 // on a paused handle, libcURL would do the transfert anyway
142 // and we would assert so force defersLoading to be false.
143 m_defersLoading = false;
145 m_curlRequest = createCurlRequest(m_currentRequest);
146 m_curlRequest->start(true);
149 Ref<CurlRequest> ResourceHandleCurlDelegate::createCurlRequest(ResourceRequest& request)
151 ASSERT(isMainThread());
153 // CurlCache : append additional cache information
154 m_addedCacheValidationHeaders = false;
156 bool hasCacheHeaders = request.httpHeaderFields().contains(HTTPHeaderName::IfModifiedSince) || request.httpHeaderFields().contains(HTTPHeaderName::IfNoneMatch);
157 if (!hasCacheHeaders) {
158 auto& cache = CurlCacheManager::singleton();
159 URL cacheUrl = request.url();
160 cacheUrl.removeFragmentIdentifier();
162 if (cache.isCached(cacheUrl)) {
163 cache.addCacheEntryClient(cacheUrl, m_handle);
165 for (const auto& entry : cache.requestHeaders(cacheUrl))
166 request.addHTTPHeaderField(entry.key, entry.value);
168 m_addedCacheValidationHeaders = true;
172 return CurlRequest::create(request, this, m_defersLoading);
175 bool ResourceHandleCurlDelegate::cancelledOrClientless()
180 return !m_handle->client();
183 void ResourceHandleCurlDelegate::curlDidReceiveResponse(const CurlResponse& receivedResponse)
185 ASSERT(isMainThread());
186 ASSERT(!m_defersLoading);
188 if (cancelledOrClientless())
191 m_handle->getInternal()->m_response = ResourceResponse(receivedResponse);
194 m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
196 if (response().isMultipart()) {
198 bool parsed = MultipartHandle::extractBoundary(response().httpHeaderField(HTTPHeaderName::ContentType), boundary);
200 m_multipartHandle = std::make_unique<MultipartHandle>(m_handle, boundary);
203 if (response().shouldRedirect()) {
208 if (response().isUnauthorized()) {
209 AuthenticationChallenge challenge(receivedResponse, m_authFailureCount, response(), m_handle);
210 m_handle->didReceiveAuthenticationChallenge(challenge);
211 m_authFailureCount++;
215 if (m_handle->client()) {
216 if (response().isNotModified()) {
217 URL cacheUrl = m_currentRequest.url();
218 cacheUrl.removeFragmentIdentifier();
220 if (CurlCacheManager::singleton().getCachedResponse(cacheUrl, response())) {
221 if (m_addedCacheValidationHeaders) {
222 response().setHTTPStatusCode(200);
223 response().setHTTPStatusText("OK");
228 CurlCacheManager::singleton().didReceiveResponse(*m_handle, response());
230 auto protectedThis = makeRef(*m_handle);
231 m_handle->didReceiveResponse(ResourceResponse(response()));
235 void ResourceHandleCurlDelegate::curlDidReceiveBuffer(Ref<SharedBuffer>&& buffer)
237 ASSERT(isMainThread());
239 if (cancelledOrClientless())
242 if (m_multipartHandle)
243 m_multipartHandle->contentReceived(buffer->data(), buffer->size());
244 else if (m_handle->client()) {
245 CurlCacheManager::singleton().didReceiveData(*m_handle, buffer->data(), buffer->size());
246 m_handle->client()->didReceiveBuffer(m_handle, WTFMove(buffer), buffer->size());
250 void ResourceHandleCurlDelegate::curlDidComplete()
252 ASSERT(isMainThread());
254 if (cancelledOrClientless())
258 m_handle->getInternal()->m_response.setDeprecatedNetworkLoadMetrics(m_curlRequest->getNetworkLoadMetrics());
260 if (m_multipartHandle)
261 m_multipartHandle->contentEnded();
263 if (m_handle->client()) {
264 CurlCacheManager::singleton().didFinishLoading(*m_handle);
265 m_handle->client()->didFinishLoading(m_handle);
269 void ResourceHandleCurlDelegate::curlDidFailWithError(const ResourceError& resourceError)
271 ASSERT(isMainThread());
273 if (cancelledOrClientless())
276 CurlCacheManager::singleton().didFail(*m_handle);
277 m_handle->client()->didFail(m_handle, resourceError);
280 void ResourceHandleCurlDelegate::continueDidReceiveResponse()
282 ASSERT(isMainThread());
284 continueAfterDidReceiveResponse();
287 void ResourceHandleCurlDelegate::platformContinueSynchronousDidReceiveResponse()
289 ASSERT(isMainThread());
291 continueAfterDidReceiveResponse();
294 void ResourceHandleCurlDelegate::continueAfterDidReceiveResponse()
296 ASSERT(isMainThread());
298 // continueDidReceiveResponse might cancel the load.
299 if (cancelledOrClientless() || !m_curlRequest)
302 m_curlRequest->completeDidReceiveResponse();
305 bool ResourceHandleCurlDelegate::shouldRedirectAsGET(const ResourceRequest& request, bool crossOrigin)
307 if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
310 if (!request.url().protocolIsInHTTPFamily())
313 if (response().isSeeOther())
316 if ((response().isMovedPermanently() || response().isFound()) && (request.httpMethod() == "POST"))
319 if (crossOrigin && (request.httpMethod() == "DELETE"))
325 void ResourceHandleCurlDelegate::willSendRequest()
327 ASSERT(isMainThread());
329 static const int maxRedirects = 20;
331 if (m_redirectCount++ > maxRedirects) {
332 m_handle->client()->didFail(m_handle, ResourceError::httpError(CURLE_TOO_MANY_REDIRECTS, response().url()));
336 String location = response().httpHeaderField(HTTPHeaderName::Location);
337 URL newURL = URL(m_firstRequest.url(), location);
338 bool crossOrigin = !protocolHostAndPortAreEqual(m_firstRequest.url(), newURL);
340 ResourceRequest newRequest = m_firstRequest;
341 newRequest.setURL(newURL);
343 if (shouldRedirectAsGET(newRequest, crossOrigin)) {
344 newRequest.setHTTPMethod("GET");
345 newRequest.setHTTPBody(nullptr);
346 newRequest.clearHTTPContentType();
349 // Should not set Referer after a redirect from a secure resource to non-secure one.
350 if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
351 newRequest.clearHTTPReferrer();
353 m_user = newURL.user();
354 m_pass = newURL.pass();
355 newRequest.removeCredentials();
358 // If the network layer carries over authentication headers from the original request
359 // in a cross-origin redirect, we want to clear those headers here.
360 newRequest.clearHTTPAuthorization();
361 newRequest.clearHTTPOrigin();
364 ResourceResponse responseCopy = response();
365 m_handle->client()->willSendRequestAsync(m_handle, WTFMove(newRequest), WTFMove(responseCopy), [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) {
366 continueWillSendRequest(WTFMove(request));
370 void ResourceHandleCurlDelegate::continueWillSendRequest(ResourceRequest&& request)
372 ASSERT(isMainThread());
374 continueAfterWillSendRequest(WTFMove(request));
377 void ResourceHandleCurlDelegate::continueAfterWillSendRequest(ResourceRequest&& request)
379 ASSERT(isMainThread());
381 // willSendRequest might cancel the load.
382 if (cancelledOrClientless() || !m_curlRequest)
385 m_currentRequest = WTFMove(request);
387 bool isSyncRequest = m_curlRequest->isSyncRequest();
388 m_curlRequest->cancel();
389 m_curlRequest->setClient(nullptr);
391 m_curlRequest = createCurlRequest(m_currentRequest);
393 if (protocolHostAndPortAreEqual(m_currentRequest.url(), response().url())) {
394 auto credential = getCredential(m_currentRequest, true);
395 m_curlRequest->setUserPass(credential.first, credential.second);
398 m_curlRequest->start(isSyncRequest);
401 ResourceResponse& ResourceHandleCurlDelegate::response()
403 return m_handle->getInternal()->m_response;
406 void ResourceHandleCurlDelegate::handleDataURL()
408 ASSERT(m_firstRequest.url().protocolIsData());
409 String url = m_firstRequest.url().string();
411 ASSERT(m_handle->client());
413 auto index = url.find(',');
414 if (index == notFound) {
415 m_handle->client()->cannotShowURL(m_handle);
419 String mediaType = url.substring(5, index - 5);
420 String data = url.substring(index + 1);
421 auto originalSize = data.length();
423 bool base64 = mediaType.endsWithIgnoringASCIICase(";base64");
425 mediaType = mediaType.left(mediaType.length() - 7);
427 if (mediaType.isEmpty())
428 mediaType = "text/plain";
430 String mimeType = extractMIMETypeFromMediaType(mediaType);
431 String charset = extractCharsetFromMediaType(mediaType);
433 if (charset.isEmpty())
434 charset = "US-ASCII";
436 ResourceResponse response;
437 response.setMimeType(mimeType);
438 response.setTextEncodingName(charset);
439 response.setURL(m_firstRequest.url());
442 data = decodeURLEscapeSequences(data);
443 m_handle->didReceiveResponse(WTFMove(response));
445 // didReceiveResponse might cause the client to be deleted.
446 if (m_handle->client()) {
448 if (base64Decode(data, out, Base64IgnoreSpacesAndNewLines) && out.size() > 0)
449 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(out.data(), out.size()), originalSize);
452 TextEncoding encoding(charset);
453 data = decodeURLEscapeSequences(data, encoding);
454 m_handle->didReceiveResponse(WTFMove(response));
456 // didReceiveResponse might cause the client to be deleted.
457 if (m_handle->client()) {
458 auto encodedData = encoding.encode(data, UnencodableHandling::URLEncodedEntities);
459 if (encodedData.size())
460 m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::create(WTFMove(encodedData)), originalSize);
464 if (m_handle->client())
465 m_handle->client()->didFinishLoading(m_handle);
468 std::pair<String, String> ResourceHandleCurlDelegate::getCredential(ResourceRequest& request, bool redirect)
470 // m_user/m_pass are credentials given manually, for instance, by the arguments passed to XMLHttpRequest.open().
471 String partition = request.cachePartition();
473 if (m_shouldUseCredentialStorage) {
474 if (m_user.isEmpty() && m_pass.isEmpty()) {
475 // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication,
476 // try and reuse the credential preemptively, as allowed by RFC 2617.
477 m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());
478 } else if (!redirect) {
479 // If there is already a protection space known for the URL, update stored credentials
480 // before sending a request. This makes it possible to implement logout by sending an
481 // XMLHttpRequest with known incorrect credentials, and aborting it immediately (so that
482 // an authentication dialog doesn't pop up).
483 CredentialStorage::defaultCredentialStorage().set(partition, Credential(m_user, m_pass, CredentialPersistenceNone), request.url());
487 String user = m_user;
488 String password = m_pass;
490 if (!m_initialCredential.isEmpty()) {
491 user = m_initialCredential.user();
492 password = m_initialCredential.password();
495 if (user.isEmpty() && password.isEmpty())
496 return std::pair<String, String>("", "");
498 return std::pair<String, String>(user, password);
501 } // namespace WebCore