[Curl] Stop sending request with credential if no authorization requested.
[WebKit-https.git] / Source / WebKit / NetworkProcess / curl / NetworkDataTaskCurl.cpp
1 /*
2  * Copyright (C) 2018 Sony Interactive Entertainment Inc.
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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkDataTaskCurl.h"
28
29 #include "AuthenticationManager.h"
30 #include "NetworkSessionCurl.h"
31 #include <WebCore/AuthenticationChallenge.h>
32 #include <WebCore/CookiesStrategy.h>
33 #include <WebCore/CurlRequest.h>
34 #include <WebCore/NetworkStorageSession.h>
35 #include <WebCore/NotImplemented.h>
36 #include <WebCore/ResourceError.h>
37 #include <WebCore/SameSiteInfo.h>
38
39 using namespace WebCore;
40
41 namespace WebKit {
42
43 NetworkDataTaskCurl::NetworkDataTaskCurl(NetworkSession& session, NetworkDataTaskClient& client, const ResourceRequest& requestWithCredentials, StoredCredentialsPolicy storedCredentialsPolicy, ContentSniffingPolicy shouldContentSniff, ContentEncodingSniffingPolicy, bool shouldClearReferrerOnHTTPSToHTTPRedirect, bool dataTaskIsForMainFrameNavigation)
44     : NetworkDataTask(session, client, requestWithCredentials, storedCredentialsPolicy, shouldClearReferrerOnHTTPSToHTTPRedirect, dataTaskIsForMainFrameNavigation)
45 {
46     if (m_scheduledFailureType != NoFailure)
47         return;
48
49     auto request = requestWithCredentials;
50     if (request.url().protocolIsInHTTPFamily()) {
51         if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
52             auto url = request.url();
53             m_user = url.user();
54             m_password = url.pass();
55             request.removeCredentials();
56
57             if (m_user.isEmpty() && m_password.isEmpty())
58                 m_initialCredential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url());
59             else
60                 m_session->networkStorageSession().credentialStorage().set(m_partition, Credential(m_user, m_password, CredentialPersistenceNone), request.url());
61         }
62     }
63
64     m_curlRequest = createCurlRequest(WTFMove(request));
65     if (!m_initialCredential.isEmpty())
66         m_curlRequest->setUserPass(m_initialCredential.user(), m_initialCredential.password());
67     m_curlRequest->start();
68 }
69
70 NetworkDataTaskCurl::~NetworkDataTaskCurl()
71 {
72     if (m_curlRequest)
73         m_curlRequest->invalidateClient();
74 }
75
76 void NetworkDataTaskCurl::resume()
77 {
78     ASSERT(m_state != State::Running);
79     if (m_state == State::Canceling || m_state == State::Completed)
80         return;
81
82     m_state = State::Running;
83
84     if (m_scheduledFailureType != NoFailure) {
85         ASSERT(m_failureTimer.isActive());
86         return;
87     }
88
89     if (m_curlRequest)
90         m_curlRequest->resume();
91 }
92
93 void NetworkDataTaskCurl::suspend()
94 {
95     ASSERT(m_state != State::Suspended);
96     if (m_state == State::Canceling || m_state == State::Completed)
97         return;
98
99     m_state = State::Suspended;
100
101     if (m_curlRequest)
102         m_curlRequest->suspend();
103 }
104
105 void NetworkDataTaskCurl::cancel()
106 {
107     if (m_state == State::Canceling || m_state == State::Completed)
108         return;
109
110     m_state = State::Canceling;
111
112     if (m_curlRequest)
113         m_curlRequest->cancel();
114 }
115
116 void NetworkDataTaskCurl::invalidateAndCancel()
117 {
118     cancel();
119
120     if (m_curlRequest)
121         m_curlRequest->invalidateClient();
122 }
123
124 NetworkDataTask::State NetworkDataTaskCurl::state() const
125 {
126     return m_state;
127 }
128
129 Ref<CurlRequest> NetworkDataTaskCurl::createCurlRequest(ResourceRequest&& request, RequestStatus status)
130 {
131     if (status == RequestStatus::NewRequest)
132         appendCookieHeader(request);
133
134     // Creates a CurlRequest in suspended state.
135     // Then, NetworkDataTaskCurl::resume() will be called and communication resumes.
136     return CurlRequest::create(request, *this, CurlRequest::ShouldSuspend::Yes);
137 }
138
139 void NetworkDataTaskCurl::curlDidSendData(CurlRequest&, unsigned long long totalBytesSent, unsigned long long totalBytesExpectedToSend)
140 {
141     auto protectedThis = makeRef(*this);
142     if (state() == State::Canceling || state() == State::Completed || !m_client)
143         return;
144
145     m_client->didSendData(totalBytesSent, totalBytesExpectedToSend);
146 }
147
148 void NetworkDataTaskCurl::curlDidReceiveResponse(CurlRequest& request, const CurlResponse& receivedResponse)
149 {
150     auto protectedThis = makeRef(*this);
151     if (state() == State::Canceling || state() == State::Completed || !m_client)
152         return;
153
154     m_response = ResourceResponse(receivedResponse);
155     m_response.setDeprecatedNetworkLoadMetrics(request.networkLoadMetrics().isolatedCopy());
156
157     handleCookieHeaders(receivedResponse);
158
159     if (m_response.shouldRedirect()) {
160         willPerformHTTPRedirection();
161         return;
162     }
163
164     if (m_response.isUnauthorized() && receivedResponse.availableHttpAuth) {
165         tryHttpAuthentication(AuthenticationChallenge(receivedResponse, m_authFailureCount, m_response));
166         m_authFailureCount++;
167         return;
168     }
169
170     if (m_response.isProxyAuthenticationRequired() && receivedResponse.availableProxyAuth) {
171         tryProxyAuthentication(AuthenticationChallenge(receivedResponse, 0, m_response));
172         return;
173     }
174
175     didReceiveResponse(ResourceResponse(m_response), [this, protectedThis = makeRef(*this)](PolicyAction policyAction) {
176         if (m_state == State::Canceling || m_state == State::Completed)
177             return;
178
179         switch (policyAction) {
180         case PolicyAction::Use:
181             if (m_curlRequest)
182                 m_curlRequest->completeDidReceiveResponse();
183             break;
184         case PolicyAction::Ignore:
185             break;
186         case PolicyAction::Download:
187             notImplemented();
188             break;
189         }
190     });
191 }
192
193 void NetworkDataTaskCurl::curlDidReceiveBuffer(CurlRequest&, Ref<SharedBuffer>&& buffer)
194 {
195     auto protectedThis = makeRef(*this);
196     if (state() == State::Canceling || state() == State::Completed || (!m_client && !isDownload()))
197         return;
198
199     m_client->didReceiveData(WTFMove(buffer));
200 }
201
202 void NetworkDataTaskCurl::curlDidComplete(CurlRequest& request)
203 {
204     if (state() == State::Canceling || state() == State::Completed || (!m_client && !isDownload()))
205         return;
206
207     m_response.setDeprecatedNetworkLoadMetrics(request.networkLoadMetrics().isolatedCopy());
208
209     m_client->didCompleteWithError({ }, m_response.deprecatedNetworkLoadMetrics());
210 }
211
212 void NetworkDataTaskCurl::curlDidFailWithError(CurlRequest&, const ResourceError& resourceError)
213 {
214     if (state() == State::Canceling || state() == State::Completed || (!m_client && !isDownload()))
215         return;
216
217     m_client->didCompleteWithError(resourceError);
218 }
219
220 bool NetworkDataTaskCurl::shouldRedirectAsGET(const ResourceRequest& request, bool crossOrigin)
221 {
222     if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
223         return false;
224
225     if (!request.url().protocolIsInHTTPFamily())
226         return true;
227
228     if (m_response.isSeeOther())
229         return true;
230
231     if ((m_response.isMovedPermanently() || m_response.isFound()) && (request.httpMethod() == "POST"))
232         return true;
233
234     if (crossOrigin && (request.httpMethod() == "DELETE"))
235         return true;
236
237     return false;
238 }
239
240 void NetworkDataTaskCurl::willPerformHTTPRedirection()
241 {
242     static const int maxRedirects = 20;
243
244     if (m_redirectCount++ > maxRedirects) {
245         m_client->didCompleteWithError(ResourceError::httpError(CURLE_TOO_MANY_REDIRECTS, m_response.url()));
246         return;
247     }
248
249     ResourceRequest request = m_firstRequest;
250     URL redirectedURL = URL(m_response.url(), m_response.httpHeaderField(HTTPHeaderName::Location));
251     if (!redirectedURL.hasFragmentIdentifier() && request.url().hasFragmentIdentifier())
252         redirectedURL.setFragmentIdentifier(request.url().fragmentIdentifier());
253     request.setURL(redirectedURL);
254
255     // Should not set Referer after a redirect from a secure resource to non-secure one.
256     if (m_shouldClearReferrerOnHTTPSToHTTPRedirect && !request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https"))
257         request.clearHTTPReferrer();
258
259     bool isCrossOrigin = !protocolHostAndPortAreEqual(m_firstRequest.url(), request.url());
260     if (!equalLettersIgnoringASCIICase(request.httpMethod(), "get")) {
261         // Change request method to GET if change was made during a previous redirection or if current redirection says so.
262         if (!request.url().protocolIsInHTTPFamily() || shouldRedirectAsGET(request, isCrossOrigin)) {
263             request.setHTTPMethod("GET");
264             request.setHTTPBody(nullptr);
265             request.clearHTTPContentType();
266         }
267     }
268
269     bool didChangeCredential = false;
270     const auto& url = request.url();
271     m_user = url.user();
272     m_password = url.pass();
273     m_lastHTTPMethod = request.httpMethod();
274     request.removeCredentials();
275
276     if (isCrossOrigin) {
277         // The network layer might carry over some headers from the original request that
278         // we want to strip here because the redirect is cross-origin.
279         request.clearHTTPAuthorization();
280         request.clearHTTPOrigin();
281     } else if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
282         // Only consider applying authentication credentials if this is actually a redirect and the redirect
283         // URL didn't include credentials of its own.
284         if (m_user.isEmpty() && m_password.isEmpty()) {
285             auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url());
286             if (!credential.isEmpty()) {
287                 m_initialCredential = credential;
288                 didChangeCredential = true;
289             }
290         }
291     }
292
293     auto response = ResourceResponse(m_response);
294     m_client->willPerformHTTPRedirection(WTFMove(response), WTFMove(request), [this, protectedThis = makeRef(*this), didChangeCredential](const ResourceRequest& newRequest) {
295         if (newRequest.isNull() || m_state == State::Canceling)
296             return;
297
298         if (m_curlRequest)
299             m_curlRequest->cancel();
300
301         auto requestCopy = newRequest;
302         m_curlRequest = createCurlRequest(WTFMove(requestCopy));
303         if (didChangeCredential && !m_initialCredential.isEmpty())
304             m_curlRequest->setUserPass(m_initialCredential.user(), m_initialCredential.password());
305         m_curlRequest->start();
306
307         if (m_state != State::Suspended) {
308             m_state = State::Suspended;
309             resume();
310         }
311     });
312 }
313
314 void NetworkDataTaskCurl::tryHttpAuthentication(AuthenticationChallenge&& challenge)
315 {
316     if (!m_user.isNull() && !m_password.isNull()) {
317         auto persistence = m_storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use ? WebCore::CredentialPersistenceForSession : WebCore::CredentialPersistenceNone;
318         restartWithCredential(Credential(m_user, m_password, persistence));
319         m_user = String();
320         m_password = String();
321         return;
322     }
323
324     if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
325         if (!m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
326             // The stored credential wasn't accepted, stop using it. There is a race condition
327             // here, since a different credential might have already been stored by another
328             // NetworkDataTask, but the observable effect should be very minor, if any.
329             m_session->networkStorageSession().credentialStorage().remove(m_partition, challenge.protectionSpace());
330         }
331
332         if (!challenge.previousFailureCount()) {
333             auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, challenge.protectionSpace());
334             if (!credential.isEmpty() && credential != m_initialCredential) {
335                 ASSERT(credential.persistence() == CredentialPersistenceNone);
336                 if (challenge.failureResponse().isUnauthorized()) {
337                     // Store the credential back, possibly adding it as a default for this directory.
338                     m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
339                 }
340                 restartWithCredential(credential);
341                 return;
342             }
343         }
344     }
345
346     m_client->didReceiveChallenge(AuthenticationChallenge(challenge), [this, protectedThis = makeRef(*this), challenge](AuthenticationChallengeDisposition disposition, const Credential& credential) {
347         if (m_state == State::Canceling || m_state == State::Completed)
348             return;
349
350         if (disposition == AuthenticationChallengeDisposition::Cancel) {
351             cancel();
352             m_client->didCompleteWithError(ResourceError::httpError(CURLE_COULDNT_RESOLVE_HOST, m_response.url()));
353             return;
354         }
355
356         if (disposition == AuthenticationChallengeDisposition::UseCredential && !credential.isEmpty()) {
357             if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
358                 if (credential.persistence() == CredentialPersistenceForSession || credential.persistence() == CredentialPersistencePermanent)
359                     m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
360             }
361         }
362
363         restartWithCredential(credential);
364     });
365 }
366
367 void NetworkDataTaskCurl::tryProxyAuthentication(WebCore::AuthenticationChallenge&& challenge)
368 {
369     m_client->didReceiveChallenge(AuthenticationChallenge(challenge), [this, protectedThis = makeRef(*this), challenge](AuthenticationChallengeDisposition disposition, const Credential& credential) {
370         if (m_state == State::Canceling || m_state == State::Completed)
371             return;
372
373         if (disposition == AuthenticationChallengeDisposition::Cancel) {
374             cancel();
375             m_client->didCompleteWithError(ResourceError::httpError(CURLE_COULDNT_RESOLVE_PROXY, m_response.url()));
376             return;
377         }
378
379         CurlContext::singleton().setProxyUserPass(credential.user(), credential.password());
380         CurlContext::singleton().setDefaultProxyAuthMethod();
381
382         auto requestCredential = m_curlRequest ? Credential(m_curlRequest->user(), m_curlRequest->password(), CredentialPersistenceNone) : Credential();
383         restartWithCredential(requestCredential);
384     });
385 }
386
387 void NetworkDataTaskCurl::restartWithCredential(const Credential& credential)
388 {
389     ASSERT(m_curlRequest);
390
391     auto previousRequest = m_curlRequest->resourceRequest();
392     m_curlRequest->cancel();
393
394     m_curlRequest = createCurlRequest(WTFMove(previousRequest), RequestStatus::ReusedRequest);
395     m_curlRequest->setUserPass(credential.user(), credential.password());
396     m_curlRequest->start();
397
398     if (m_state != State::Suspended) {
399         m_state = State::Suspended;
400         resume();
401     }
402 }
403
404 void NetworkDataTaskCurl::appendCookieHeader(WebCore::ResourceRequest& request)
405 {
406     const auto& storageSession = m_session->networkStorageSession();
407     const auto& cookieJar = storageSession.cookieStorage();
408     auto includeSecureCookies = request.url().protocolIs("https") ? IncludeSecureCookies::Yes : IncludeSecureCookies::No;
409     auto cookieHeaderField = cookieJar.cookieRequestHeaderFieldValue(storageSession, request.firstPartyForCookies(), WebCore::SameSiteInfo::create(request), request.url(), std::nullopt, std::nullopt, includeSecureCookies).first;
410     if (!cookieHeaderField.isEmpty())
411         request.addHTTPHeaderField(HTTPHeaderName::Cookie, cookieHeaderField);
412 }
413
414 void NetworkDataTaskCurl::handleCookieHeaders(const CurlResponse& response)
415 {
416     static const auto setCookieHeader = "set-cookie: ";
417
418     const auto& storageSession = m_session->networkStorageSession();
419     const auto& cookieJar = storageSession.cookieStorage();
420     for (auto header : response.headers) {
421         if (header.startsWithIgnoringASCIICase(setCookieHeader)) {
422             String setCookieString = header.right(header.length() - strlen(setCookieHeader));
423             cookieJar.setCookiesFromHTTPResponse(storageSession, response.url, setCookieString);
424         }
425     }
426 }
427
428 } // namespace WebKit