diff options
Diffstat (limited to 'Source/WebKit2/NetworkProcess/soup')
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.cpp | 1102 | ||||
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.h | 145 | ||||
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/NetworkProcessMainSoup.cpp | 40 | ||||
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/NetworkProcessSoup.cpp | 149 | ||||
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.cpp | 65 | ||||
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.h (renamed from Source/WebKit2/NetworkProcess/soup/NetworkResourceLoadSchedulerSoup.cpp) | 37 | ||||
-rw-r--r-- | Source/WebKit2/NetworkProcess/soup/RemoteNetworkingContextSoup.cpp | 17 |
7 files changed, 1468 insertions, 87 deletions
diff --git a/Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.cpp b/Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.cpp new file mode 100644 index 000000000..8e77fc407 --- /dev/null +++ b/Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.cpp @@ -0,0 +1,1102 @@ +/* + * Copyright (C) 2016 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "NetworkDataTaskSoup.h" + +#include "AuthenticationManager.h" +#include "DataReference.h" +#include "Download.h" +#include "DownloadSoupErrors.h" +#include "NetworkLoad.h" +#include "NetworkProcess.h" +#include "NetworkSessionSoup.h" +#include "WebErrors.h" +#include <WebCore/AuthenticationChallenge.h> +#include <WebCore/HTTPParsers.h> +#include <WebCore/MIMETypeRegistry.h> +#include <WebCore/NetworkStorageSession.h> +#include <WebCore/SharedBuffer.h> +#include <WebCore/SoupNetworkSession.h> +#include <wtf/MainThread.h> + +using namespace WebCore; + +namespace WebKit { + +static const size_t gDefaultReadBufferSize = 8192; + +NetworkDataTaskSoup::NetworkDataTaskSoup(NetworkSession& session, NetworkDataTaskClient& client, const ResourceRequest& requestWithCredentials, StoredCredentials storedCredentials, ContentSniffingPolicy shouldContentSniff, bool shouldClearReferrerOnHTTPSToHTTPRedirect) + : NetworkDataTask(session, client, requestWithCredentials, storedCredentials, shouldClearReferrerOnHTTPSToHTTPRedirect) + , m_shouldContentSniff(shouldContentSniff) + , m_timeoutSource(RunLoop::main(), this, &NetworkDataTaskSoup::timeoutFired) +{ + m_session->registerNetworkDataTask(*this); + if (m_scheduledFailureType != NoFailure) + return; + + auto request = requestWithCredentials; + if (request.url().protocolIsInHTTPFamily()) { +#if ENABLE(WEB_TIMING) + m_startTime = monotonicallyIncreasingTimeMS(); +#endif + auto url = request.url(); + if (m_storedCredentials == AllowStoredCredentials) { + m_user = url.user(); + m_password = url.pass(); + request.removeCredentials(); + + if (m_user.isEmpty() && m_password.isEmpty()) + m_initialCredential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url()); + else + m_session->networkStorageSession().credentialStorage().set(m_partition, Credential(m_user, m_password, CredentialPersistenceNone), request.url()); + } + applyAuthenticationToRequest(request); + } + createRequest(WTFMove(request)); +} + +NetworkDataTaskSoup::~NetworkDataTaskSoup() +{ + clearRequest(); + m_session->unregisterNetworkDataTask(*this); +} + +String NetworkDataTaskSoup::suggestedFilename() const +{ + if (!m_suggestedFilename.isEmpty()) + return m_suggestedFilename; + + String suggestedFilename = m_response.suggestedFilename(); + if (!suggestedFilename.isEmpty()) + return suggestedFilename; + + return decodeURLEscapeSequences(m_response.url().lastPathComponent()); +} + +void NetworkDataTaskSoup::setPendingDownloadLocation(const String& filename, const SandboxExtension::Handle& sandboxExtensionHandle, bool allowOverwrite) +{ + NetworkDataTask::setPendingDownloadLocation(filename, sandboxExtensionHandle, allowOverwrite); + m_allowOverwriteDownload = allowOverwrite; +} + +void NetworkDataTaskSoup::createRequest(ResourceRequest&& request) +{ + m_currentRequest = WTFMove(request); + + GUniquePtr<SoupURI> soupURI = m_currentRequest.createSoupURI(); + if (!soupURI) { + scheduleFailure(InvalidURLFailure); + return; + } + + GRefPtr<SoupRequest> soupRequest = adoptGRef(soup_session_request_uri(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), soupURI.get(), nullptr)); + if (!soupRequest) { + scheduleFailure(InvalidURLFailure); + return; + } + + m_currentRequest.updateSoupRequest(soupRequest.get()); + + if (!m_currentRequest.url().protocolIsInHTTPFamily()) { + m_soupRequest = WTFMove(soupRequest); + return; + } + + // HTTP request. + GRefPtr<SoupMessage> soupMessage = adoptGRef(soup_request_http_get_message(SOUP_REQUEST_HTTP(soupRequest.get()))); + if (!soupMessage) { + scheduleFailure(InvalidURLFailure); + return; + } + + unsigned messageFlags = SOUP_MESSAGE_NO_REDIRECT; + + m_currentRequest.updateSoupMessage(soupMessage.get()); + if (m_shouldContentSniff == DoNotSniffContent) + soup_message_disable_feature(soupMessage.get(), SOUP_TYPE_CONTENT_SNIFFER); + if (m_user.isEmpty() && m_password.isEmpty() && m_storedCredentials == DoNotAllowStoredCredentials) { +#if SOUP_CHECK_VERSION(2, 57, 1) + messageFlags |= SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE; +#else + // In case credential is not available and credential storage should not to be used, + // disable authentication manager so that credentials stored in libsoup are not used. + soup_message_disable_feature(soupMessage.get(), SOUP_TYPE_AUTH_MANAGER); +#endif + } + + // Make sure we have an Accept header for subresources; some sites want this to serve some of their subresources. + if (!soup_message_headers_get_one(soupMessage->request_headers, "Accept")) + soup_message_headers_append(soupMessage->request_headers, "Accept", "*/*"); + + // In the case of XHR .send() and .send("") explicitly tell libsoup to send a zero content-lenght header + // for consistency with other UA implementations like Firefox. It's done in the backend here instead of + // in XHR code since in XHR CORS checking prevents us from this kind of late header manipulation. + if ((soupMessage->method == SOUP_METHOD_POST || soupMessage->method == SOUP_METHOD_PUT) && !soupMessage->request_body->length) + soup_message_headers_set_content_length(soupMessage->request_headers, 0); + + soup_message_set_flags(soupMessage.get(), static_cast<SoupMessageFlags>(soup_message_get_flags(soupMessage.get()) | messageFlags)); + +#if SOUP_CHECK_VERSION(2, 43, 1) + soup_message_set_priority(soupMessage.get(), toSoupMessagePriority(m_currentRequest.priority())); +#endif + + m_soupRequest = WTFMove(soupRequest); + m_soupMessage = WTFMove(soupMessage); + + g_signal_connect(m_soupMessage.get(), "notify::tls-errors", G_CALLBACK(tlsErrorsChangedCallback), this); + g_signal_connect(m_soupMessage.get(), "got-headers", G_CALLBACK(gotHeadersCallback), this); + g_signal_connect(m_soupMessage.get(), "wrote-body-data", G_CALLBACK(wroteBodyDataCallback), this); + g_signal_connect(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), "authenticate", G_CALLBACK(authenticateCallback), this); +#if ENABLE(WEB_TIMING) + g_signal_connect(m_soupMessage.get(), "network-event", G_CALLBACK(networkEventCallback), this); + g_signal_connect(m_soupMessage.get(), "restarted", G_CALLBACK(restartedCallback), this); +#if SOUP_CHECK_VERSION(2, 49, 91) + g_signal_connect(m_soupMessage.get(), "starting", G_CALLBACK(startingCallback), this); +#else + g_signal_connect(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), "request-started", G_CALLBACK(requestStartedCallback), this); +#endif +#endif +} + +void NetworkDataTaskSoup::clearRequest() +{ + if (m_state == State::Completed) + return; + + m_state = State::Completed; + + stopTimeout(); + m_pendingResult = nullptr; + m_soupRequest = nullptr; + m_inputStream = nullptr; + m_multipartInputStream = nullptr; + m_downloadOutputStream = nullptr; + g_cancellable_cancel(m_cancellable.get()); + m_cancellable = nullptr; + if (m_soupMessage) { + g_signal_handlers_disconnect_matched(m_soupMessage.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); + soup_session_cancel_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), m_soupMessage.get(), SOUP_STATUS_CANCELLED); + m_soupMessage = nullptr; + } + g_signal_handlers_disconnect_matched(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); +} + +void NetworkDataTaskSoup::resume() +{ + ASSERT(m_state != State::Running); + if (m_state == State::Canceling || m_state == State::Completed) + return; + + m_state = State::Running; + + if (m_scheduledFailureType != NoFailure) { + ASSERT(m_failureTimer.isActive()); + return; + } + + startTimeout(); + + RefPtr<NetworkDataTaskSoup> protectedThis(this); + if (m_soupRequest && !m_cancellable) { + m_cancellable = adoptGRef(g_cancellable_new()); + soup_request_send_async(m_soupRequest.get(), m_cancellable.get(), reinterpret_cast<GAsyncReadyCallback>(sendRequestCallback), protectedThis.leakRef()); + return; + } + + if (m_pendingResult) { + GRefPtr<GAsyncResult> pendingResult = WTFMove(m_pendingResult); + if (m_inputStream) + readCallback(m_inputStream.get(), pendingResult.get(), protectedThis.leakRef()); + else if (m_multipartInputStream) + requestNextPartCallback(m_multipartInputStream.get(), pendingResult.get(), protectedThis.leakRef()); + else if (m_soupRequest) + sendRequestCallback(m_soupRequest.get(), pendingResult.get(), protectedThis.leakRef()); + else + ASSERT_NOT_REACHED(); + } +} + +void NetworkDataTaskSoup::suspend() +{ + ASSERT(m_state != State::Suspended); + if (m_state == State::Canceling || m_state == State::Completed) + return; + m_state = State::Suspended; + + stopTimeout(); +} + +void NetworkDataTaskSoup::cancel() +{ + if (m_state == State::Canceling || m_state == State::Completed) + return; + + m_state = State::Canceling; + + if (m_soupMessage) + soup_session_cancel_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), m_soupMessage.get(), SOUP_STATUS_CANCELLED); + + g_cancellable_cancel(m_cancellable.get()); + + if (isDownload()) + cleanDownloadFiles(); +} + +void NetworkDataTaskSoup::invalidateAndCancel() +{ + cancel(); + clearRequest(); +} + +NetworkDataTask::State NetworkDataTaskSoup::state() const +{ + return m_state; +} + +void NetworkDataTaskSoup::timeoutFired() +{ + if (m_state == State::Canceling || m_state == State::Completed || !m_client) { + clearRequest(); + return; + } + + RefPtr<NetworkDataTaskSoup> protectedThis(this); + invalidateAndCancel(); + m_client->didCompleteWithError(ResourceError::timeoutError(m_firstRequest.url())); +} + +void NetworkDataTaskSoup::startTimeout() +{ + if (m_firstRequest.timeoutInterval() > 0) + m_timeoutSource.startOneShot(m_firstRequest.timeoutInterval()); +} + +void NetworkDataTaskSoup::stopTimeout() +{ + m_timeoutSource.stop(); +} + +void NetworkDataTaskSoup::sendRequestCallback(SoupRequest* soupRequest, GAsyncResult* result, NetworkDataTaskSoup* task) +{ + RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task); + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + ASSERT(soupRequest == task->m_soupRequest.get()); + + if (task->state() == State::Suspended) { + ASSERT(!task->m_pendingResult); + task->m_pendingResult = result; + return; + } + + GUniqueOutPtr<GError> error; + GRefPtr<GInputStream> inputStream = adoptGRef(soup_request_send_finish(soupRequest, result, &error.outPtr())); + if (error) + task->didFail(ResourceError::httpError(task->m_soupMessage.get(), error.get(), soupRequest)); + else + task->didSendRequest(WTFMove(inputStream)); +} + +void NetworkDataTaskSoup::didSendRequest(GRefPtr<GInputStream>&& inputStream) +{ + if (m_soupMessage) { + if (m_shouldContentSniff == SniffContent && m_soupMessage->status_code != SOUP_STATUS_NOT_MODIFIED) + m_response.setSniffedContentType(soup_request_get_content_type(m_soupRequest.get())); + m_response.updateFromSoupMessage(m_soupMessage.get()); + if (m_response.mimeType().isEmpty() && m_soupMessage->status_code != SOUP_STATUS_NOT_MODIFIED) + m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(m_response.url().path())); + + if (shouldStartHTTPRedirection()) { + m_inputStream = WTFMove(inputStream); + skipInputStreamForRedirection(); + return; + } + + if (m_response.isMultipart()) + m_multipartInputStream = adoptGRef(soup_multipart_input_stream_new(m_soupMessage.get(), inputStream.get())); + else + m_inputStream = WTFMove(inputStream); + +#if ENABLE(WEB_TIMING) + m_response.networkLoadTiming().responseStart = monotonicallyIncreasingTimeMS() - m_startTime; +#endif + } else { + m_response.setURL(m_firstRequest.url()); + const gchar* contentType = soup_request_get_content_type(m_soupRequest.get()); + m_response.setMimeType(extractMIMETypeFromMediaType(contentType)); + m_response.setTextEncodingName(extractCharsetFromMediaType(contentType)); + m_response.setExpectedContentLength(soup_request_get_content_length(m_soupRequest.get())); + if (m_response.mimeType().isEmpty()) + m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(m_response.url().path())); + + m_inputStream = WTFMove(inputStream); + } + + dispatchDidReceiveResponse(); +} + +void NetworkDataTaskSoup::dispatchDidReceiveResponse() +{ + ASSERT(!m_response.isNull()); + + didReceiveResponse(ResourceResponse(m_response), [this, protectedThis = makeRef(*this)](PolicyAction policyAction) { + if (m_state == State::Canceling || m_state == State::Completed) { + clearRequest(); + return; + } + + switch (policyAction) { + case PolicyAction::PolicyUse: + if (m_inputStream) + read(); + else if (m_multipartInputStream) + requestNextPart(); + else + ASSERT_NOT_REACHED(); + + break; + case PolicyAction::PolicyIgnore: + clearRequest(); + break; + case PolicyAction::PolicyDownload: + download(); + break; + } + }); +} + +void NetworkDataTaskSoup::tlsErrorsChangedCallback(SoupMessage* soupMessage, GParamSpec*, NetworkDataTaskSoup* task) +{ + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + + ASSERT(soupMessage == task->m_soupMessage.get()); + task->tlsErrorsChanged(); +} + +void NetworkDataTaskSoup::tlsErrorsChanged() +{ + ASSERT(m_soupRequest); + SoupNetworkSession::checkTLSErrors(m_soupRequest.get(), m_soupMessage.get(), [this] (const ResourceError& error) { + if (error.isNull()) + return; + + RefPtr<NetworkDataTaskSoup> protectedThis(this); + invalidateAndCancel(); + m_client->didCompleteWithError(error); + }); +} + +void NetworkDataTaskSoup::applyAuthenticationToRequest(ResourceRequest& request) +{ + if (m_user.isEmpty() && m_password.isEmpty()) + return; + + auto url = request.url(); + url.setUser(m_user); + url.setPass(m_password); + request.setURL(url); + + m_user = String(); + m_password = String(); +} + +void NetworkDataTaskSoup::authenticateCallback(SoupSession* session, SoupMessage* soupMessage, SoupAuth* soupAuth, gboolean retrying, NetworkDataTaskSoup* task) +{ + ASSERT(session == static_cast<NetworkSessionSoup&>(task->m_session.get()).soupSession()); + if (soupMessage != task->m_soupMessage.get()) + return; + + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + + task->authenticate(AuthenticationChallenge(soupMessage, soupAuth, retrying)); +} + +static inline bool isAuthenticationFailureStatusCode(int httpStatusCode) +{ + return httpStatusCode == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED || httpStatusCode == SOUP_STATUS_UNAUTHORIZED; +} + +void NetworkDataTaskSoup::authenticate(AuthenticationChallenge&& challenge) +{ + ASSERT(m_soupMessage); + if (m_storedCredentials == AllowStoredCredentials) { + if (!m_initialCredential.isEmpty() || challenge.previousFailureCount()) { + // The stored credential wasn't accepted, stop using it. There is a race condition + // here, since a different credential might have already been stored by another + // NetworkDataTask, but the observable effect should be very minor, if any. + m_session->networkStorageSession().credentialStorage().remove(m_partition, challenge.protectionSpace()); + } + + if (!challenge.previousFailureCount()) { + auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, challenge.protectionSpace()); + if (!credential.isEmpty() && credential != m_initialCredential) { + ASSERT(credential.persistence() == CredentialPersistenceNone); + + if (isAuthenticationFailureStatusCode(challenge.failureResponse().httpStatusCode())) { + // Store the credential back, possibly adding it as a default for this directory. + m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url()); + } + soup_auth_authenticate(challenge.soupAuth(), credential.user().utf8().data(), credential.password().utf8().data()); + return; + } + } + } + + soup_session_pause_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), m_soupMessage.get()); + + // We could also do this before we even start the request, but that would be at the expense + // of all request latency, versus a one-time latency for the small subset of requests that + // use HTTP authentication. In the end, this doesn't matter much, because persistent credentials + // will become session credentials after the first use. + if (m_storedCredentials == AllowStoredCredentials) { + auto protectionSpace = challenge.protectionSpace(); + m_session->networkStorageSession().getCredentialFromPersistentStorage(protectionSpace, + [this, protectedThis = makeRef(*this), authChallenge = WTFMove(challenge)] (Credential&& credential) mutable { + if (m_state == State::Canceling || m_state == State::Completed || !m_client) { + clearRequest(); + return; + } + + authChallenge.setProposedCredential(WTFMove(credential)); + continueAuthenticate(WTFMove(authChallenge)); + }); + } else + continueAuthenticate(WTFMove(challenge)); +} + +void NetworkDataTaskSoup::continueAuthenticate(AuthenticationChallenge&& challenge) +{ + m_client->didReceiveChallenge(challenge, [this, protectedThis = makeRef(*this), challenge](AuthenticationChallengeDisposition disposition, const Credential& credential) { + if (m_state == State::Canceling || m_state == State::Completed) { + clearRequest(); + return; + } + + if (disposition == AuthenticationChallengeDisposition::Cancel) { + cancel(); + didFail(cancelledError(m_soupRequest.get())); + return; + } + + if (disposition == AuthenticationChallengeDisposition::UseCredential && !credential.isEmpty()) { + if (m_storedCredentials == AllowStoredCredentials) { + // Eventually we will manage per-session credentials only internally or use some newly-exposed API from libsoup, + // because once we authenticate via libsoup, there is no way to ignore it for a particular request. Right now, + // we place the credentials in the store even though libsoup will never fire the authenticate signal again for + // this protection space. + if (credential.persistence() == CredentialPersistenceForSession || credential.persistence() == CredentialPersistencePermanent) + m_session->networkStorageSession().credentialStorage().set(m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url()); + + if (credential.persistence() == CredentialPersistencePermanent) { + m_protectionSpaceForPersistentStorage = challenge.protectionSpace(); + m_credentialForPersistentStorage = credential; + } + } + + soup_auth_authenticate(challenge.soupAuth(), credential.user().utf8().data(), credential.password().utf8().data()); + } + + soup_session_unpause_message(static_cast<NetworkSessionSoup&>(m_session.get()).soupSession(), m_soupMessage.get()); + }); +} + +void NetworkDataTaskSoup::skipInputStreamForRedirectionCallback(GInputStream* inputStream, GAsyncResult* result, NetworkDataTaskSoup* task) +{ + RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task); + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + ASSERT(inputStream == task->m_inputStream.get()); + + GUniqueOutPtr<GError> error; + gssize bytesSkipped = g_input_stream_skip_finish(inputStream, result, &error.outPtr()); + if (error) + task->didFail(ResourceError::genericGError(error.get(), task->m_soupRequest.get())); + else if (bytesSkipped > 0) + task->skipInputStreamForRedirection(); + else + task->didFinishSkipInputStreamForRedirection(); +} + +void NetworkDataTaskSoup::skipInputStreamForRedirection() +{ + ASSERT(m_inputStream); + RefPtr<NetworkDataTaskSoup> protectedThis(this); + g_input_stream_skip_async(m_inputStream.get(), gDefaultReadBufferSize, G_PRIORITY_DEFAULT, m_cancellable.get(), + reinterpret_cast<GAsyncReadyCallback>(skipInputStreamForRedirectionCallback), protectedThis.leakRef()); +} + +void NetworkDataTaskSoup::didFinishSkipInputStreamForRedirection() +{ + g_input_stream_close(m_inputStream.get(), nullptr, nullptr); + continueHTTPRedirection(); +} + +static bool shouldRedirectAsGET(SoupMessage* message, bool crossOrigin) +{ + if (message->method == SOUP_METHOD_GET || message->method == SOUP_METHOD_HEAD) + return false; + + switch (message->status_code) { + case SOUP_STATUS_SEE_OTHER: + return true; + case SOUP_STATUS_FOUND: + case SOUP_STATUS_MOVED_PERMANENTLY: + if (message->method == SOUP_METHOD_POST) + return true; + break; + } + + if (crossOrigin && message->method == SOUP_METHOD_DELETE) + return true; + + return false; +} + +bool NetworkDataTaskSoup::shouldStartHTTPRedirection() +{ + ASSERT(m_soupMessage); + ASSERT(!m_response.isNull()); + + auto status = m_response.httpStatusCode(); + if (!SOUP_STATUS_IS_REDIRECTION(status)) + return false; + + // Some 3xx status codes aren't actually redirects. + if (status == 300 || status == 304 || status == 305 || status == 306) + return false; + + if (m_response.httpHeaderField(HTTPHeaderName::Location).isEmpty()) + return false; + + return true; +} + +void NetworkDataTaskSoup::continueHTTPRedirection() +{ + ASSERT(m_soupMessage); + ASSERT(!m_response.isNull()); + + static const unsigned maxRedirects = 20; + if (m_redirectCount++ > maxRedirects) { + didFail(ResourceError::transportError(m_soupRequest.get(), SOUP_STATUS_TOO_MANY_REDIRECTS, "Too many redirects")); + return; + } + + ResourceRequest request = m_currentRequest; + URL redirectedURL = URL(m_response.url(), m_response.httpHeaderField(HTTPHeaderName::Location)); + if (!redirectedURL.hasFragmentIdentifier() && request.url().hasFragmentIdentifier()) + redirectedURL.setFragmentIdentifier(request.url().fragmentIdentifier()); + request.setURL(redirectedURL); + + // Should not set Referer after a redirect from a secure resource to non-secure one. + if (m_shouldClearReferrerOnHTTPSToHTTPRedirect && !request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https")) + request.clearHTTPReferrer(); + + bool isCrossOrigin = !protocolHostAndPortAreEqual(m_currentRequest.url(), request.url()); + if (!equalLettersIgnoringASCIICase(request.httpMethod(), "get")) { + // Change newRequest method to GET if change was made during a previous redirection or if current redirection says so. + if (m_soupMessage->method == SOUP_METHOD_GET || !request.url().protocolIsInHTTPFamily() || shouldRedirectAsGET(m_soupMessage.get(), isCrossOrigin)) { + request.setHTTPMethod("GET"); + request.setHTTPBody(nullptr); + request.clearHTTPContentType(); + } + } + + const auto& url = request.url(); + m_user = url.user(); + m_password = url.pass(); + m_lastHTTPMethod = request.httpMethod(); + request.removeCredentials(); + + if (isCrossOrigin) { + // The network layer might carry over some headers from the original request that + // we want to strip here because the redirect is cross-origin. + request.clearHTTPAuthorization(); + request.clearHTTPOrigin(); + } else if (url.protocolIsInHTTPFamily() && m_storedCredentials == AllowStoredCredentials) { + if (m_user.isEmpty() && m_password.isEmpty()) { + auto credential = m_session->networkStorageSession().credentialStorage().get(m_partition, request.url()); + if (!credential.isEmpty()) + m_initialCredential = credential; + } + } + + clearRequest(); + + auto response = ResourceResponse(m_response); + m_client->willPerformHTTPRedirection(WTFMove(response), WTFMove(request), [this, protectedThis = makeRef(*this), isCrossOrigin](const ResourceRequest& newRequest) { + if (newRequest.isNull() || m_state == State::Canceling) + return; + + auto request = newRequest; + if (request.url().protocolIsInHTTPFamily()) { +#if ENABLE(WEB_TIMING) + if (isCrossOrigin) + m_startTime = monotonicallyIncreasingTimeMS(); +#endif + applyAuthenticationToRequest(request); + } + createRequest(WTFMove(request)); + if (m_soupRequest && m_state != State::Suspended) { + m_state = State::Suspended; + resume(); + } + }); +} + +void NetworkDataTaskSoup::readCallback(GInputStream* inputStream, GAsyncResult* result, NetworkDataTaskSoup* task) +{ + RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task); + if (task->state() == State::Canceling || task->state() == State::Completed || (!task->m_client && !task->isDownload())) { + task->clearRequest(); + return; + } + ASSERT(inputStream == task->m_inputStream.get()); + + if (task->state() == State::Suspended) { + ASSERT(!task->m_pendingResult); + task->m_pendingResult = result; + return; + } + + GUniqueOutPtr<GError> error; + gssize bytesRead = g_input_stream_read_finish(inputStream, result, &error.outPtr()); + if (error) + task->didFail(ResourceError::genericGError(error.get(), task->m_soupRequest.get())); + else if (bytesRead > 0) + task->didRead(bytesRead); + else + task->didFinishRead(); +} + +void NetworkDataTaskSoup::read() +{ + RefPtr<NetworkDataTaskSoup> protectedThis(this); + ASSERT(m_inputStream); + m_readBuffer.grow(gDefaultReadBufferSize); + g_input_stream_read_async(m_inputStream.get(), m_readBuffer.data(), m_readBuffer.size(), G_PRIORITY_DEFAULT, m_cancellable.get(), + reinterpret_cast<GAsyncReadyCallback>(readCallback), protectedThis.leakRef()); +} + +void NetworkDataTaskSoup::didRead(gssize bytesRead) +{ + m_readBuffer.shrink(bytesRead); + if (m_downloadOutputStream) { + ASSERT(isDownload()); + writeDownload(); + } else { + ASSERT(m_client); + m_client->didReceiveData(SharedBuffer::adoptVector(m_readBuffer)); + read(); + } +} + +void NetworkDataTaskSoup::didFinishRead() +{ + ASSERT(m_inputStream); + g_input_stream_close(m_inputStream.get(), nullptr, nullptr); + m_inputStream = nullptr; + if (m_multipartInputStream) { + requestNextPart(); + return; + } + + if (m_downloadOutputStream) { + didFinishDownload(); + return; + } + + clearRequest(); + ASSERT(m_client); + m_client->didCompleteWithError({ }); +} + +void NetworkDataTaskSoup::requestNextPartCallback(SoupMultipartInputStream* multipartInputStream, GAsyncResult* result, NetworkDataTaskSoup* task) +{ + RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task); + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + ASSERT(multipartInputStream == task->m_multipartInputStream.get()); + + if (task->state() == State::Suspended) { + ASSERT(!task->m_pendingResult); + task->m_pendingResult = result; + return; + } + + GUniqueOutPtr<GError> error; + GRefPtr<GInputStream> inputStream = adoptGRef(soup_multipart_input_stream_next_part_finish(multipartInputStream, result, &error.outPtr())); + if (error) + task->didFail(ResourceError::httpError(task->m_soupMessage.get(), error.get(), task->m_soupRequest.get())); + else if (inputStream) + task->didRequestNextPart(WTFMove(inputStream)); + else + task->didFinishRequestNextPart(); +} + +void NetworkDataTaskSoup::requestNextPart() +{ + RefPtr<NetworkDataTaskSoup> protectedThis(this); + ASSERT(m_multipartInputStream); + ASSERT(!m_inputStream); + soup_multipart_input_stream_next_part_async(m_multipartInputStream.get(), G_PRIORITY_DEFAULT, m_cancellable.get(), + reinterpret_cast<GAsyncReadyCallback>(requestNextPartCallback), protectedThis.leakRef()); +} + +void NetworkDataTaskSoup::didRequestNextPart(GRefPtr<GInputStream>&& inputStream) +{ + ASSERT(!m_inputStream); + m_inputStream = WTFMove(inputStream); + m_response = ResourceResponse(); + m_response.setURL(m_firstRequest.url()); + m_response.updateFromSoupMessageHeaders(soup_multipart_input_stream_get_headers(m_multipartInputStream.get())); + dispatchDidReceiveResponse(); +} + +void NetworkDataTaskSoup::didFinishRequestNextPart() +{ + ASSERT(!m_inputStream); + ASSERT(m_multipartInputStream); + g_input_stream_close(G_INPUT_STREAM(m_multipartInputStream.get()), nullptr, nullptr); + clearRequest(); + m_client->didCompleteWithError({ }); +} + +void NetworkDataTaskSoup::gotHeadersCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task) +{ + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + ASSERT(task->m_soupMessage.get() == soupMessage); + task->didGetHeaders(); +} + +void NetworkDataTaskSoup::didGetHeaders() +{ + // We are a bit more conservative with the persistent credential storage than the session store, + // since we are waiting until we know that this authentication succeeded before actually storing. + // This is because we want to avoid hitting the disk twice (once to add and once to remove) for + // incorrect credentials or polluting the keychain with invalid credentials. + if (!isAuthenticationFailureStatusCode(m_soupMessage->status_code) && m_soupMessage->status_code < 500) { + m_session->networkStorageSession().saveCredentialToPersistentStorage(m_protectionSpaceForPersistentStorage, m_credentialForPersistentStorage); + m_protectionSpaceForPersistentStorage = ProtectionSpace(); + m_credentialForPersistentStorage = Credential(); + } +} + +void NetworkDataTaskSoup::wroteBodyDataCallback(SoupMessage* soupMessage, SoupBuffer* buffer, NetworkDataTaskSoup* task) +{ + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) { + task->clearRequest(); + return; + } + ASSERT(task->m_soupMessage.get() == soupMessage); + task->didWriteBodyData(buffer->length); +} + +void NetworkDataTaskSoup::didWriteBodyData(uint64_t bytesSent) +{ + RefPtr<NetworkDataTaskSoup> protectedThis(this); + m_bodyDataTotalBytesSent += bytesSent; + m_client->didSendData(m_bodyDataTotalBytesSent, m_soupMessage->request_body->length); +} + +void NetworkDataTaskSoup::download() +{ + ASSERT(isDownload()); + ASSERT(m_pendingDownloadLocation); + ASSERT(!m_response.isNull()); + + if (m_response.httpStatusCode() >= 400) { + didFailDownload(platformDownloadNetworkError(m_response.httpStatusCode(), m_response.url(), m_response.httpStatusText())); + return; + } + + CString downloadDestinationPath = m_pendingDownloadLocation.utf8(); + m_downloadDestinationFile = adoptGRef(g_file_new_for_path(downloadDestinationPath.data())); + GRefPtr<GFileOutputStream> outputStream; + GUniqueOutPtr<GError> error; + if (m_allowOverwriteDownload) + outputStream = adoptGRef(g_file_replace(m_downloadDestinationFile.get(), nullptr, FALSE, G_FILE_CREATE_NONE, nullptr, &error.outPtr())); + else + outputStream = adoptGRef(g_file_create(m_downloadDestinationFile.get(), G_FILE_CREATE_NONE, nullptr, &error.outPtr())); + if (!outputStream) { + didFailDownload(platformDownloadDestinationError(m_response, error->message)); + return; + } + + GUniquePtr<char> intermediatePath(g_strdup_printf("%s.wkdownload", downloadDestinationPath.data())); + m_downloadIntermediateFile = adoptGRef(g_file_new_for_path(intermediatePath.get())); + outputStream = adoptGRef(g_file_replace(m_downloadIntermediateFile.get(), nullptr, TRUE, G_FILE_CREATE_NONE, nullptr, &error.outPtr())); + if (!outputStream) { + didFailDownload(platformDownloadDestinationError(m_response, error->message)); + return; + } + m_downloadOutputStream = adoptGRef(G_OUTPUT_STREAM(outputStream.leakRef())); + + auto& downloadManager = NetworkProcess::singleton().downloadManager(); + auto download = std::make_unique<Download>(downloadManager, m_pendingDownloadID, *this, m_session->sessionID(), suggestedFilename()); + auto* downloadPtr = download.get(); + downloadManager.dataTaskBecameDownloadTask(m_pendingDownloadID, WTFMove(download)); + downloadPtr->didCreateDestination(m_pendingDownloadLocation); + + ASSERT(!m_client); + read(); +} + +void NetworkDataTaskSoup::writeDownloadCallback(GOutputStream* outputStream, GAsyncResult* result, NetworkDataTaskSoup* task) +{ + RefPtr<NetworkDataTaskSoup> protectedThis = adoptRef(task); + if (task->state() == State::Canceling || task->state() == State::Completed || !task->isDownload()) { + task->clearRequest(); + return; + } + ASSERT(outputStream == task->m_downloadOutputStream.get()); + + GUniqueOutPtr<GError> error; + gsize bytesWritten; +#if GLIB_CHECK_VERSION(2, 44, 0) + g_output_stream_write_all_finish(outputStream, result, &bytesWritten, &error.outPtr()); +#else + gssize writeTaskResult = g_task_propagate_int(G_TASK(result), &error.outPtr()); + if (writeTaskResult != -1) + bytesWritten = writeTaskResult; +#endif + if (error) + task->didFailDownload(platformDownloadDestinationError(task->m_response, error->message)); + else + task->didWriteDownload(bytesWritten); +} + +void NetworkDataTaskSoup::writeDownload() +{ + RefPtr<NetworkDataTaskSoup> protectedThis(this); +#if GLIB_CHECK_VERSION(2, 44, 0) + g_output_stream_write_all_async(m_downloadOutputStream.get(), m_readBuffer.data(), m_readBuffer.size(), G_PRIORITY_DEFAULT, m_cancellable.get(), + reinterpret_cast<GAsyncReadyCallback>(writeDownloadCallback), protectedThis.leakRef()); +#else + GRefPtr<GTask> writeTask = adoptGRef(g_task_new(m_downloadOutputStream.get(), m_cancellable.get(), + reinterpret_cast<GAsyncReadyCallback>(writeDownloadCallback), protectedThis.leakRef())); + g_task_set_task_data(writeTask.get(), this, nullptr); + g_task_run_in_thread(writeTask.get(), [](GTask* writeTask, gpointer source, gpointer userData, GCancellable* cancellable) { + auto* task = static_cast<NetworkDataTaskSoup*>(userData); + GOutputStream* outputStream = G_OUTPUT_STREAM(source); + RELEASE_ASSERT(task->m_downloadOutputStream.get() == outputStream); + RELEASE_ASSERT(task->m_cancellable.get() == cancellable); + GError* error = nullptr; + if (g_cancellable_set_error_if_cancelled(cancellable, &error)) { + g_task_return_error(writeTask, error); + return; + } + + gsize bytesWritten; + if (g_output_stream_write_all(outputStream, task->m_readBuffer.data(), task->m_readBuffer.size(), &bytesWritten, cancellable, &error)) + g_task_return_int(writeTask, bytesWritten); + else + g_task_return_error(writeTask, error); + }); +#endif +} + +void NetworkDataTaskSoup::didWriteDownload(gsize bytesWritten) +{ + ASSERT(bytesWritten == m_readBuffer.size()); + auto* download = NetworkProcess::singleton().downloadManager().download(m_pendingDownloadID); + ASSERT(download); + download->didReceiveData(bytesWritten); + read(); +} + +void NetworkDataTaskSoup::didFinishDownload() +{ + ASSERT(!m_response.isNull()); + ASSERT(m_downloadOutputStream); + g_output_stream_close(m_downloadOutputStream.get(), nullptr, nullptr); + m_downloadOutputStream = nullptr; + + ASSERT(m_downloadDestinationFile); + ASSERT(m_downloadIntermediateFile); + GUniqueOutPtr<GError> error; + if (!g_file_move(m_downloadIntermediateFile.get(), m_downloadDestinationFile.get(), G_FILE_COPY_OVERWRITE, m_cancellable.get(), nullptr, nullptr, &error.outPtr())) { + didFailDownload(platformDownloadDestinationError(m_response, error->message)); + return; + } + + GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new()); + CString uri = m_response.url().string().utf8(); + g_file_info_set_attribute_string(info.get(), "metadata::download-uri", uri.data()); + g_file_info_set_attribute_string(info.get(), "xattr::xdg.origin.url", uri.data()); + g_file_set_attributes_async(m_downloadDestinationFile.get(), info.get(), G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, nullptr, nullptr, nullptr); + + clearRequest(); + auto* download = NetworkProcess::singleton().downloadManager().download(m_pendingDownloadID); + ASSERT(download); + download->didFinish(); +} + +void NetworkDataTaskSoup::didFailDownload(const ResourceError& error) +{ + clearRequest(); + cleanDownloadFiles(); + if (m_client) + m_client->didCompleteWithError(error); + else { + auto* download = NetworkProcess::singleton().downloadManager().download(m_pendingDownloadID); + ASSERT(download); + download->didFail(error, IPC::DataReference()); + } +} + +void NetworkDataTaskSoup::cleanDownloadFiles() +{ + if (m_downloadDestinationFile) { + g_file_delete(m_downloadDestinationFile.get(), nullptr, nullptr); + m_downloadDestinationFile = nullptr; + } + if (m_downloadIntermediateFile) { + g_file_delete(m_downloadIntermediateFile.get(), nullptr, nullptr); + m_downloadIntermediateFile = nullptr; + } +} + +void NetworkDataTaskSoup::didFail(const ResourceError& error) +{ + if (isDownload()) { + didFailDownload(platformDownloadNetworkError(error.errorCode(), error.failingURL(), error.localizedDescription())); + return; + } + + clearRequest(); + ASSERT(m_client); + m_client->didCompleteWithError(error); +} + +#if ENABLE(WEB_TIMING) +void NetworkDataTaskSoup::networkEventCallback(SoupMessage* soupMessage, GSocketClientEvent event, GIOStream*, NetworkDataTaskSoup* task) +{ + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) + return; + + ASSERT(task->m_soupMessage.get() == soupMessage); + task->networkEvent(event); +} + +void NetworkDataTaskSoup::networkEvent(GSocketClientEvent event) +{ + double deltaTime = monotonicallyIncreasingTimeMS() - m_startTime; + auto& loadTiming = m_response.networkLoadTiming(); + switch (event) { + case G_SOCKET_CLIENT_RESOLVING: + loadTiming.domainLookupStart = deltaTime; + break; + case G_SOCKET_CLIENT_RESOLVED: + loadTiming.domainLookupEnd = deltaTime; + break; + case G_SOCKET_CLIENT_CONNECTING: + loadTiming.connectStart = deltaTime; + break; + case G_SOCKET_CLIENT_CONNECTED: + // Web Timing considers that connection time involves dns, proxy & TLS negotiation... + // so we better pick G_SOCKET_CLIENT_COMPLETE for connectEnd + break; + case G_SOCKET_CLIENT_PROXY_NEGOTIATING: + break; + case G_SOCKET_CLIENT_PROXY_NEGOTIATED: + break; + case G_SOCKET_CLIENT_TLS_HANDSHAKING: + loadTiming.secureConnectionStart = deltaTime; + break; + case G_SOCKET_CLIENT_TLS_HANDSHAKED: + break; + case G_SOCKET_CLIENT_COMPLETE: + loadTiming.connectEnd = deltaTime; + break; + default: + ASSERT_NOT_REACHED(); + break; + } +} + +#if SOUP_CHECK_VERSION(2, 49, 91) +void NetworkDataTaskSoup::startingCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task) +{ + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) + return; + + ASSERT(task->m_soupMessage.get() == soupMessage); + task->didStartRequest(); +} +#else +void NetworkDataTaskSoup::requestStartedCallback(SoupSession* session, SoupMessage* soupMessage, SoupSocket*, NetworkDataTaskSoup* task) +{ + ASSERT(session == static_cast<NetworkSessionSoup&>(task->m_session.get()).soupSession()); + if (soupMessage != task->m_soupMessage.get()) + return; + + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) + return; + + task->didStartRequest(); +} +#endif + +void NetworkDataTaskSoup::didStartRequest() +{ + m_response.networkLoadTiming().requestStart = monotonicallyIncreasingTimeMS() - m_startTime; +} + +void NetworkDataTaskSoup::restartedCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task) +{ + // Called each time the message is going to be sent again except the first time. + // This happens when libsoup handles HTTP authentication. + if (task->state() == State::Canceling || task->state() == State::Completed || !task->m_client) + return; + + ASSERT(task->m_soupMessage.get() == soupMessage); + task->didRestart(); +} + +void NetworkDataTaskSoup::didRestart() +{ + m_startTime = monotonicallyIncreasingTimeMS(); +} +#endif // ENABLE(WEB_TIMING) + +} // namespace WebKit + diff --git a/Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.h b/Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.h new file mode 100644 index 000000000..40ca87c85 --- /dev/null +++ b/Source/WebKit2/NetworkProcess/soup/NetworkDataTaskSoup.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2016 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "NetworkDataTask.h" +#include <WebCore/ProtectionSpace.h> +#include <WebCore/ResourceResponse.h> +#include <wtf/RunLoop.h> +#include <wtf/glib/GRefPtr.h> + +namespace WebKit { + +class NetworkDataTaskSoup final : public NetworkDataTask { +public: + static Ref<NetworkDataTask> create(NetworkSession& session, NetworkDataTaskClient& client, const WebCore::ResourceRequest& request, WebCore::StoredCredentials storedCredentials, WebCore::ContentSniffingPolicy shouldContentSniff, bool shouldClearReferrerOnHTTPSToHTTPRedirect) + { + return adoptRef(*new NetworkDataTaskSoup(session, client, request, storedCredentials, shouldContentSniff, shouldClearReferrerOnHTTPSToHTTPRedirect)); + } + + ~NetworkDataTaskSoup(); + +private: + NetworkDataTaskSoup(NetworkSession&, NetworkDataTaskClient&, const WebCore::ResourceRequest&, WebCore::StoredCredentials, WebCore::ContentSniffingPolicy, bool shouldClearReferrerOnHTTPSToHTTPRedirect); + + void suspend() override; + void cancel() override; + void resume() override; + void invalidateAndCancel() override; + NetworkDataTask::State state() const override; + + void setPendingDownloadLocation(const String&, const SandboxExtension::Handle&, bool /*allowOverwrite*/) override; + String suggestedFilename() const override; + + void timeoutFired(); + void startTimeout(); + void stopTimeout(); + + void createRequest(WebCore::ResourceRequest&&); + void clearRequest(); + static void sendRequestCallback(SoupRequest*, GAsyncResult*, NetworkDataTaskSoup*); + void didSendRequest(GRefPtr<GInputStream>&&); + void dispatchDidReceiveResponse(); + + static void tlsErrorsChangedCallback(SoupMessage*, GParamSpec*, NetworkDataTaskSoup*); + void tlsErrorsChanged(); + + void applyAuthenticationToRequest(WebCore::ResourceRequest&); + static void authenticateCallback(SoupSession*, SoupMessage*, SoupAuth*, gboolean retrying, NetworkDataTaskSoup*); + void authenticate(WebCore::AuthenticationChallenge&&); + void continueAuthenticate(WebCore::AuthenticationChallenge&&); + + static void skipInputStreamForRedirectionCallback(GInputStream*, GAsyncResult*, NetworkDataTaskSoup*); + void skipInputStreamForRedirection(); + void didFinishSkipInputStreamForRedirection(); + bool shouldStartHTTPRedirection(); + void continueHTTPRedirection(); + + static void readCallback(GInputStream*, GAsyncResult*, NetworkDataTaskSoup*); + void read(); + void didRead(gssize bytesRead); + void didFinishRead(); + + static void requestNextPartCallback(SoupMultipartInputStream*, GAsyncResult*, NetworkDataTaskSoup*); + void requestNextPart(); + void didRequestNextPart(GRefPtr<GInputStream>&&); + void didFinishRequestNextPart(); + + static void gotHeadersCallback(SoupMessage*, NetworkDataTaskSoup*); + void didGetHeaders(); + + static void wroteBodyDataCallback(SoupMessage*, SoupBuffer*, NetworkDataTaskSoup*); + void didWriteBodyData(uint64_t bytesSent); + + void download(); + static void writeDownloadCallback(GOutputStream*, GAsyncResult*, NetworkDataTaskSoup*); + void writeDownload(); + void didWriteDownload(gsize bytesWritten); + void didFailDownload(const WebCore::ResourceError&); + void didFinishDownload(); + void cleanDownloadFiles(); + + void didFail(const WebCore::ResourceError&); + +#if ENABLE(WEB_TIMING) + static void networkEventCallback(SoupMessage*, GSocketClientEvent, GIOStream*, NetworkDataTaskSoup*); + void networkEvent(GSocketClientEvent); +#if SOUP_CHECK_VERSION(2, 49, 91) + static void startingCallback(SoupMessage*, NetworkDataTaskSoup*); +#else + static void requestStartedCallback(SoupSession*, SoupMessage*, SoupSocket*, NetworkDataTaskSoup*); +#endif + void didStartRequest(); + static void restartedCallback(SoupMessage*, NetworkDataTaskSoup*); + void didRestart(); +#endif // ENABLE(WEB_TIMING) + + State m_state { State::Suspended }; + WebCore::ContentSniffingPolicy m_shouldContentSniff; + GRefPtr<SoupRequest> m_soupRequest; + GRefPtr<SoupMessage> m_soupMessage; + GRefPtr<GInputStream> m_inputStream; + GRefPtr<SoupMultipartInputStream> m_multipartInputStream; + GRefPtr<GCancellable> m_cancellable; + GRefPtr<GAsyncResult> m_pendingResult; + WebCore::ProtectionSpace m_protectionSpaceForPersistentStorage; + WebCore::Credential m_credentialForPersistentStorage; + WebCore::ResourceRequest m_currentRequest; + WebCore::ResourceResponse m_response; + Vector<char> m_readBuffer; + unsigned m_redirectCount { 0 }; + uint64_t m_bodyDataTotalBytesSent { 0 }; + GRefPtr<GFile> m_downloadDestinationFile; + GRefPtr<GFile> m_downloadIntermediateFile; + GRefPtr<GOutputStream> m_downloadOutputStream; + bool m_allowOverwriteDownload { false }; +#if ENABLE(WEB_TIMING) + double m_startTime { 0 }; +#endif + RunLoop::Timer<NetworkDataTaskSoup> m_timeoutSource; +}; + +} // namespace WebKit diff --git a/Source/WebKit2/NetworkProcess/soup/NetworkProcessMainSoup.cpp b/Source/WebKit2/NetworkProcess/soup/NetworkProcessMainSoup.cpp new file mode 100644 index 000000000..d5081acd3 --- /dev/null +++ b/Source/WebKit2/NetworkProcess/soup/NetworkProcessMainSoup.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 Igalia S.L. + * Copyright (C) 2013 Company 100 Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "NetworkProcess.h" + +#include "ChildProcessMain.h" +#include "NetworkProcessMainUnix.h" + +namespace WebKit { + +int NetworkProcessMainUnix(int argc, char** argv) +{ + return ChildProcessMain<NetworkProcess, ChildProcessMainBase>(argc, argv); +} + +} // namespace WebKit diff --git a/Source/WebKit2/NetworkProcess/soup/NetworkProcessSoup.cpp b/Source/WebKit2/NetworkProcess/soup/NetworkProcessSoup.cpp index 5730ca61c..d8dcde330 100644 --- a/Source/WebKit2/NetworkProcess/soup/NetworkProcessSoup.cpp +++ b/Source/WebKit2/NetworkProcess/soup/NetworkProcessSoup.cpp @@ -25,72 +25,100 @@ */ #include "config.h" -#if ENABLE(NETWORK_PROCESS) #include "NetworkProcess.h" +#include "NetworkCache.h" #include "NetworkProcessCreationParameters.h" #include "ResourceCachesToClear.h" #include "WebCookieManager.h" #include <WebCore/CertificateInfo.h> #include <WebCore/FileSystem.h> +#include <WebCore/NetworkStorageSession.h> #include <WebCore/NotImplemented.h> #include <WebCore/ResourceHandle.h> #include <WebCore/SoupNetworkSession.h> #include <libsoup/soup.h> -#include <wtf/gobject/GRefPtr.h> -#include <wtf/gobject/GUniquePtr.h> +#include <wtf/RAMSize.h> +#include <wtf/glib/GRefPtr.h> +#include <wtf/glib/GUniquePtr.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> using namespace WebCore; namespace WebKit { -static uint64_t getCacheDiskFreeSize(SoupCache* cache) +static CString buildAcceptLanguages(const Vector<String>& languages) { - ASSERT(cache); - - GUniqueOutPtr<char> cacheDir; - g_object_get(G_OBJECT(cache), "cache-dir", &cacheDir.outPtr(), NULL); - if (!cacheDir) - return 0; - - return WebCore::getVolumeFreeSizeForPath(cacheDir.get()); -} + size_t languagesCount = languages.size(); + + // Ignore "C" locale. + size_t cLocalePosition = languages.find("c"); + if (cLocalePosition != notFound) + languagesCount--; + + // Fallback to "en" if the list is empty. + if (!languagesCount) + return "en"; + + // Calculate deltas for the quality values. + int delta; + if (languagesCount < 10) + delta = 10; + else if (languagesCount < 20) + delta = 5; + else + delta = 1; + + // Set quality values for each language. + StringBuilder builder; + for (size_t i = 0; i < languages.size(); ++i) { + if (i == cLocalePosition) + continue; + + if (i) + builder.appendLiteral(", "); + + builder.append(languages[i]); + + int quality = 100 - i * delta; + if (quality > 0 && quality < 100) { + char buffer[8]; + g_ascii_formatd(buffer, 8, "%.2f", quality / 100.0); + builder.append(String::format(";q=%s", buffer)); + } + } -static uint64_t getMemorySize() -{ - static uint64_t kDefaultMemorySize = 512; -#if !OS(WINDOWS) - long pageSize = sysconf(_SC_PAGESIZE); - if (pageSize == -1) - return kDefaultMemorySize; - - long physPages = sysconf(_SC_PHYS_PAGES); - if (physPages == -1) - return kDefaultMemorySize; - - return ((pageSize / 1024) * physPages) / 1024; -#else - // Fallback to default for other platforms. - return kDefaultMemorySize; -#endif + return builder.toString().utf8(); } void NetworkProcess::userPreferredLanguagesChanged(const Vector<String>& languages) { - SoupNetworkSession::defaultSession().setAcceptLanguages(languages); + auto acceptLanguages = buildAcceptLanguages(languages); + SoupNetworkSession::setInitialAcceptLanguages(acceptLanguages); + NetworkStorageSession::forEach([&acceptLanguages](const WebCore::NetworkStorageSession& session) { + if (auto* soupSession = session.soupNetworkSession()) + soupSession->setAcceptLanguages(acceptLanguages); + }); } void NetworkProcess::platformInitializeNetworkProcess(const NetworkProcessCreationParameters& parameters) { + if (parameters.proxySettings.mode != SoupNetworkProxySettings::Mode::Default) + setNetworkProxySettings(parameters.proxySettings); + ASSERT(!parameters.diskCacheDirectory.isEmpty()); - GRefPtr<SoupCache> soupCache = adoptGRef(soup_cache_new(parameters.diskCacheDirectory.utf8().data(), SOUP_CACHE_SINGLE_USER)); - SoupNetworkSession::defaultSession().setCache(soupCache.get()); - // Set an initial huge max_size for the SoupCache so the call to soup_cache_load() won't evict any cached - // resource. The final size of the cache will be set by NetworkProcess::platformSetCacheModel(). - unsigned initialMaxSize = soup_cache_get_max_size(soupCache.get()); - soup_cache_set_max_size(soupCache.get(), G_MAXUINT); - soup_cache_load(soupCache.get()); - soup_cache_set_max_size(soupCache.get(), initialMaxSize); + m_diskCacheDirectory = parameters.diskCacheDirectory; + + SoupNetworkSession::clearOldSoupCache(WebCore::directoryName(m_diskCacheDirectory)); + + NetworkCache::Cache::Parameters cacheParameters { + parameters.shouldEnableNetworkCacheEfficacyLogging +#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION) + , parameters.shouldEnableNetworkCacheSpeculativeRevalidation +#endif + }; + NetworkCache::singleton().initialize(m_diskCacheDirectory, cacheParameters); if (!parameters.cookiePersistentStoragePath.isEmpty()) { supplement<WebCookieManager>()->setCookiePersistentStorage(parameters.cookiePersistentStoragePath, @@ -104,37 +132,18 @@ void NetworkProcess::platformInitializeNetworkProcess(const NetworkProcessCreati setIgnoreTLSErrors(parameters.ignoreTLSErrors); } -void NetworkProcess::platformSetCacheModel(CacheModel cacheModel) +void NetworkProcess::platformSetURLCacheSize(unsigned, uint64_t) { - unsigned cacheTotalCapacity = 0; - unsigned cacheMinDeadCapacity = 0; - unsigned cacheMaxDeadCapacity = 0; - double deadDecodedDataDeletionInterval = 0; - unsigned pageCacheCapacity = 0; - - unsigned long urlCacheMemoryCapacity = 0; - unsigned long urlCacheDiskCapacity = 0; - - SoupCache* cache = SoupNetworkSession::defaultSession().cache(); - uint64_t diskFreeSize = getCacheDiskFreeSize(cache) / 1024 / 1024; - - uint64_t memSize = getMemorySize(); - calculateCacheSizes(cacheModel, memSize, diskFreeSize, - cacheTotalCapacity, cacheMinDeadCapacity, cacheMaxDeadCapacity, deadDecodedDataDeletionInterval, - pageCacheCapacity, urlCacheMemoryCapacity, urlCacheDiskCapacity); - - if (urlCacheDiskCapacity > soup_cache_get_max_size(cache)) - soup_cache_set_max_size(cache, urlCacheDiskCapacity); } void NetworkProcess::setIgnoreTLSErrors(bool ignoreTLSErrors) { - ResourceHandle::setIgnoreSSLErrors(ignoreTLSErrors); + SoupNetworkSession::setShouldIgnoreTLSErrors(ignoreTLSErrors); } void NetworkProcess::allowSpecificHTTPSCertificateForHost(const CertificateInfo& certificateInfo, const String& host) { - ResourceHandle::setClientCertificate(host, certificateInfo.certificate()); + SoupNetworkSession::allowSpecificHTTPSCertificateForHost(certificateInfo, host); } void NetworkProcess::clearCacheForAllOrigins(uint32_t cachesToClear) @@ -142,7 +151,12 @@ void NetworkProcess::clearCacheForAllOrigins(uint32_t cachesToClear) if (cachesToClear == InMemoryResourceCachesOnly) return; - soup_cache_clear(SoupNetworkSession::defaultSession().cache()); + clearDiskCache(std::chrono::system_clock::time_point::min(), [] { }); +} + +void NetworkProcess::clearDiskCache(std::chrono::system_clock::time_point modifiedSince, std::function<void ()> completionHandler) +{ + NetworkCache::singleton().clear(modifiedSince, WTFMove(completionHandler)); } void NetworkProcess::platformTerminate() @@ -150,6 +164,13 @@ void NetworkProcess::platformTerminate() notImplemented(); } -} // namespace WebKit +void NetworkProcess::setNetworkProxySettings(const SoupNetworkProxySettings& settings) +{ + SoupNetworkSession::setProxySettings(settings); + NetworkStorageSession::forEach([](const NetworkStorageSession& session) { + if (auto* soupSession = session.soupNetworkSession()) + soupSession->setupProxy(); + }); +} -#endif +} // namespace WebKit diff --git a/Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.cpp b/Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.cpp new file mode 100644 index 000000000..f6baedac1 --- /dev/null +++ b/Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "NetworkSessionSoup.h" + +#include "NetworkProcess.h" +#include "WebCookieManager.h" +#include <WebCore/NetworkStorageSession.h> +#include <WebCore/SoupNetworkSession.h> +#include <libsoup/soup.h> + +using namespace WebCore; + +namespace WebKit { + +NetworkSessionSoup::NetworkSessionSoup(SessionID sessionID) + : NetworkSession(sessionID) +{ + networkStorageSession().setCookieObserverHandler([this] { + NetworkProcess::singleton().supplement<WebCookieManager>()->notifyCookiesDidChange(m_sessionID); + }); +} + +NetworkSessionSoup::~NetworkSessionSoup() +{ + if (auto* storageSession = NetworkStorageSession::storageSession(m_sessionID)) + storageSession->setCookieObserverHandler(nullptr); +} + +SoupSession* NetworkSessionSoup::soupSession() const +{ + return networkStorageSession().getOrCreateSoupNetworkSession().soupSession(); +} + +void NetworkSessionSoup::clearCredentials() +{ +#if SOUP_CHECK_VERSION(2, 57, 1) + soup_auth_manager_clear_cached_credentials(SOUP_AUTH_MANAGER(soup_session_get_feature(soupSession(), SOUP_TYPE_AUTH_MANAGER))); +#endif +} + +} // namespace WebKit diff --git a/Source/WebKit2/NetworkProcess/soup/NetworkResourceLoadSchedulerSoup.cpp b/Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.h index 9d03979e3..e0196029a 100644 --- a/Source/WebKit2/NetworkProcess/soup/NetworkResourceLoadSchedulerSoup.cpp +++ b/Source/WebKit2/NetworkProcess/soup/NetworkSessionSoup.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. - * Copyright (C) 2013 Company 100 Inc. + * Copyright (C) 2016 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -24,26 +23,28 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#include "config.h" -#if ENABLE(NETWORK_PROCESS) +#pragma once -#include "NetworkResourceLoadScheduler.h" +#include "NetworkSession.h" + +typedef struct _SoupSession SoupSession; namespace WebKit { -void NetworkResourceLoadScheduler::platformInitializeMaximumHTTPConnectionCountPerHost() -{ - // Soup has its own queue control; it wants to have all requests given to - // it, so that it is able to look ahead, and schedule them in a good way. - // See the comment in ResourceRequestSoup.cpp - static const unsigned unlimitedConnectionCount = 10000; +class NetworkSessionSoup final : public NetworkSession { +public: + static Ref<NetworkSession> create(WebCore::SessionID sessionID) + { + return adoptRef(*new NetworkSessionSoup(sessionID)); + } + ~NetworkSessionSoup(); - // FIXME: Take advantage of Web-platform specific knowledge that can help - // prioritization better than libsoup alone can do. - // See https://bugs.webkit.org/show_bug.cgi?id=110115#c13 - m_maxRequestsInFlightPerHost = unlimitedConnectionCount; -} + SoupSession* soupSession() const; -} // namespace WebKit +private: + NetworkSessionSoup(WebCore::SessionID); -#endif + void clearCredentials() override; +}; + +} // namespace WebKit diff --git a/Source/WebKit2/NetworkProcess/soup/RemoteNetworkingContextSoup.cpp b/Source/WebKit2/NetworkProcess/soup/RemoteNetworkingContextSoup.cpp index d7cf86efb..2dba10521 100644 --- a/Source/WebKit2/NetworkProcess/soup/RemoteNetworkingContextSoup.cpp +++ b/Source/WebKit2/NetworkProcess/soup/RemoteNetworkingContextSoup.cpp @@ -26,9 +26,10 @@ */ #include "config.h" -#if ENABLE(NETWORK_PROCESS) #include "RemoteNetworkingContext.h" +#include "NetworkSession.h" +#include "SessionTracker.h" #include <WebCore/NetworkStorageSession.h> #include <WebCore/NotImplemented.h> #include <WebCore/ResourceHandle.h> @@ -46,16 +47,22 @@ bool RemoteNetworkingContext::isValid() const return true; } -void RemoteNetworkingContext::ensurePrivateBrowsingSession(uint64_t sessionID) +void RemoteNetworkingContext::ensurePrivateBrowsingSession(SessionID sessionID) { - notImplemented(); + ASSERT(sessionID.isEphemeral()); + + if (NetworkStorageSession::storageSession(sessionID)) + return; + + NetworkStorageSession::ensurePrivateBrowsingSession(sessionID, String::number(sessionID.sessionID())); + SessionTracker::setSession(sessionID, NetworkSession::create(sessionID)); } NetworkStorageSession& RemoteNetworkingContext::storageSession() const { + if (auto session = NetworkStorageSession::storageSession(m_sessionID)) + return *session; return NetworkStorageSession::defaultStorageSession(); } } - -#endif // ENABLE(NETWORK_PROCESS) |