1/*
2 * Copyright (C) 2016 Igalia S.L.
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 "NetworkDataTaskSoup.h"
28
29#include "AuthenticationChallengeDisposition.h"
30#include "AuthenticationManager.h"
31#include "DataReference.h"
32#include "Download.h"
33#include "NetworkLoad.h"
34#include "NetworkProcess.h"
35#include "NetworkSessionSoup.h"
36#include "WebErrors.h"
37#include <WebCore/AuthenticationChallenge.h>
38#include <WebCore/HTTPParsers.h>
39#include <WebCore/MIMETypeRegistry.h>
40#include <WebCore/NetworkStorageSession.h>
41#include <WebCore/SharedBuffer.h>
42#include <WebCore/SoupNetworkSession.h>
43#include <WebCore/TextEncoding.h>
44#include <wtf/MainThread.h>
45#include <wtf/glib/RunLoopSourcePriority.h>
46
47namespace WebKit {
48using namespace WebCore;
49
50static const size_t gDefaultReadBufferSize = 8192;
51
52NetworkDataTaskSoup::NetworkDataTaskSoup(NetworkSession& session, NetworkDataTaskClient& client, const ResourceRequest& requestWithCredentials, StoredCredentialsPolicy storedCredentialsPolicy, ContentSniffingPolicy shouldContentSniff, WebCore::ContentEncodingSniffingPolicy, bool shouldClearReferrerOnHTTPSToHTTPRedirect, bool dataTaskIsForMainFrameNavigation)
53 : NetworkDataTask(session, client, requestWithCredentials, storedCredentialsPolicy, shouldClearReferrerOnHTTPSToHTTPRedirect, dataTaskIsForMainFrameNavigation)
54 , m_shouldContentSniff(shouldContentSniff)
55 , m_timeoutSource(RunLoop::main(), this, &NetworkDataTaskSoup::timeoutFired)
56{
57 m_session->registerNetworkDataTask(*this);
58 if (m_scheduledFailureType != NoFailure)
59 return;
60
61 auto request = requestWithCredentials;
62 if (request.url().protocolIsInHTTPFamily()) {
63 m_startTime = MonotonicTime::now();
64 auto url = request.url();
65 if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
66 m_user = url.user();
67 m_password = url.pass();
68 request.removeCredentials();
69
70 if (m_user.isEmpty() && m_password.isEmpty())
71 m_initialCredential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url());
72 else
73 m_session->networkStorageSession().credentialStorage().set(m_partition, Credential(m_user, m_password, CredentialPersistenceNone), request.url());
74 }
75 applyAuthenticationToRequest(request);
76 }
77 createRequest(WTFMove(request));
78}
79
80NetworkDataTaskSoup::~NetworkDataTaskSoup()
81{
82 clearRequest();
83 m_session->unregisterNetworkDataTask(*this);
84}
85
86String NetworkDataTaskSoup::suggestedFilename() const
87{
88 if (!m_suggestedFilename.isEmpty())
89 return m_suggestedFilename;
90
91 String suggestedFilename = m_response.suggestedFilename();
92 if (!suggestedFilename.isEmpty())
93 return suggestedFilename;
94
95 return decodeURLEscapeSequences(m_response.url().lastPathComponent());
96}
97
98void NetworkDataTaskSoup::setPendingDownloadLocation(const String& filename, SandboxExtension::Handle&& sandboxExtensionHandle, bool allowOverwrite)
99{
100 NetworkDataTask::setPendingDownloadLocation(filename, WTFMove(sandboxExtensionHandle), allowOverwrite);
101 m_allowOverwriteDownload = allowOverwrite;
102}
103
104void NetworkDataTaskSoup::createRequest(ResourceRequest&& request)
105{
106 m_currentRequest = WTFMove(request);
107
108 GUniquePtr<SoupURI> soupURI = m_currentRequest.createSoupURI();
109 if (!soupURI) {
110 scheduleFailure(InvalidURLFailure);
111 return;
112 }
113
114 GRefPtr<SoupRequest> soupRequest = adoptGRef(soup_session_request_uri(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), soupURI.get(), nullptr));
115 if (!soupRequest) {
116 scheduleFailure(InvalidURLFailure);
117 return;
118 }
119
120 m_currentRequest.updateSoupRequest(soupRequest.get());
121
122 if (!m_currentRequest.url().protocolIsInHTTPFamily()) {
123 m_soupRequest = WTFMove(soupRequest);
124 return;
125 }
126
127 // HTTP request.
128 GRefPtr<SoupMessage> soupMessage = adoptGRef(soup_request_http_get_message(SOUP_REQUEST_HTTP(soupRequest.get())));
129 if (!soupMessage) {
130 scheduleFailure(InvalidURLFailure);
131 return;
132 }
133
134 unsigned messageFlags = SOUP_MESSAGE_NO_REDIRECT;
135
136 m_currentRequest.updateSoupMessage(soupMessage.get());
137 if (m_shouldContentSniff == ContentSniffingPolicy::DoNotSniffContent)
138 soup_message_disable_feature(soupMessage.get(), SOUP_TYPE_CONTENT_SNIFFER);
139 if (m_user.isEmpty() && m_password.isEmpty() && m_storedCredentialsPolicy == StoredCredentialsPolicy::DoNotUse) {
140#if SOUP_CHECK_VERSION(2, 57, 1)
141 messageFlags |= SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE;
142#else
143 // In case credential is not available and credential storage should not to be used,
144 // disable authentication manager so that credentials stored in libsoup are not used.
145 soup_message_disable_feature(soupMessage.get(), SOUP_TYPE_AUTH_MANAGER);
146#endif
147 }
148
149 // Make sure we have an Accept header for subresources; some sites want this to serve some of their subresources.
150 if (!soup_message_headers_get_one(soupMessage->request_headers, "Accept"))
151 soup_message_headers_append(soupMessage->request_headers, "Accept", "*/*");
152
153 // In the case of XHR .send() and .send("") explicitly tell libsoup to send a zero content-lenght header
154 // for consistency with other UA implementations like Firefox. It's done in the backend here instead of
155 // in XHR code since in XHR CORS checking prevents us from this kind of late header manipulation.
156 if ((soupMessage->method == SOUP_METHOD_POST || soupMessage->method == SOUP_METHOD_PUT) && !soupMessage->request_body->length)
157 soup_message_headers_set_content_length(soupMessage->request_headers, 0);
158
159 soup_message_set_flags(soupMessage.get(), static_cast<SoupMessageFlags>(soup_message_get_flags(soupMessage.get()) | messageFlags));
160
161#if SOUP_CHECK_VERSION(2, 43, 1)
162 soup_message_set_priority(soupMessage.get(), toSoupMessagePriority(m_currentRequest.priority()));
163#endif
164
165 m_soupRequest = WTFMove(soupRequest);
166 m_soupMessage = WTFMove(soupMessage);
167
168 g_signal_connect(m_soupMessage.get(), "got-headers", G_CALLBACK(gotHeadersCallback), this);
169 g_signal_connect(m_soupMessage.get(), "wrote-body-data", G_CALLBACK(wroteBodyDataCallback), this);
170 g_signal_connect(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), "authenticate", G_CALLBACK(authenticateCallback), this);
171 g_signal_connect(m_soupMessage.get(), "network-event", G_CALLBACK(networkEventCallback), this);
172 g_signal_connect(m_soupMessage.get(), "restarted", G_CALLBACK(restartedCallback), this);
173#if SOUP_CHECK_VERSION(2, 49, 91)
174 g_signal_connect(m_soupMessage.get(), "starting", G_CALLBACK(startingCallback), this);
175#else
176 g_signal_connect(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), "request-started", G_CALLBACK(requestStartedCallback), this);
177#endif
178}
179
180void NetworkDataTaskSoup::clearRequest()
181{
182 if (m_state == State::Completed)
183 return;
184
185 m_state = State::Completed;
186
187 stopTimeout();
188 m_pendingResult = nullptr;
189 m_soupRequest = nullptr;
190 m_inputStream = nullptr;
191 m_multipartInputStream = nullptr;
192 m_downloadOutputStream = nullptr;
193 g_cancellable_cancel(m_cancellable.get());
194 m_cancellable = nullptr;
195 if (m_soupMessage) {
196 g_signal_handlers_disconnect_matched(m_soupMessage.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
197 soup_session_cancel_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), m_soupMessage.get(), SOUP_STATUS_CANCELLED);
198 m_soupMessage = nullptr;
199 }
200 g_signal_handlers_disconnect_matched(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
201}
202
203void NetworkDataTaskSoup::resume()
204{
205 ASSERT(m_state != State::Running);
206 if (m_state == State::Canceling || m_state == State::Completed)
207 return;
208
209 m_state = State::Running;
210
211 if (m_scheduledFailureType != NoFailure) {
212 ASSERT(m_failureTimer.isActive());
213 return;
214 }
215
216 startTimeout();
217
218 RefPtr<NetworkDataTaskSoup> protectedThis(this);
219 if (m_soupRequest && !m_cancellable) {
220 m_cancellable = adoptGRef(g_cancellable_new());
221 soup_request_send_async(m_soupRequest.get(), m_cancellable.get(), reinterpret_cast<GAsyncReadyCallback>(sendRequestCallback), protectedThis.leakRef());
222 return;
223 }
224
225 if (m_pendingResult) {
226 GRefPtr<GAsyncResult> pendingResult = WTFMove(m_pendingResult);
227 if (m_inputStream)
228 readCallback(m_inputStream.get(), pendingResult.get(), protectedThis.leakRef());
229 else if (m_multipartInputStream)
230 requestNextPartCallback(m_multipartInputStream.get(), pendingResult.get(), protectedThis.leakRef());
231 else if (m_soupRequest)
232 sendRequestCallback(m_soupRequest.get(), pendingResult.get(), protectedThis.leakRef());
233 else
234 ASSERT_NOT_REACHED();
235 }
236}
237
238void NetworkDataTaskSoup::cancel()
239{
240 if (m_state == State::Canceling || m_state == State::Completed)
241 return;
242
243 m_state = State::Canceling;
244
245 if (m_soupMessage)
246 soup_session_cancel_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), m_soupMessage.get(), SOUP_STATUS_CANCELLED);
247
248 g_cancellable_cancel(m_cancellable.get());
249
250 if (isDownload())
251 cleanDownloadFiles();
252}
253
254void NetworkDataTaskSoup::invalidateAndCancel()
255{
256 cancel();
257 clearRequest();
258}
259
260NetworkDataTask::State NetworkDataTaskSoup::state() const
261{
262 return m_state;
263}
264
265void NetworkDataTaskSoup::timeoutFired()
266{
267 if (m_state == State::Canceling || m_state == State::Completed || !m_client) {
268 clearRequest();
269 return;
270 }
271
272 RefPtr<NetworkDataTaskSoup> protectedThis(this);
273 invalidateAndCancel();
274 dispatchDidCompleteWithError(ResourceError::timeoutError(m_firstRequest.url()));
275}
276
277void NetworkDataTaskSoup::startTimeout()
278{
279 if (m_firstRequest.timeoutInterval() > 0)
280 m_timeoutSource.startOneShot(1_s * m_firstRequest.timeoutInterval());
281}
282
283void NetworkDataTaskSoup::stopTimeout()
284{
285 m_timeoutSource.stop();
286}
287
288void NetworkDataTaskSoup::sendRequestCallback(SoupRequest* soupRequest, GAsyncResult* result, NetworkDataTaskSoup* task)
289{
290 RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task);
291 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
292 task->clearRequest();
293 return;
294 }
295 ASSERT(soupRequest == task->m_soupRequest.get());
296
297 if (task->state() == State::Suspended) {
298 ASSERT(!task->m_pendingResult);
299 task->m_pendingResult = result;
300 return;
301 }
302
303 GUniqueOutPtr<GError> error;
304 GRefPtr<GInputStream> inputStream = adoptGRef(soup_request_send_finish(soupRequest, result, &error.outPtr()));
305 if (error)
306 task->didFail(ResourceError::httpError(task->m_soupMessage.get(), error.get(), soupRequest));
307 else
308 task->didSendRequest(WTFMove(inputStream));
309}
310
311void NetworkDataTaskSoup::didSendRequest(GRefPtr<GInputStream>&& inputStream)
312{
313 if (m_soupMessage) {
314 if (m_shouldContentSniff == ContentSniffingPolicy::SniffContent && m_soupMessage->status_code != SOUP_STATUS_NOT_MODIFIED)
315 m_response.setSniffedContentType(soup_request_get_content_type(m_soupRequest.get()));
316 m_response.updateFromSoupMessage(m_soupMessage.get());
317 if (m_response.mimeType().isEmpty() && m_soupMessage->status_code != SOUP_STATUS_NOT_MODIFIED)
318 m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(m_response.url().path()));
319
320 if (shouldStartHTTPRedirection()) {
321 m_inputStream = WTFMove(inputStream);
322 skipInputStreamForRedirection();
323 return;
324 }
325
326 if (m_response.isMultipart())
327 m_multipartInputStream = adoptGRef(soup_multipart_input_stream_new(m_soupMessage.get(), inputStream.get()));
328 else
329 m_inputStream = WTFMove(inputStream);
330
331 m_networkLoadMetrics.responseStart = MonotonicTime::now() - m_startTime;
332 } else {
333 m_response.setURL(m_firstRequest.url());
334 const gchar* contentType = soup_request_get_content_type(m_soupRequest.get());
335 m_response.setMimeType(extractMIMETypeFromMediaType(contentType));
336 m_response.setTextEncodingName(extractCharsetFromMediaType(contentType));
337 m_response.setExpectedContentLength(soup_request_get_content_length(m_soupRequest.get()));
338 if (m_response.mimeType().isEmpty())
339 m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(m_response.url().path()));
340
341 m_inputStream = WTFMove(inputStream);
342 }
343
344 dispatchDidReceiveResponse();
345}
346
347void NetworkDataTaskSoup::dispatchDidReceiveResponse()
348{
349 ASSERT(!m_response.isNull());
350
351 // FIXME: Remove this once nobody depends on deprecatedNetworkLoadMetrics.
352 NetworkLoadMetrics& deprecatedResponseMetrics = m_response.deprecatedNetworkLoadMetrics();
353 deprecatedResponseMetrics.responseStart = m_networkLoadMetrics.responseStart;
354 deprecatedResponseMetrics.domainLookupStart = m_networkLoadMetrics.domainLookupStart;
355 deprecatedResponseMetrics.domainLookupEnd = m_networkLoadMetrics.domainLookupEnd;
356 deprecatedResponseMetrics.connectStart = m_networkLoadMetrics.connectStart;
357 deprecatedResponseMetrics.secureConnectionStart = m_networkLoadMetrics.secureConnectionStart;
358 deprecatedResponseMetrics.connectEnd = m_networkLoadMetrics.connectEnd;
359 deprecatedResponseMetrics.requestStart = m_networkLoadMetrics.requestStart;
360 deprecatedResponseMetrics.responseStart = m_networkLoadMetrics.responseStart;
361
362 didReceiveResponse(ResourceResponse(m_response), [this, protectedThis = makeRef(*this)](PolicyAction policyAction) {
363 if (m_state == State::Canceling || m_state == State::Completed) {
364 clearRequest();
365 return;
366 }
367
368 switch (policyAction) {
369 case PolicyAction::Use:
370 if (m_inputStream)
371 read();
372 else if (m_multipartInputStream)
373 requestNextPart();
374 else
375 ASSERT_NOT_REACHED();
376
377 break;
378 case PolicyAction::Ignore:
379 clearRequest();
380 break;
381 case PolicyAction::Download:
382 download();
383 break;
384 case PolicyAction::StopAllLoads:
385 ASSERT_NOT_REACHED();
386 break;
387 }
388 });
389}
390
391void NetworkDataTaskSoup::dispatchDidCompleteWithError(const ResourceError& error)
392{
393 m_networkLoadMetrics.responseEnd = MonotonicTime::now() - m_startTime;
394 m_networkLoadMetrics.markComplete();
395
396 m_client->didCompleteWithError(error, m_networkLoadMetrics);
397}
398
399gboolean NetworkDataTaskSoup::tlsConnectionAcceptCertificateCallback(GTlsConnection* connection, GTlsCertificate* certificate, GTlsCertificateFlags errors, NetworkDataTaskSoup* task)
400{
401 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
402 task->clearRequest();
403 return FALSE;
404 }
405
406 auto* connectionMessage = g_object_get_data(G_OBJECT(connection), "wk-soup-message");
407 if (connectionMessage != task->m_soupMessage.get())
408 return FALSE;
409
410 return task->tlsConnectionAcceptCertificate(certificate, errors);
411}
412
413bool NetworkDataTaskSoup::tlsConnectionAcceptCertificate(GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors)
414{
415 ASSERT(m_soupRequest);
416 URL url = soupURIToURL(soup_request_get_uri(m_soupRequest.get()));
417 auto error = SoupNetworkSession::checkTLSErrors(url, certificate, tlsErrors);
418 if (!error)
419 return true;
420
421 RefPtr<NetworkDataTaskSoup> protectedThis(this);
422 invalidateAndCancel();
423 dispatchDidCompleteWithError(error.value());
424 return false;
425}
426
427void NetworkDataTaskSoup::applyAuthenticationToRequest(ResourceRequest& request)
428{
429 if (m_user.isEmpty() && m_password.isEmpty())
430 return;
431
432 auto url = request.url();
433 url.setUser(m_user);
434 url.setPass(m_password);
435 request.setURL(url);
436
437 m_user = String();
438 m_password = String();
439}
440
441void NetworkDataTaskSoup::authenticateCallback(SoupSession* session, SoupMessage* soupMessage, SoupAuth* soupAuth, gboolean retrying, NetworkDataTaskSoup* task)
442{
443 ASSERT(session == static_cast<NetworkSessionSoup&>(task->m_session.get()).soupSession());
444
445 // We don't return early here in case the given soupMessage is different to m_soupMessage when
446 // it's proxy authentication and the request URL is HTTPS, because in that case libsoup uses a
447 // tunnel internally and the SoupMessage used for the authentication is the tunneling one.
448 // See https://bugs.webkit.org/show_bug.cgi?id=175378.
449 if (soupMessage != task->m_soupMessage.get() && (soupMessage->status_code != SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED || !task->m_currentRequest.url().protocolIs("https")))
450 return;
451
452 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
453 task->clearRequest();
454 return;
455 }
456
457 task->authenticate(AuthenticationChallenge(soupMessage, soupAuth, retrying));
458}
459
460static inline bool isAuthenticationFailureStatusCode(int httpStatusCode)
461{
462 return httpStatusCode == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED || httpStatusCode == SOUP_STATUS_UNAUTHORIZED;
463}
464
465void NetworkDataTaskSoup::authenticate(AuthenticationChallenge&& challenge)
466{
467 ASSERT(m_soupMessage);
468 if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
469 if (!m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
470 // The stored credential wasn't accepted, stop using it. There is a race condition
471 // here, since a different credential might have already been stored by another
472 // NetworkDataTask, but the observable effect should be very minor, if any.
473 m_session->networkStorageSession().credentialStorage().remove(m_partition, challenge.protectionSpace());
474 }
475
476 if (!challenge.previousFailureCount()) {
477 auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, challenge.protectionSpace());
478 if (!credential.isEmpty() && credential != m_initialCredential) {
479 ASSERT(credential.persistence() == CredentialPersistenceNone);
480
481 if (isAuthenticationFailureStatusCode(challenge.failureResponse().httpStatusCode())) {
482 // Store the credential back, possibly adding it as a default for this directory.
483 m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
484 }
485 soup_auth_authenticate(challenge.soupAuth(), credential.user().utf8().data(), credential.password().utf8().data());
486 return;
487 }
488 }
489 }
490
491 soup_session_pause_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), challenge.soupMessage());
492
493 // We could also do this before we even start the request, but that would be at the expense
494 // of all request latency, versus a one-time latency for the small subset of requests that
495 // use HTTP authentication. In the end, this doesn't matter much, because persistent credentials
496 // will become session credentials after the first use.
497 if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
498 auto protectionSpace = challenge.protectionSpace();
499 m_session->networkStorageSession().getCredentialFromPersistentStorage(protectionSpace, m_cancellable.get(),
500 [this, protectedThis = makeRef(*this), authChallenge = WTFMove(challenge)] (Credential&& credential) mutable {
501 if (m_state == State::Canceling || m_state == State::Completed || !m_client) {
502 clearRequest();
503 return;
504 }
505
506 authChallenge.setProposedCredential(WTFMove(credential));
507 continueAuthenticate(WTFMove(authChallenge));
508 });
509 } else
510 continueAuthenticate(WTFMove(challenge));
511}
512
513void NetworkDataTaskSoup::continueAuthenticate(AuthenticationChallenge&& challenge)
514{
515 m_client->didReceiveChallenge(AuthenticationChallenge(challenge), [this, protectedThis = makeRef(*this), challenge](AuthenticationChallengeDisposition disposition, const Credential& credential) {
516 if (m_state == State::Canceling || m_state == State::Completed) {
517 clearRequest();
518 return;
519 }
520
521 if (disposition == AuthenticationChallengeDisposition::Cancel) {
522 cancel();
523 didFail(cancelledError(m_soupRequest.get()));
524 return;
525 }
526
527 if (disposition == AuthenticationChallengeDisposition::UseCredential && !credential.isEmpty()) {
528 if (m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
529 // Eventually we will manage per-session credentials only internally or use some newly-exposed API from libsoup,
530 // because once we authenticate via libsoup, there is no way to ignore it for a particular request. Right now,
531 // we place the credentials in the store even though libsoup will never fire the authenticate signal again for
532 // this protection space.
533 if (credential.persistence() == CredentialPersistenceForSession || credential.persistence() == CredentialPersistencePermanent)
534 m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
535
536 if (credential.persistence() == CredentialPersistencePermanent) {
537 m_protectionSpaceForPersistentStorage = challenge.protectionSpace();
538 m_credentialForPersistentStorage = credential;
539 }
540 }
541
542 soup_auth_authenticate(challenge.soupAuth(), credential.user().utf8().data(), credential.password().utf8().data());
543 }
544
545 soup_session_unpause_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), challenge.soupMessage());
546 });
547}
548
549void NetworkDataTaskSoup::skipInputStreamForRedirectionCallback(GInputStream* inputStream, GAsyncResult* result, NetworkDataTaskSoup* task)
550{
551 RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task);
552 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
553 task->clearRequest();
554 return;
555 }
556 ASSERT(inputStream == task->m_inputStream.get());
557
558 GUniqueOutPtr<GError> error;
559 gssize bytesSkipped = g_input_stream_skip_finish(inputStream, result, &error.outPtr());
560 if (error)
561 task->didFail(ResourceError::genericGError(error.get(), task->m_soupRequest.get()));
562 else if (bytesSkipped > 0)
563 task->skipInputStreamForRedirection();
564 else
565 task->didFinishSkipInputStreamForRedirection();
566}
567
568void NetworkDataTaskSoup::skipInputStreamForRedirection()
569{
570 ASSERT(m_inputStream);
571 RefPtr<NetworkDataTaskSoup> protectedThis(this);
572 g_input_stream_skip_async(m_inputStream.get(), gDefaultReadBufferSize, RunLoopSourcePriority::AsyncIONetwork, m_cancellable.get(),
573 reinterpret_cast<GAsyncReadyCallback>(skipInputStreamForRedirectionCallback), protectedThis.leakRef());
574}
575
576void NetworkDataTaskSoup::didFinishSkipInputStreamForRedirection()
577{
578 g_input_stream_close(m_inputStream.get(), nullptr, nullptr);
579 continueHTTPRedirection();
580}
581
582static bool shouldRedirectAsGET(SoupMessage* message, bool crossOrigin)
583{
584 if (message->method == SOUP_METHOD_GET || message->method == SOUP_METHOD_HEAD)
585 return false;
586
587 switch (message->status_code) {
588 case SOUP_STATUS_SEE_OTHER:
589 return true;
590 case SOUP_STATUS_FOUND:
591 case SOUP_STATUS_MOVED_PERMANENTLY:
592 if (message->method == SOUP_METHOD_POST)
593 return true;
594 break;
595 }
596
597 if (crossOrigin && message->method == SOUP_METHOD_DELETE)
598 return true;
599
600 return false;
601}
602
603bool NetworkDataTaskSoup::shouldStartHTTPRedirection()
604{
605 ASSERT(m_soupMessage);
606 ASSERT(!m_response.isNull());
607
608 auto status = m_response.httpStatusCode();
609 if (!SOUP_STATUS_IS_REDIRECTION(status))
610 return false;
611
612 // Some 3xx status codes aren't actually redirects.
613 if (status == 300 || status == 304 || status == 305 || status == 306)
614 return false;
615
616 if (m_response.httpHeaderField(HTTPHeaderName::Location).isEmpty())
617 return false;
618
619 return true;
620}
621
622void NetworkDataTaskSoup::continueHTTPRedirection()
623{
624 ASSERT(m_soupMessage);
625 ASSERT(!m_response.isNull());
626
627 static const unsigned maxRedirects = 20;
628 if (m_redirectCount++ > maxRedirects) {
629 didFail(ResourceError::transportError(m_soupRequest.get(), SOUP_STATUS_TOO_MANY_REDIRECTS, "Too many redirects"));
630 return;
631 }
632
633 ResourceRequest request = m_currentRequest;
634 URL redirectedURL = URL(m_response.url(), m_response.httpHeaderField(HTTPHeaderName::Location));
635 if (!redirectedURL.hasFragmentIdentifier() && request.url().hasFragmentIdentifier())
636 redirectedURL.setFragmentIdentifier(request.url().fragmentIdentifier());
637 request.setURL(redirectedURL);
638
639 // Should not set Referer after a redirect from a secure resource to non-secure one.
640 if (m_shouldClearReferrerOnHTTPSToHTTPRedirect && !request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https"))
641 request.clearHTTPReferrer();
642
643 bool isCrossOrigin = !protocolHostAndPortAreEqual(m_currentRequest.url(), request.url());
644 if (!equalLettersIgnoringASCIICase(request.httpMethod(), "get")) {
645 // Change newRequest method to GET if change was made during a previous redirection or if current redirection says so.
646 if (m_soupMessage->method == SOUP_METHOD_GET || !request.url().protocolIsInHTTPFamily() || shouldRedirectAsGET(m_soupMessage.get(), isCrossOrigin)) {
647 request.setHTTPMethod("GET");
648 request.setHTTPBody(nullptr);
649 request.clearHTTPContentType();
650 }
651 }
652
653 const auto& url = request.url();
654 m_user = url.user();
655 m_password = url.pass();
656 m_lastHTTPMethod = request.httpMethod();
657 request.removeCredentials();
658
659 if (isCrossOrigin) {
660 // The network layer might carry over some headers from the original request that
661 // we want to strip here because the redirect is cross-origin.
662 request.clearHTTPAuthorization();
663 request.clearHTTPOrigin();
664 } else if (url.protocolIsInHTTPFamily() && m_storedCredentialsPolicy == StoredCredentialsPolicy::Use) {
665 if (m_user.isEmpty() && m_password.isEmpty()) {
666 auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url());
667 if (!credential.isEmpty())
668 m_initialCredential = credential;
669 }
670 }
671
672 clearRequest();
673
674 auto response = ResourceResponse(m_response);
675 m_client->willPerformHTTPRedirection(WTFMove(response), WTFMove(request), [this, protectedThis = makeRef(*this), isCrossOrigin](const ResourceRequest& newRequest) {
676 if (newRequest.isNull() || m_state == State::Canceling)
677 return;
678
679 auto request = newRequest;
680 if (request.url().protocolIsInHTTPFamily()) {
681 if (isCrossOrigin) {
682 m_startTime = MonotonicTime::now();
683 m_networkLoadMetrics.reset();
684 }
685
686 applyAuthenticationToRequest(request);
687 }
688 createRequest(WTFMove(request));
689 if (m_soupRequest && m_state != State::Suspended) {
690 m_state = State::Suspended;
691 resume();
692 }
693 });
694}
695
696void NetworkDataTaskSoup::readCallback(GInputStream* inputStream, GAsyncResult* result, NetworkDataTaskSoup* task)
697{
698 RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task);
699 if (task->state() == State::Canceling || task->state() == State::Completed || (!task->m_client && !task->isDownload())) {
700 task->clearRequest();
701 return;
702 }
703 ASSERT(inputStream == task->m_inputStream.get());
704
705 if (task->state() == State::Suspended) {
706 ASSERT(!task->m_pendingResult);
707 task->m_pendingResult = result;
708 return;
709 }
710
711 GUniqueOutPtr<GError> error;
712 gssize bytesRead = g_input_stream_read_finish(inputStream, result, &error.outPtr());
713 if (error)
714 task->didFail(ResourceError::genericGError(error.get(), task->m_soupRequest.get()));
715 else if (bytesRead > 0)
716 task->didRead(bytesRead);
717 else
718 task->didFinishRead();
719}
720
721void NetworkDataTaskSoup::read()
722{
723 RefPtr<NetworkDataTaskSoup> protectedThis(this);
724 ASSERT(m_inputStream);
725 m_readBuffer.grow(gDefaultReadBufferSize);
726 g_input_stream_read_async(m_inputStream.get(), m_readBuffer.data(), m_readBuffer.size(), RunLoopSourcePriority::AsyncIONetwork, m_cancellable.get(),
727 reinterpret_cast<GAsyncReadyCallback>(readCallback), protectedThis.leakRef());
728}
729
730void NetworkDataTaskSoup::didRead(gssize bytesRead)
731{
732 m_readBuffer.shrink(bytesRead);
733 if (m_downloadOutputStream) {
734 ASSERT(isDownload());
735 writeDownload();
736 } else {
737 ASSERT(m_client);
738 m_client->didReceiveData(SharedBuffer::create(WTFMove(m_readBuffer)));
739 read();
740 }
741}
742
743void NetworkDataTaskSoup::didFinishRead()
744{
745 ASSERT(m_inputStream);
746 g_input_stream_close(m_inputStream.get(), nullptr, nullptr);
747 m_inputStream = nullptr;
748 if (m_multipartInputStream) {
749 requestNextPart();
750 return;
751 }
752
753 if (m_downloadOutputStream) {
754 didFinishDownload();
755 return;
756 }
757
758 clearRequest();
759 ASSERT(m_client);
760 dispatchDidCompleteWithError({ });
761}
762
763void NetworkDataTaskSoup::requestNextPartCallback(SoupMultipartInputStream* multipartInputStream, GAsyncResult* result, NetworkDataTaskSoup* task)
764{
765 RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task);
766 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
767 task->clearRequest();
768 return;
769 }
770 ASSERT(multipartInputStream == task->m_multipartInputStream.get());
771
772 if (task->state() == State::Suspended) {
773 ASSERT(!task->m_pendingResult);
774 task->m_pendingResult = result;
775 return;
776 }
777
778 GUniqueOutPtr<GError> error;
779 GRefPtr<GInputStream> inputStream = adoptGRef(soup_multipart_input_stream_next_part_finish(multipartInputStream, result, &error.outPtr()));
780 if (error)
781 task->didFail(ResourceError::httpError(task->m_soupMessage.get(), error.get(), task->m_soupRequest.get()));
782 else if (inputStream)
783 task->didRequestNextPart(WTFMove(inputStream));
784 else
785 task->didFinishRequestNextPart();
786}
787
788void NetworkDataTaskSoup::requestNextPart()
789{
790 RefPtr<NetworkDataTaskSoup> protectedThis(this);
791 ASSERT(m_multipartInputStream);
792 ASSERT(!m_inputStream);
793 soup_multipart_input_stream_next_part_async(m_multipartInputStream.get(), RunLoopSourcePriority::AsyncIONetwork, m_cancellable.get(),
794 reinterpret_cast<GAsyncReadyCallback>(requestNextPartCallback), protectedThis.leakRef());
795}
796
797void NetworkDataTaskSoup::didRequestNextPart(GRefPtr<GInputStream>&& inputStream)
798{
799 ASSERT(!m_inputStream);
800 m_inputStream = WTFMove(inputStream);
801 m_response = ResourceResponse();
802 m_response.setURL(m_firstRequest.url());
803 m_response.updateFromSoupMessageHeaders(soup_multipart_input_stream_get_headers(m_multipartInputStream.get()));
804 dispatchDidReceiveResponse();
805}
806
807void NetworkDataTaskSoup::didFinishRequestNextPart()
808{
809 ASSERT(!m_inputStream);
810 ASSERT(m_multipartInputStream);
811 g_input_stream_close(G_INPUT_STREAM(m_multipartInputStream.get()), nullptr, nullptr);
812 clearRequest();
813 dispatchDidCompleteWithError({ });
814}
815
816void NetworkDataTaskSoup::gotHeadersCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task)
817{
818 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
819 task->clearRequest();
820 return;
821 }
822 ASSERT(task->m_soupMessage.get() == soupMessage);
823 task->didGetHeaders();
824}
825
826void NetworkDataTaskSoup::didGetHeaders()
827{
828 // We are a bit more conservative with the persistent credential storage than the session store,
829 // since we are waiting until we know that this authentication succeeded before actually storing.
830 // This is because we want to avoid hitting the disk twice (once to add and once to remove) for
831 // incorrect credentials or polluting the keychain with invalid credentials.
832 if (!isAuthenticationFailureStatusCode(m_soupMessage->status_code) && m_soupMessage->status_code < 500) {
833 m_session->networkStorageSession().saveCredentialToPersistentStorage(m_protectionSpaceForPersistentStorage, m_credentialForPersistentStorage);
834 m_protectionSpaceForPersistentStorage = ProtectionSpace();
835 m_credentialForPersistentStorage = Credential();
836 }
837
838 // Soup adds more headers to the request after starting signal is emitted, and got-headers
839 // is the first one we receive after starting, so we use it also to get information about the
840 // request headers.
841 if (shouldCaptureExtraNetworkLoadMetrics()) {
842 HTTPHeaderMap requestHeaders;
843 SoupMessageHeadersIter headersIter;
844 soup_message_headers_iter_init(&headersIter, m_soupMessage->request_headers);
845 const char* headerName;
846 const char* headerValue;
847 while (soup_message_headers_iter_next(&headersIter, &headerName, &headerValue))
848 requestHeaders.set(String(headerName), String(headerValue));
849 m_networkLoadMetrics.requestHeaders = WTFMove(requestHeaders);
850 }
851}
852
853void NetworkDataTaskSoup::wroteBodyDataCallback(SoupMessage* soupMessage, SoupBuffer* buffer, NetworkDataTaskSoup* task)
854{
855 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) {
856 task->clearRequest();
857 return;
858 }
859 ASSERT(task->m_soupMessage.get() == soupMessage);
860 task->didWriteBodyData(buffer->length);
861}
862
863void NetworkDataTaskSoup::didWriteBodyData(uint64_t bytesSent)
864{
865 RefPtr<NetworkDataTaskSoup> protectedThis(this);
866 m_bodyDataTotalBytesSent += bytesSent;
867 m_client->didSendData(m_bodyDataTotalBytesSent, m_soupMessage->request_body->length);
868}
869
870void NetworkDataTaskSoup::download()
871{
872 ASSERT(isDownload());
873 ASSERT(m_pendingDownloadLocation);
874 ASSERT(!m_response.isNull());
875
876 if (m_response.httpStatusCode() >= 400) {
877 didFailDownload(downloadNetworkError(m_response.url(), m_response.httpStatusText()));
878 return;
879 }
880
881 CString downloadDestinationPath = m_pendingDownloadLocation.utf8();
882 m_downloadDestinationFile = adoptGRef(g_file_new_for_path(downloadDestinationPath.data()));
883 GRefPtr<GFileOutputStream> outputStream;
884 GUniqueOutPtr<GError> error;
885 if (m_allowOverwriteDownload)
886 outputStream = adoptGRef(g_file_replace(m_downloadDestinationFile.get(), nullptr, FALSE, G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
887 else
888 outputStream = adoptGRef(g_file_create(m_downloadDestinationFile.get(), G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
889 if (!outputStream) {
890 didFailDownload(downloadDestinationError(m_response, error->message));
891 return;
892 }
893
894 GUniquePtr<char> intermediatePath(g_strdup_printf("%s.wkdownload", downloadDestinationPath.data()));
895 m_downloadIntermediateFile = adoptGRef(g_file_new_for_path(intermediatePath.get()));
896 outputStream = adoptGRef(g_file_replace(m_downloadIntermediateFile.get(), nullptr, TRUE, G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
897 if (!outputStream) {
898 didFailDownload(downloadDestinationError(m_response, error->message));
899 return;
900 }
901 m_downloadOutputStream = adoptGRef(G_OUTPUT_STREAM(outputStream.leakRef()));
902
903 auto& downloadManager = m_session->networkProcess().downloadManager();
904 auto download = std::make_unique<Download>(downloadManager, m_pendingDownloadID, *this, m_session->sessionID(), suggestedFilename());
905 auto* downloadPtr = download.get();
906 downloadManager.dataTaskBecameDownloadTask(m_pendingDownloadID, WTFMove(download));
907 downloadPtr->didCreateDestination(m_pendingDownloadLocation);
908
909 ASSERT(!m_client);
910 read();
911}
912
913void NetworkDataTaskSoup::writeDownloadCallback(GOutputStream* outputStream, GAsyncResult* result, NetworkDataTaskSoup* task)
914{
915 RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task);
916 if (task->state() == State::Canceling || task->state() == State::Completed || !task->isDownload()) {
917 task->clearRequest();
918 return;
919 }
920 ASSERT(outputStream == task->m_downloadOutputStream.get());
921
922 GUniqueOutPtr<GError> error;
923 gsize bytesWritten;
924#if GLIB_CHECK_VERSION(2, 44, 0)
925 g_output_stream_write_all_finish(outputStream, result, &bytesWritten, &error.outPtr());
926#else
927 gssize writeTaskResult = g_task_propagate_int(G_TASK(result), &error.outPtr());
928 if (writeTaskResult != -1)
929 bytesWritten = writeTaskResult;
930#endif
931 if (error)
932 task->didFailDownload(downloadDestinationError(task->m_response, error->message));
933 else
934 task->didWriteDownload(bytesWritten);
935}
936
937void NetworkDataTaskSoup::writeDownload()
938{
939 RefPtr<NetworkDataTaskSoup> protectedThis(this);
940#if GLIB_CHECK_VERSION(2, 44, 0)
941 g_output_stream_write_all_async(m_downloadOutputStream.get(), m_readBuffer.data(), m_readBuffer.size(), RunLoopSourcePriority::AsyncIONetwork, m_cancellable.get(),
942 reinterpret_cast<GAsyncReadyCallback>(writeDownloadCallback), protectedThis.leakRef());
943#else
944 GRefPtr<GTask> writeTask = adoptGRef(g_task_new(m_downloadOutputStream.get(), m_cancellable.get(),
945 reinterpret_cast<GAsyncReadyCallback>(writeDownloadCallback), protectedThis.leakRef()));
946 g_task_set_task_data(writeTask.get(), this, nullptr);
947 g_task_run_in_thread(writeTask.get(), [](GTask* writeTask, gpointer source, gpointer userData, GCancellable* cancellable) {
948 auto* task = static_cast<NetworkDataTaskSoup*>(userData);
949 GOutputStream* outputStream = G_OUTPUT_STREAM(source);
950 RELEASE_ASSERT(task->m_downloadOutputStream.get() == outputStream);
951 RELEASE_ASSERT(task->m_cancellable.get() == cancellable);
952 GError* error = nullptr;
953 if (g_cancellable_set_error_if_cancelled(cancellable, &error)) {
954 g_task_return_error(writeTask, error);
955 return;
956 }
957
958 gsize bytesWritten;
959 if (g_output_stream_write_all(outputStream, task->m_readBuffer.data(), task->m_readBuffer.size(), &bytesWritten, cancellable, &error))
960 g_task_return_int(writeTask, bytesWritten);
961 else
962 g_task_return_error(writeTask, error);
963 });
964#endif
965}
966
967void NetworkDataTaskSoup::didWriteDownload(gsize bytesWritten)
968{
969 ASSERT(bytesWritten == m_readBuffer.size());
970 auto* download = m_session->networkProcess().downloadManager().download(m_pendingDownloadID);
971 ASSERT(download);
972 download->didReceiveData(bytesWritten);
973 read();
974}
975
976void NetworkDataTaskSoup::didFinishDownload()
977{
978 ASSERT(!m_response.isNull());
979 ASSERT(m_downloadOutputStream);
980 g_output_stream_close(m_downloadOutputStream.get(), nullptr, nullptr);
981 m_downloadOutputStream = nullptr;
982
983 ASSERT(m_downloadDestinationFile);
984 ASSERT(m_downloadIntermediateFile);
985 GUniqueOutPtr<GError> error;
986 if (!g_file_move(m_downloadIntermediateFile.get(), m_downloadDestinationFile.get(), G_FILE_COPY_OVERWRITE, m_cancellable.get(), nullptr, nullptr, &error.outPtr())) {
987 didFailDownload(downloadDestinationError(m_response, error->message));
988 return;
989 }
990
991 GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new());
992 CString uri = m_response.url().string().utf8();
993 g_file_info_set_attribute_string(info.get(), "metadata::download-uri", uri.data());
994 g_file_info_set_attribute_string(info.get(), "xattr::xdg.origin.url", uri.data());
995 g_file_set_attributes_async(m_downloadDestinationFile.get(), info.get(), G_FILE_QUERY_INFO_NONE, RunLoopSourcePriority::AsyncIONetwork, nullptr, nullptr, nullptr);
996
997 clearRequest();
998 auto* download = m_session->networkProcess().downloadManager().download(m_pendingDownloadID);
999 ASSERT(download);
1000 download->didFinish();
1001}
1002
1003void NetworkDataTaskSoup::didFailDownload(const ResourceError& error)
1004{
1005 clearRequest();
1006 cleanDownloadFiles();
1007 if (m_client)
1008 dispatchDidCompleteWithError(error);
1009 else {
1010 auto* download = m_session->networkProcess().downloadManager().download(m_pendingDownloadID);
1011 ASSERT(download);
1012 download->didFail(error, IPC::DataReference());
1013 }
1014}
1015
1016void NetworkDataTaskSoup::cleanDownloadFiles()
1017{
1018 if (m_downloadDestinationFile) {
1019 g_file_delete(m_downloadDestinationFile.get(), nullptr, nullptr);
1020 m_downloadDestinationFile = nullptr;
1021 }
1022 if (m_downloadIntermediateFile) {
1023 g_file_delete(m_downloadIntermediateFile.get(), nullptr, nullptr);
1024 m_downloadIntermediateFile = nullptr;
1025 }
1026}
1027
1028void NetworkDataTaskSoup::didFail(const ResourceError& error)
1029{
1030 if (isDownload()) {
1031 didFailDownload(downloadNetworkError(error.failingURL(), error.localizedDescription()));
1032 return;
1033 }
1034
1035 clearRequest();
1036 ASSERT(m_client);
1037 dispatchDidCompleteWithError(error);
1038}
1039
1040void NetworkDataTaskSoup::networkEventCallback(SoupMessage* soupMessage, GSocketClientEvent event, GIOStream* stream, NetworkDataTaskSoup* task)
1041{
1042 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client)
1043 return;
1044
1045 ASSERT(task->m_soupMessage.get() == soupMessage);
1046 task->networkEvent(event, stream);
1047}
1048
1049void NetworkDataTaskSoup::networkEvent(GSocketClientEvent event, GIOStream* stream)
1050{
1051 Seconds deltaTime = MonotonicTime::now() - m_startTime;
1052 switch (event) {
1053 case G_SOCKET_CLIENT_RESOLVING:
1054 m_networkLoadMetrics.domainLookupStart = deltaTime;
1055 break;
1056 case G_SOCKET_CLIENT_RESOLVED:
1057 m_networkLoadMetrics.domainLookupEnd = deltaTime;
1058 break;
1059 case G_SOCKET_CLIENT_CONNECTING:
1060 m_networkLoadMetrics.connectStart = deltaTime;
1061 break;
1062 case G_SOCKET_CLIENT_CONNECTED:
1063 // Web Timing considers that connection time involves dns, proxy & TLS negotiation...
1064 // so we better pick G_SOCKET_CLIENT_COMPLETE for connectEnd
1065 break;
1066 case G_SOCKET_CLIENT_PROXY_NEGOTIATING:
1067 break;
1068 case G_SOCKET_CLIENT_PROXY_NEGOTIATED:
1069 break;
1070 case G_SOCKET_CLIENT_TLS_HANDSHAKING:
1071 m_networkLoadMetrics.secureConnectionStart = deltaTime;
1072 RELEASE_ASSERT(G_IS_TLS_CONNECTION(stream));
1073 g_object_set_data(G_OBJECT(stream), "wk-soup-message", m_soupMessage.get());
1074 g_signal_connect(stream, "accept-certificate", G_CALLBACK(tlsConnectionAcceptCertificateCallback), this);
1075 break;
1076 case G_SOCKET_CLIENT_TLS_HANDSHAKED:
1077 break;
1078 case G_SOCKET_CLIENT_COMPLETE:
1079 m_networkLoadMetrics.connectEnd = deltaTime;
1080 break;
1081 default:
1082 ASSERT_NOT_REACHED();
1083 break;
1084 }
1085}
1086
1087#if SOUP_CHECK_VERSION(2, 49, 91)
1088void NetworkDataTaskSoup::startingCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task)
1089{
1090 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client)
1091 return;
1092
1093 ASSERT(task->m_soupMessage.get() == soupMessage);
1094 task->didStartRequest();
1095}
1096#else
1097void NetworkDataTaskSoup::requestStartedCallback(SoupSession* session, SoupMessage* soupMessage, SoupSocket*, NetworkDataTaskSoup* task)
1098{
1099 ASSERT(session == static_cast<NetworkSessionSoup&>(task->m_session.get()).soupSession());
1100 if (soupMessage != task->m_soupMessage.get())
1101 return;
1102
1103 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client)
1104 return;
1105
1106 task->didStartRequest();
1107}
1108#endif
1109
1110void NetworkDataTaskSoup::didStartRequest()
1111{
1112 m_networkLoadMetrics.requestStart = MonotonicTime::now() - m_startTime;
1113}
1114
1115void NetworkDataTaskSoup::restartedCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task)
1116{
1117 // Called each time the message is going to be sent again except the first time.
1118 // This happens when libsoup handles HTTP authentication.
1119 if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client)
1120 return;
1121
1122 ASSERT(task->m_soupMessage.get() == soupMessage);
1123 task->didRestart();
1124}
1125
1126void NetworkDataTaskSoup::didRestart()
1127{
1128 m_startTime = MonotonicTime::now();
1129 m_networkLoadMetrics.reset();
1130}
1131
1132} // namespace WebKit
1133
1134