NetworkDataTaskSoup.cpp [plain text]
#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>
#include <wtf/glib/RunLoopSourcePriority.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 = MonotonicTime::now();
#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;
}
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
soup_message_disable_feature(soupMessage.get(), SOUP_TYPE_AUTH_MANAGER);
#endif
}
if (!soup_message_headers_get_one(soupMessage->request_headers, "Accept"))
soup_message_headers_append(soupMessage->request_headers, "Accept", "*/*");
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();
dispatchDidCompleteWithError(ResourceError::timeoutError(m_firstRequest.url()));
}
void NetworkDataTaskSoup::startTimeout()
{
if (m_firstRequest.timeoutInterval() > 0)
m_timeoutSource.startOneShot(1_s * 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_networkLoadMetrics.responseStart = MonotonicTime::now() - 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());
#if ENABLE(WEB_TIMING)
NetworkLoadMetrics& deprecatedResponseMetrics = m_response.deprecatedNetworkLoadMetrics();
deprecatedResponseMetrics.responseStart = m_networkLoadMetrics.responseStart;
deprecatedResponseMetrics.domainLookupStart = m_networkLoadMetrics.domainLookupStart;
deprecatedResponseMetrics.domainLookupEnd = m_networkLoadMetrics.domainLookupEnd;
deprecatedResponseMetrics.connectStart = m_networkLoadMetrics.connectStart;
deprecatedResponseMetrics.secureConnectionStart = m_networkLoadMetrics.secureConnectionStart;
deprecatedResponseMetrics.connectEnd = m_networkLoadMetrics.connectEnd;
deprecatedResponseMetrics.requestStart = m_networkLoadMetrics.requestStart;
deprecatedResponseMetrics.responseStart = m_networkLoadMetrics.responseStart;
#endif
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::dispatchDidCompleteWithError(const ResourceError& error)
{
#if ENABLE(WEB_TIMING)
m_networkLoadMetrics.responseEnd = MonotonicTime::now() - m_startTime;
m_networkLoadMetrics.markComplete();
#endif
m_client->didCompleteWithError(error, m_networkLoadMetrics);
}
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();
dispatchDidCompleteWithError(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()) {
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())) {
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());
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) {
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, RunLoopSourcePriority::AsyncIONetwork, 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;
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);
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")) {
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) {
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 = MonotonicTime::now();
m_networkLoadMetrics.reset();
}
#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(), RunLoopSourcePriority::AsyncIONetwork, 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::create(WTFMove(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);
dispatchDidCompleteWithError({ });
}
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(), RunLoopSourcePriority::AsyncIONetwork, 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();
dispatchDidCompleteWithError({ });
}
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()
{
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();
}
#if ENABLE(WEB_TIMING)
if (shouldCaptureExtraNetworkLoadMetrics()) {
HTTPHeaderMap requestHeaders;
SoupMessageHeadersIter headersIter;
soup_message_headers_iter_init(&headersIter, m_soupMessage->request_headers);
const char* headerName;
const char* headerValue;
while (soup_message_headers_iter_next(&headersIter, &headerName, &headerValue))
requestHeaders.set(String(headerName), String(headerValue));
m_networkLoadMetrics.requestHeaders = WTFMove(requestHeaders);
}
#endif
}
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(downloadNetworkError(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(downloadDestinationError(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(downloadDestinationError(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(downloadDestinationError(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(), RunLoopSourcePriority::AsyncIONetwork, 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(downloadDestinationError(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, RunLoopSourcePriority::AsyncIONetwork, 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)
dispatchDidCompleteWithError(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(downloadNetworkError(error.failingURL(), error.localizedDescription()));
return;
}
clearRequest();
ASSERT(m_client);
dispatchDidCompleteWithError(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)
{
Seconds deltaTime = MonotonicTime::now() - m_startTime;
switch (event) {
case G_SOCKET_CLIENT_RESOLVING:
m_networkLoadMetrics.domainLookupStart = deltaTime;
break;
case G_SOCKET_CLIENT_RESOLVED:
m_networkLoadMetrics.domainLookupEnd = deltaTime;
break;
case G_SOCKET_CLIENT_CONNECTING:
m_networkLoadMetrics.connectStart = deltaTime;
break;
case G_SOCKET_CLIENT_CONNECTED:
break;
case G_SOCKET_CLIENT_PROXY_NEGOTIATING:
break;
case G_SOCKET_CLIENT_PROXY_NEGOTIATED:
break;
case G_SOCKET_CLIENT_TLS_HANDSHAKING:
m_networkLoadMetrics.secureConnectionStart = deltaTime;
break;
case G_SOCKET_CLIENT_TLS_HANDSHAKED:
break;
case G_SOCKET_CLIENT_COMPLETE:
m_networkLoadMetrics.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_networkLoadMetrics.requestStart = MonotonicTime::now() - m_startTime;
}
void NetworkDataTaskSoup::restartedCallback(SoupMessage* soupMessage, NetworkDataTaskSoup* task)
{
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 = MonotonicTime::now();
m_networkLoadMetrics.reset();
}
#endif // ENABLE(WEB_TIMING)
}