ResourceHandleSoup.cpp [plain text]
#include "config.h"
#include "ResourceHandle.h"
#if USE(SOUP)
#include "CredentialStorage.h"
#include "FileSystem.h"
#include "GUniquePtrSoup.h"
#include "HTTPParsers.h"
#include "LocalizedStrings.h"
#include "MIMETypeRegistry.h"
#include "NetworkStorageSession.h"
#include "NetworkingContext.h"
#include "ResourceError.h"
#include "ResourceHandleClient.h"
#include "ResourceHandleInternal.h"
#include "ResourceResponse.h"
#include "SharedBuffer.h"
#include "SoupNetworkSession.h"
#include "TextEncoding.h"
#include <errno.h>
#include <fcntl.h>
#include <gio/gio.h>
#include <glib.h>
#include <libsoup/soup.h>
#include <sys/stat.h>
#include <sys/types.h>
#if !COMPILER(MSVC)
#include <unistd.h>
#endif
#include <wtf/CompletionHandler.h>
#include <wtf/CurrentTime.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/RunLoopSourcePriority.h>
#include <wtf/text/CString.h>
namespace WebCore {
static const size_t gDefaultReadBufferSize = 8192;
static bool createSoupRequestAndMessageForHandle(ResourceHandle*, const ResourceRequest&);
static void cleanupSoupRequestOperation(ResourceHandle*, bool isDestroying = false);
static void sendRequestCallback(GObject*, GAsyncResult*, gpointer);
static void readCallback(GObject*, GAsyncResult*, gpointer);
static void continueAfterDidReceiveResponse(ResourceHandle*);
ResourceHandleInternal::~ResourceHandleInternal() = default;
static SoupSession* sessionFromContext(NetworkingContext* context)
{
if (!context || !context->isValid())
return NetworkStorageSession::defaultStorageSession().getOrCreateSoupNetworkSession().soupSession();
return context->storageSession().getOrCreateSoupNetworkSession().soupSession();
}
ResourceHandle::~ResourceHandle()
{
cleanupSoupRequestOperation(this, true);
}
SoupSession* ResourceHandleInternal::soupSession()
{
return m_session ? m_session->soupSession() : sessionFromContext(m_context.get());
}
RefPtr<ResourceHandle> ResourceHandle::create(SoupNetworkSession& session, const ResourceRequest& request, ResourceHandleClient* client, bool defersLoading, bool shouldContentSniff, bool shouldContentEncodingSniff)
{
auto newHandle = adoptRef(*new ResourceHandle(session, request, client, defersLoading, shouldContentSniff, shouldContentEncodingSniff));
if (newHandle->d->m_scheduledFailureType != NoFailure)
return WTFMove(newHandle);
if (newHandle->start())
return WTFMove(newHandle);
return nullptr;
}
ResourceHandle::ResourceHandle(SoupNetworkSession& session, const ResourceRequest& request, ResourceHandleClient* client, bool defersLoading, bool shouldContentSniff, bool shouldContentEncodingSniff)
: d(std::make_unique<ResourceHandleInternal>(this, nullptr, request, client, defersLoading, shouldContentSniff && shouldContentSniffURL(request.url()), shouldContentEncodingSniff))
{
if (!request.url().isValid()) {
scheduleFailure(InvalidURLFailure);
return;
}
if (!portAllowed(request.url())) {
scheduleFailure(BlockedFailure);
return;
}
d->m_session = &session;
}
bool ResourceHandle::cancelledOrClientless()
{
if (!client())
return true;
return getInternal()->m_cancelled;
}
void ResourceHandle::ensureReadBuffer()
{
ResourceHandleInternal* d = getInternal();
if (d->m_soupBuffer)
return;
auto* buffer = static_cast<uint8_t*>(fastMalloc(gDefaultReadBufferSize));
d->m_soupBuffer.reset(soup_buffer_new_with_owner(buffer, gDefaultReadBufferSize, buffer, fastFree));
ASSERT(d->m_soupBuffer);
}
static bool isAuthenticationFailureStatusCode(int httpStatusCode)
{
return httpStatusCode == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED || httpStatusCode == SOUP_STATUS_UNAUTHORIZED;
}
static void tlsErrorsChangedCallback(SoupMessage* message, GParamSpec*, gpointer data)
{
RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
if (!handle || handle->cancelledOrClientless())
return;
SoupNetworkSession::checkTLSErrors(handle->getInternal()->m_soupRequest.get(), message, [handle] (const ResourceError& error) {
if (error.isNull())
return;
handle->client()->didFail(handle.get(), error);
handle->cancel();
});
}
static void gotHeadersCallback(SoupMessage* message, gpointer data)
{
ResourceHandle* handle = static_cast<ResourceHandle*>(data);
if (!handle || handle->cancelledOrClientless())
return;
ResourceHandleInternal* d = handle->getInternal();
if (d->m_context && d->m_context->isValid()) {
if (!isAuthenticationFailureStatusCode(message->status_code) && message->status_code < 500) {
d->m_context->storageSession().saveCredentialToPersistentStorage(
d->m_credentialDataToSaveInPersistentStore.protectionSpace,
d->m_credentialDataToSaveInPersistentStore.credential);
}
}
d->m_response.updateFromSoupMessage(message);
}
static void applyAuthenticationToRequest(ResourceHandle* handle, ResourceRequest& request, bool redirect)
{
ResourceHandleInternal* d = handle->getInternal();
String partition = request.cachePartition();
if (handle->shouldUseCredentialStorage()) {
if (d->m_user.isEmpty() && d->m_pass.isEmpty())
d->m_initialCredential = CredentialStorage::defaultCredentialStorage().get(partition, request.url());
else if (!redirect) {
CredentialStorage::defaultCredentialStorage().set(partition, Credential(d->m_user, d->m_pass, CredentialPersistenceNone), request.url());
}
}
String user = d->m_user;
String password = d->m_pass;
if (!d->m_initialCredential.isEmpty()) {
user = d->m_initialCredential.user();
password = d->m_initialCredential.password();
}
if (user.isEmpty() && password.isEmpty()) {
d->m_useAuthenticationManager = handle->shouldUseCredentialStorage();
return;
}
URL urlWithCredentials(request.url());
urlWithCredentials.setUser(user);
urlWithCredentials.setPass(password);
request.setURL(urlWithCredentials);
}
static void restartedCallback(SoupMessage*, gpointer data)
{
ResourceHandle* handle = static_cast<ResourceHandle*>(data);
if (!handle || handle->cancelledOrClientless())
return;
handle->m_requestTime = MonotonicTime::now();
}
static bool shouldRedirect(ResourceHandle* handle)
{
ResourceHandleInternal* d = handle->getInternal();
SoupMessage* message = d->m_soupMessage.get();
if (message->status_code == 300 || message->status_code == 304 || message->status_code == 305 || message->status_code == 306)
return false;
if (!soup_message_headers_get_one(message->response_headers, "Location"))
return false;
return true;
}
static bool shouldRedirectAsGET(SoupMessage* message, URL& newURL, bool crossOrigin)
{
if (message->method == SOUP_METHOD_GET || message->method == SOUP_METHOD_HEAD)
return false;
if (!newURL.protocolIsInHTTPFamily())
return true;
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;
}
static void continueAfterWillSendRequest(ResourceHandle* handle, ResourceRequest&& request)
{
if (handle->cancelledOrClientless())
return;
ResourceHandleInternal* d = handle->getInternal();
if (protocolHostAndPortAreEqual(request.url(), d->m_response.url()))
applyAuthenticationToRequest(handle, request, true);
if (!createSoupRequestAndMessageForHandle(handle, request)) {
d->client()->cannotShowURL(handle);
return;
}
handle->sendPendingRequest();
}
static void doRedirect(ResourceHandle* handle)
{
ResourceHandleInternal* d = handle->getInternal();
static const int maxRedirects = 20;
if (d->m_redirectCount++ > maxRedirects) {
d->client()->didFail(handle, ResourceError::transportError(d->m_soupRequest.get(), SOUP_STATUS_TOO_MANY_REDIRECTS, "Too many redirects"));
cleanupSoupRequestOperation(handle);
return;
}
ResourceRequest newRequest = handle->firstRequest();
SoupMessage* message = d->m_soupMessage.get();
const char* location = soup_message_headers_get_one(message->response_headers, "Location");
URL newURL = URL(URL(soup_message_get_uri(message)), location);
bool crossOrigin = !protocolHostAndPortAreEqual(handle->firstRequest().url(), newURL);
newRequest.setURL(newURL);
if (newRequest.httpMethod() != "GET") {
if (message->method == SOUP_METHOD_GET || shouldRedirectAsGET(message, newURL, crossOrigin)) {
newRequest.setHTTPMethod("GET");
newRequest.setHTTPBody(nullptr);
newRequest.clearHTTPContentType();
}
}
if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
newRequest.clearHTTPReferrer();
d->m_user = newURL.user();
d->m_pass = newURL.pass();
newRequest.removeCredentials();
if (crossOrigin) {
newRequest.clearHTTPAuthorization();
newRequest.clearHTTPOrigin();
}
cleanupSoupRequestOperation(handle);
ResourceResponse responseCopy = d->m_response;
d->client()->willSendRequestAsync(handle, WTFMove(newRequest), WTFMove(responseCopy), [handle = makeRef(*handle)] (ResourceRequest&& request) {
continueAfterWillSendRequest(handle.ptr(), WTFMove(request));
});
}
static void redirectSkipCallback(GObject*, GAsyncResult* asyncResult, gpointer data)
{
RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
if (handle->cancelledOrClientless()) {
cleanupSoupRequestOperation(handle.get());
return;
}
GUniqueOutPtr<GError> error;
ResourceHandleInternal* d = handle->getInternal();
gssize bytesSkipped = g_input_stream_skip_finish(d->m_inputStream.get(), asyncResult, &error.outPtr());
if (error) {
handle->client()->didFail(handle.get(), ResourceError::genericGError(error.get(), d->m_soupRequest.get()));
cleanupSoupRequestOperation(handle.get());
return;
}
if (bytesSkipped > 0) {
g_input_stream_skip_async(d->m_inputStream.get(), gDefaultReadBufferSize, RunLoopSourcePriority::AsyncIONetwork,
d->m_cancellable.get(), redirectSkipCallback, handle.get());
return;
}
g_input_stream_close(d->m_inputStream.get(), 0, 0);
doRedirect(handle.get());
}
static void wroteBodyDataCallback(SoupMessage*, SoupBuffer* buffer, gpointer data)
{
RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
if (!handle)
return;
ASSERT(buffer);
ResourceHandleInternal* d = handle->getInternal();
d->m_bodyDataSent += buffer->length;
if (handle->cancelledOrClientless())
return;
handle->client()->didSendData(handle.get(), d->m_bodyDataSent, d->m_bodySize);
}
static void cleanupSoupRequestOperation(ResourceHandle* handle, bool isDestroying)
{
ResourceHandleInternal* d = handle->getInternal();
d->m_soupRequest.clear();
d->m_inputStream.clear();
d->m_multipartInputStream.clear();
d->m_cancellable.clear();
d->m_soupBuffer.reset();
if (d->m_soupMessage) {
g_signal_handlers_disconnect_matched(d->m_soupMessage.get(), G_SIGNAL_MATCH_DATA,
0, 0, 0, 0, handle);
g_object_set_data(G_OBJECT(d->m_soupMessage.get()), "handle", 0);
d->m_soupMessage.clear();
}
d->m_timeoutSource.stop();
if (!isDestroying)
handle->deref();
}
size_t ResourceHandle::currentStreamPosition() const
{
GInputStream* baseStream = d->m_inputStream.get();
while (!G_IS_SEEKABLE(baseStream) && G_IS_FILTER_INPUT_STREAM(baseStream))
baseStream = g_filter_input_stream_get_base_stream(G_FILTER_INPUT_STREAM(baseStream));
if (!G_IS_SEEKABLE(baseStream))
return 0;
return g_seekable_tell(G_SEEKABLE(baseStream));
}
static void nextMultipartResponsePartCallback(GObject* , GAsyncResult* result, gpointer data)
{
RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
if (handle->cancelledOrClientless()) {
cleanupSoupRequestOperation(handle.get());
return;
}
ResourceHandleInternal* d = handle->getInternal();
ASSERT(!d->m_inputStream);
GUniqueOutPtr<GError> error;
d->m_inputStream = adoptGRef(soup_multipart_input_stream_next_part_finish(d->m_multipartInputStream.get(), result, &error.outPtr()));
if (error) {
handle->client()->didFail(handle.get(), ResourceError::httpError(d->m_soupMessage.get(), error.get(), d->m_soupRequest.get()));
cleanupSoupRequestOperation(handle.get());
return;
}
if (!d->m_inputStream) {
handle->client()->didFinishLoading(handle.get());
cleanupSoupRequestOperation(handle.get());
return;
}
d->m_response = ResourceResponse();
d->m_response.setURL(handle->firstRequest().url());
d->m_response.updateFromSoupMessageHeaders(soup_multipart_input_stream_get_headers(d->m_multipartInputStream.get()));
d->m_previousPosition = 0;
handle->didReceiveResponse(ResourceResponse(d->m_response));
}
static void sendRequestCallback(GObject*, GAsyncResult* result, gpointer data)
{
RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
if (handle->cancelledOrClientless()) {
cleanupSoupRequestOperation(handle.get());
return;
}
ResourceHandleInternal* d = handle->getInternal();
SoupMessage* soupMessage = d->m_soupMessage.get();
if (d->m_defersLoading) {
d->m_deferredResult = result;
return;
}
GUniqueOutPtr<GError> error;
GRefPtr<GInputStream> inputStream = adoptGRef(soup_request_send_finish(d->m_soupRequest.get(), result, &error.outPtr()));
if (error) {
handle->client()->didFail(handle.get(), ResourceError::httpError(soupMessage, error.get(), d->m_soupRequest.get()));
cleanupSoupRequestOperation(handle.get());
return;
}
if (soupMessage) {
if (handle->shouldContentSniff() && soupMessage->status_code != SOUP_STATUS_NOT_MODIFIED) {
const char* sniffedType = soup_request_get_content_type(d->m_soupRequest.get());
d->m_response.setSniffedContentType(sniffedType);
}
d->m_response.updateFromSoupMessage(soupMessage);
if (SOUP_STATUS_IS_REDIRECTION(soupMessage->status_code) && shouldRedirect(handle.get())) {
d->m_inputStream = inputStream;
g_input_stream_skip_async(d->m_inputStream.get(), gDefaultReadBufferSize, RunLoopSourcePriority::AsyncIONetwork,
d->m_cancellable.get(), redirectSkipCallback, handle.get());
return;
}
} else {
d->m_response.setURL(handle->firstRequest().url());
const gchar* contentType = soup_request_get_content_type(d->m_soupRequest.get());
d->m_response.setMimeType(extractMIMETypeFromMediaType(contentType));
d->m_response.setTextEncodingName(extractCharsetFromMediaType(contentType));
d->m_response.setExpectedContentLength(soup_request_get_content_length(d->m_soupRequest.get()));
}
d->m_response.deprecatedNetworkLoadMetrics().responseStart = MonotonicTime::now() - handle->m_requestTime;
if (soupMessage && d->m_response.isMultipart())
d->m_multipartInputStream = adoptGRef(soup_multipart_input_stream_new(soupMessage, inputStream.get()));
else
d->m_inputStream = inputStream;
handle->didReceiveResponse(ResourceResponse(d->m_response));
}
void ResourceHandle::platformContinueSynchronousDidReceiveResponse()
{
continueAfterDidReceiveResponse(this);
}
static void continueAfterDidReceiveResponse(ResourceHandle* handle)
{
if (handle->cancelledOrClientless()) {
cleanupSoupRequestOperation(handle);
return;
}
ResourceHandleInternal* d = handle->getInternal();
if (d->m_soupMessage && d->m_multipartInputStream && !d->m_inputStream) {
soup_multipart_input_stream_next_part_async(d->m_multipartInputStream.get(), RunLoopSourcePriority::AsyncIONetwork,
d->m_cancellable.get(), nextMultipartResponsePartCallback, handle);
return;
}
ASSERT(d->m_inputStream);
handle->ensureReadBuffer();
g_input_stream_read_async(d->m_inputStream.get(), const_cast<char*>(d->m_soupBuffer->data), d->m_soupBuffer->length,
RunLoopSourcePriority::AsyncIONetwork, d->m_cancellable.get(), readCallback, handle);
}
void ResourceHandle::didStartRequest()
{
getInternal()->m_response.deprecatedNetworkLoadMetrics().requestStart = MonotonicTime::now() - m_requestTime;
}
#if SOUP_CHECK_VERSION(2, 49, 91)
static void startingCallback(SoupMessage*, ResourceHandle* handle)
{
handle->didStartRequest();
}
#endif // SOUP_CHECK_VERSION(2, 49, 91)
static void networkEventCallback(SoupMessage*, GSocketClientEvent event, GIOStream*, gpointer data)
{
ResourceHandle* handle = static_cast<ResourceHandle*>(data);
if (!handle)
return;
if (handle->cancelledOrClientless())
return;
ResourceHandleInternal* d = handle->getInternal();
Seconds deltaTime = MonotonicTime::now() - handle->m_requestTime;
switch (event) {
case G_SOCKET_CLIENT_RESOLVING:
d->m_response.deprecatedNetworkLoadMetrics().domainLookupStart = deltaTime;
break;
case G_SOCKET_CLIENT_RESOLVED:
d->m_response.deprecatedNetworkLoadMetrics().domainLookupEnd = deltaTime;
break;
case G_SOCKET_CLIENT_CONNECTING:
d->m_response.deprecatedNetworkLoadMetrics().connectStart = deltaTime;
if (d->m_response.deprecatedNetworkLoadMetrics().domainLookupStart != Seconds(-1)) {
d->m_response.deprecatedNetworkLoadMetrics().connectStart -=
d->m_response.deprecatedNetworkLoadMetrics().domainLookupEnd - d->m_response.deprecatedNetworkLoadMetrics().domainLookupStart;
}
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:
d->m_response.deprecatedNetworkLoadMetrics().secureConnectionStart = deltaTime;
break;
case G_SOCKET_CLIENT_TLS_HANDSHAKED:
break;
case G_SOCKET_CLIENT_COMPLETE:
d->m_response.deprecatedNetworkLoadMetrics().connectEnd = deltaTime;
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
static bool createSoupMessageForHandleAndRequest(ResourceHandle* handle, const ResourceRequest& request)
{
ASSERT(handle);
ResourceHandleInternal* d = handle->getInternal();
ASSERT(d->m_soupRequest);
d->m_soupMessage = adoptGRef(soup_request_http_get_message(SOUP_REQUEST_HTTP(d->m_soupRequest.get())));
if (!d->m_soupMessage)
return false;
SoupMessage* soupMessage = d->m_soupMessage.get();
request.updateSoupMessage(soupMessage);
d->m_bodySize = soupMessage->request_body->length;
g_object_set_data(G_OBJECT(soupMessage), "handle", handle);
if (!handle->shouldContentSniff())
soup_message_disable_feature(soupMessage, SOUP_TYPE_CONTENT_SNIFFER);
if (!d->m_useAuthenticationManager)
soup_message_disable_feature(soupMessage, SOUP_TYPE_AUTH_MANAGER);
if (!soup_message_headers_get_one(soupMessage->request_headers, "Accept"))
soup_message_headers_append(soupMessage->request_headers, "Accept", "*/*");
if ((request.httpMethod() == "POST" || request.httpMethod() == "PUT") && !d->m_bodySize)
soup_message_headers_set_content_length(soupMessage->request_headers, 0);
g_signal_connect(d->m_soupMessage.get(), "notify::tls-errors", G_CALLBACK(tlsErrorsChangedCallback), handle);
g_signal_connect(d->m_soupMessage.get(), "got-headers", G_CALLBACK(gotHeadersCallback), handle);
g_signal_connect(d->m_soupMessage.get(), "wrote-body-data", G_CALLBACK(wroteBodyDataCallback), handle);
unsigned flags = SOUP_MESSAGE_NO_REDIRECT;
soup_message_set_flags(d->m_soupMessage.get(), static_cast<SoupMessageFlags>(soup_message_get_flags(d->m_soupMessage.get()) | flags));
#if SOUP_CHECK_VERSION(2, 49, 91)
g_signal_connect(d->m_soupMessage.get(), "starting", G_CALLBACK(startingCallback), handle);
#endif
g_signal_connect(d->m_soupMessage.get(), "network-event", G_CALLBACK(networkEventCallback), handle);
g_signal_connect(d->m_soupMessage.get(), "restarted", G_CALLBACK(restartedCallback), handle);
#if SOUP_CHECK_VERSION(2, 43, 1)
soup_message_set_priority(d->m_soupMessage.get(), toSoupMessagePriority(request.priority()));
#endif
return true;
}
static bool createSoupRequestAndMessageForHandle(ResourceHandle* handle, const ResourceRequest& request)
{
ResourceHandleInternal* d = handle->getInternal();
GUniquePtr<SoupURI> soupURI = request.createSoupURI();
if (!soupURI)
return false;
GUniqueOutPtr<GError> error;
d->m_soupRequest = adoptGRef(soup_session_request_uri(d->soupSession(), soupURI.get(), &error.outPtr()));
if (error) {
d->m_soupRequest.clear();
return false;
}
if (request.url().protocolIsInHTTPFamily() && !createSoupMessageForHandleAndRequest(handle, request)) {
d->m_soupRequest.clear();
return false;
}
request.updateSoupRequest(d->m_soupRequest.get());
return true;
}
bool ResourceHandle::start()
{
ASSERT(!d->m_soupMessage);
if (d->m_context && !d->m_context->isValid())
return false;
const ResourceRequest& request = firstRequest();
if (!request.url().protocolIsInHTTPFamily() && request.httpMethod() != "GET" && request.httpMethod() != "POST") {
this->scheduleFailure(InvalidURLFailure); return true;
}
applyAuthenticationToRequest(this, firstRequest(), false);
if (!createSoupRequestAndMessageForHandle(this, request)) {
this->scheduleFailure(InvalidURLFailure); return true;
}
if (!d->m_defersLoading)
sendPendingRequest();
return true;
}
RefPtr<ResourceHandle> ResourceHandle::releaseForDownload(ResourceHandleClient* downloadClient)
{
ResourceHandle* newHandle = new ResourceHandle(d->m_context.get(), firstRequest(), nullptr, d->m_defersLoading, d->m_shouldContentSniff, d->m_shouldContentEncodingSniff);
newHandle->relaxAdoptionRequirement();
std::swap(d, newHandle->d);
g_signal_handlers_disconnect_matched(newHandle->d->m_soupMessage.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
g_object_set_data(G_OBJECT(newHandle->d->m_soupMessage.get()), "handle", newHandle);
newHandle->d->m_client = downloadClient;
continueAfterDidReceiveResponse(newHandle);
return newHandle;
}
void ResourceHandle::timeoutFired()
{
client()->didFail(this, ResourceError::timeoutError(firstRequest().url()));
cancel();
}
void ResourceHandle::sendPendingRequest()
{
m_requestTime = MonotonicTime::now();
if (d->m_firstRequest.timeoutInterval() > 0)
d->m_timeoutSource.startOneShot(1_s * d->m_firstRequest.timeoutInterval());
ref();
d->m_cancellable = adoptGRef(g_cancellable_new());
soup_request_send_async(d->m_soupRequest.get(), d->m_cancellable.get(), sendRequestCallback, this);
}
void ResourceHandle::cancel()
{
d->m_cancelled = true;
if (d->m_soupMessage)
soup_session_cancel_message(d->soupSession(), d->m_soupMessage.get(), SOUP_STATUS_CANCELLED);
else if (d->m_cancellable)
g_cancellable_cancel(d->m_cancellable.get());
}
bool ResourceHandle::shouldUseCredentialStorage()
{
return (!client() || client()->shouldUseCredentialStorage(this)) && firstRequest().url().protocolIsInHTTPFamily();
}
void ResourceHandle::continueDidReceiveAuthenticationChallenge(const Credential& credentialFromPersistentStorage)
{
ASSERT(!d->m_currentWebChallenge.isNull());
AuthenticationChallenge& challenge = d->m_currentWebChallenge;
ASSERT(d->m_soupMessage);
if (!credentialFromPersistentStorage.isEmpty())
challenge.setProposedCredential(credentialFromPersistentStorage);
if (!client()) {
soup_session_unpause_message(d->soupSession(), d->m_soupMessage.get());
clearAuthentication();
return;
}
client()->didReceiveAuthenticationChallenge(this, challenge);
}
void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
{
ASSERT(d->m_currentWebChallenge.isNull());
String partition = firstRequest().cachePartition();
bool useCredentialStorage = shouldUseCredentialStorage();
if (useCredentialStorage) {
if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
CredentialStorage::defaultCredentialStorage().remove(partition, challenge.protectionSpace());
}
if (!challenge.previousFailureCount()) {
Credential credential = CredentialStorage::defaultCredentialStorage().get(partition, challenge.protectionSpace());
if (!credential.isEmpty() && credential != d->m_initialCredential) {
ASSERT(credential.persistence() == CredentialPersistenceNone);
if (isAuthenticationFailureStatusCode(challenge.failureResponse().httpStatusCode()))
CredentialStorage::defaultCredentialStorage().set(partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
soup_auth_authenticate(challenge.soupAuth(), credential.user().utf8().data(), credential.password().utf8().data());
return;
}
}
}
d->m_currentWebChallenge = challenge;
soup_session_pause_message(d->soupSession(), d->m_soupMessage.get());
if (useCredentialStorage && d->m_context && d->m_context->isValid()) {
d->m_context->storageSession().getCredentialFromPersistentStorage(challenge.protectionSpace(), [this, protectedThis = makeRef(*this)] (Credential&& credential) {
continueDidReceiveAuthenticationChallenge(WTFMove(credential));
});
return;
}
continueDidReceiveAuthenticationChallenge(Credential());
}
void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
{
ASSERT(!challenge.isNull());
if (challenge != d->m_currentWebChallenge)
return;
soup_session_unpause_message(d->soupSession(), d->m_soupMessage.get());
clearAuthentication();
}
void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
{
ASSERT(!challenge.isNull());
if (challenge != d->m_currentWebChallenge)
return;
if (credential.isEmpty()) {
receivedRequestToContinueWithoutCredential(challenge);
return;
}
String partition = firstRequest().cachePartition();
if (shouldUseCredentialStorage()) {
if (credential.persistence() == CredentialPersistenceForSession || credential.persistence() == CredentialPersistencePermanent)
CredentialStorage::defaultCredentialStorage().set(partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
if (credential.persistence() == CredentialPersistencePermanent) {
d->m_credentialDataToSaveInPersistentStore.credential = credential;
d->m_credentialDataToSaveInPersistentStore.protectionSpace = challenge.protectionSpace();
}
}
ASSERT(d->m_soupMessage);
soup_auth_authenticate(challenge.soupAuth(), credential.user().utf8().data(), credential.password().utf8().data());
soup_session_unpause_message(d->soupSession(), d->m_soupMessage.get());
clearAuthentication();
}
void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
{
ASSERT(!challenge.isNull());
if (challenge != d->m_currentWebChallenge)
return;
if (cancelledOrClientless()) {
clearAuthentication();
return;
}
ASSERT(d->m_soupMessage);
soup_session_unpause_message(d->soupSession(), d->m_soupMessage.get());
if (client())
client()->receivedCancellation(this, challenge);
clearAuthentication();
}
void ResourceHandle::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge&)
{
ASSERT_NOT_REACHED();
}
void ResourceHandle::receivedChallengeRejection(const AuthenticationChallenge& challenge)
{
notImplemented();
receivedRequestToContinueWithoutCredential(challenge);
}
static bool waitingToSendRequest(ResourceHandle* handle)
{
return handle->getInternal()->m_soupRequest && !handle->getInternal()->m_cancellable;
}
void ResourceHandle::platformSetDefersLoading(bool defersLoading)
{
if (cancelledOrClientless())
return;
if (defersLoading) {
d->m_timeoutSource.stop();
return;
}
if (waitingToSendRequest(this)) {
sendPendingRequest();
return;
}
if (d->m_deferredResult) {
GRefPtr<GAsyncResult> asyncResult = adoptGRef(d->m_deferredResult.leakRef());
if (d->m_inputStream)
readCallback(G_OBJECT(d->m_inputStream.get()), asyncResult.get(), this);
else
sendRequestCallback(G_OBJECT(d->m_soupRequest.get()), asyncResult.get(), this);
}
}
void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext*, const ResourceRequest&, StoredCredentialsPolicy, ResourceError&, ResourceResponse&, Vector<char>&)
{
ASSERT_NOT_REACHED();
}
static void readCallback(GObject*, GAsyncResult* asyncResult, gpointer data)
{
RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
if (handle->cancelledOrClientless()) {
cleanupSoupRequestOperation(handle.get());
return;
}
ResourceHandleInternal* d = handle->getInternal();
if (d->m_defersLoading) {
d->m_deferredResult = asyncResult;
return;
}
GUniqueOutPtr<GError> error;
gssize bytesRead = g_input_stream_read_finish(d->m_inputStream.get(), asyncResult, &error.outPtr());
if (error) {
handle->client()->didFail(handle.get(), ResourceError::genericGError(error.get(), d->m_soupRequest.get()));
cleanupSoupRequestOperation(handle.get());
return;
}
if (!bytesRead) {
if (d->m_soupMessage && d->m_multipartInputStream) {
d->m_inputStream.clear();
soup_multipart_input_stream_next_part_async(d->m_multipartInputStream.get(), RunLoopSourcePriority::AsyncIONetwork,
d->m_cancellable.get(), nextMultipartResponsePartCallback, handle.get());
return;
}
g_input_stream_close(d->m_inputStream.get(), 0, 0);
handle->client()->didFinishLoading(handle.get());
cleanupSoupRequestOperation(handle.get());
return;
}
ASSERT(!d->m_response.isNull());
size_t currentPosition = handle->currentStreamPosition();
size_t encodedDataLength = currentPosition ? currentPosition - d->m_previousPosition : bytesRead;
ASSERT(d->m_soupBuffer);
d->m_soupBuffer->length = bytesRead; handle->client()->didReceiveBuffer(handle.get(), SharedBuffer::wrapSoupBuffer(d->m_soupBuffer.release()), encodedDataLength);
d->m_previousPosition = currentPosition;
if (handle->cancelledOrClientless()) {
cleanupSoupRequestOperation(handle.get());
return;
}
handle->ensureReadBuffer();
g_input_stream_read_async(d->m_inputStream.get(), const_cast<char*>(d->m_soupBuffer->data), d->m_soupBuffer->length, RunLoopSourcePriority::AsyncIONetwork,
d->m_cancellable.get(), readCallback, handle.get());
}
void ResourceHandle::continueDidReceiveResponse()
{
continueAfterDidReceiveResponse(this);
}
}
#endif