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 | |
47 | namespace WebKit { |
48 | using namespace WebCore; |
49 | |
50 | static const size_t gDefaultReadBufferSize = 8192; |
51 | |
52 | NetworkDataTaskSoup::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 | |
80 | NetworkDataTaskSoup::~NetworkDataTaskSoup() |
81 | { |
82 | clearRequest(); |
83 | m_session->unregisterNetworkDataTask(*this); |
84 | } |
85 | |
86 | String 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 | |
98 | void NetworkDataTaskSoup::setPendingDownloadLocation(const String& filename, SandboxExtension::Handle&& sandboxExtensionHandle, bool allowOverwrite) |
99 | { |
100 | NetworkDataTask::setPendingDownloadLocation(filename, WTFMove(sandboxExtensionHandle), allowOverwrite); |
101 | m_allowOverwriteDownload = allowOverwrite; |
102 | } |
103 | |
104 | void 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 | |
180 | void 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 | |
203 | void 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 | |
238 | void 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 | |
254 | void NetworkDataTaskSoup::invalidateAndCancel() |
255 | { |
256 | cancel(); |
257 | clearRequest(); |
258 | } |
259 | |
260 | NetworkDataTask::State NetworkDataTaskSoup::state() const |
261 | { |
262 | return m_state; |
263 | } |
264 | |
265 | void 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 | |
277 | void NetworkDataTaskSoup::startTimeout() |
278 | { |
279 | if (m_firstRequest.timeoutInterval() > 0) |
280 | m_timeoutSource.startOneShot(1_s * m_firstRequest.timeoutInterval()); |
281 | } |
282 | |
283 | void NetworkDataTaskSoup::stopTimeout() |
284 | { |
285 | m_timeoutSource.stop(); |
286 | } |
287 | |
288 | void 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 | |
311 | void 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 | |
347 | void 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 | |
391 | void 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 | |
399 | gboolean 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 | |
413 | bool 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 | |
427 | void 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 | |
441 | void 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 | |
460 | static inline bool isAuthenticationFailureStatusCode(int httpStatusCode) |
461 | { |
462 | return httpStatusCode == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED || httpStatusCode == SOUP_STATUS_UNAUTHORIZED; |
463 | } |
464 | |
465 | void 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 | |
513 | void 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 | |
549 | void 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 | |
568 | void 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 | |
576 | void NetworkDataTaskSoup::didFinishSkipInputStreamForRedirection() |
577 | { |
578 | g_input_stream_close(m_inputStream.get(), nullptr, nullptr); |
579 | continueHTTPRedirection(); |
580 | } |
581 | |
582 | static 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 | |
603 | bool 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 | |
622 | void 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 | |
696 | void 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 | |
721 | void 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 | |
730 | void 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 | |
743 | void 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 | |
763 | void 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 | |
788 | void 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 | |
797 | void 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 | |
807 | void 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 | |
816 | void NetworkDataTaskSoup::(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 | |
826 | void NetworkDataTaskSoup::() |
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 ; |
843 | SoupMessageHeadersIter ; |
844 | soup_message_headers_iter_init(&headersIter, m_soupMessage->request_headers); |
845 | const char* ; |
846 | const char* ; |
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 | |
853 | void 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 | |
863 | void 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 | |
870 | void 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 | |
913 | void 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 | |
937 | void 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 | |
967 | void 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 | |
976 | void 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 | |
1003 | void 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 | |
1016 | void 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 | |
1028 | void 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 | |
1040 | void 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 | |
1049 | void 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) |
1088 | void 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 |
1097 | void 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 | |
1110 | void NetworkDataTaskSoup::didStartRequest() |
1111 | { |
1112 | m_networkLoadMetrics.requestStart = MonotonicTime::now() - m_startTime; |
1113 | } |
1114 | |
1115 | void 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 | |
1126 | void NetworkDataTaskSoup::didRestart() |
1127 | { |
1128 | m_startTime = MonotonicTime::now(); |
1129 | m_networkLoadMetrics.reset(); |
1130 | } |
1131 | |
1132 | } // namespace WebKit |
1133 | |
1134 | |