blob: 92b5abe357e0f0174b5211d9a39873ccf641cc51 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010, 2011, 2012, 2013 Research In Motion Limited. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "NetworkJob.h"
#include "AuthenticationChallengeManager.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "CookieManager.h"
#include "CredentialBackingStore.h"
#include "CredentialStorage.h"
#include "Frame.h"
#include "FrameLoaderClientBlackBerry.h"
#include "HTTPParsers.h"
#include "KURL.h"
#include "MIMESniffing.h"
#include "MIMETypeRegistry.h"
#include "NetworkManager.h"
#include "Page.h"
#include "RSSFilterStream.h"
#include "ResourceHandleClient.h"
#include "ResourceHandleInternal.h"
#include "ResourceRequest.h"
#include <BlackBerryPlatformLog.h>
#include <BlackBerryPlatformSettings.h>
#include <LocalizeResource.h>
#include <network/MultipartStream.h>
#include <network/NetworkStreamFactory.h>
using BlackBerry::Platform::NetworkRequest;
namespace WebCore {
static const int s_redirectMaximum = 10;
inline static bool isInfo(int statusCode)
{
return 100 <= statusCode && statusCode < 200;
}
inline static bool isRedirect(int statusCode)
{
return 300 <= statusCode && statusCode < 400 && statusCode != 304;
}
inline static bool isUnauthorized(int statusCode)
{
return statusCode == 401;
}
static const char* const appendableHeaders[] = {"access-control-allow-origin", "allow",
"set-cookie", "set-cookie2", "vary", "via", "warning"};
static bool isAppendableHeader(const String& key)
{
// Non-standard header fields are conventionally marked by prefixing the field name with X-.
if (key.startsWith("x-"))
return true;
for (size_t i = 0; i < sizeof(appendableHeaders) /sizeof(char*); i++)
if (key == appendableHeaders[i])
return true;
return false;
}
NetworkJob::NetworkJob()
: FrameDestructionObserver(0)
, m_playerId(0)
, m_deleteJobTimer(this, &NetworkJob::fireDeleteJobTimer)
, m_streamFactory(0)
, m_isFile(false)
, m_isFTP(false)
, m_isFTPDir(true)
#ifndef NDEBUG
, m_isRunning(true) // Always started immediately after creation.
#endif
, m_cancelled(false)
, m_statusReceived(false)
, m_dataReceived(false)
, m_responseSent(false)
, m_callingClient(false)
, m_needsRetryAsFTPDirectory(false)
, m_isOverrideContentType(false)
, m_newJobWithCredentialsStarted(false)
, m_isHeadMethod(false)
, m_extendedStatusCode(0)
, m_redirectCount(0)
, m_deferredData(*this)
, m_deferLoadingCount(0)
, m_isAuthenticationChallenging(false)
{
}
NetworkJob::~NetworkJob()
{
if (m_isAuthenticationChallenging)
AuthenticationChallengeManager::instance()->cancelAuthenticationChallenge(this);
}
void NetworkJob::initialize(int playerId,
const String& pageGroupName,
const KURL& url,
const BlackBerry::Platform::NetworkRequest& request,
PassRefPtr<ResourceHandle> handle,
BlackBerry::Platform::NetworkStreamFactory* streamFactory,
Frame* frame,
int deferLoadingCount,
int redirectCount)
{
BLACKBERRY_ASSERT(handle);
BLACKBERRY_ASSERT(frame);
m_playerId = playerId;
m_pageGroupName = pageGroupName;
m_response.setURL(url);
m_isFile = url.protocolIs("file") || url.protocolIs("local");
m_isFTP = url.protocolIs("ftp");
m_handle = handle;
m_streamFactory = streamFactory;
if (frame && frame->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) {
// In the case the frame will be detached soon, we still need to ping the server, but it is
// no longer safe to reference the Frame object.
// See http://trac.webkit.org/changeset/65910 and https://bugs.webkit.org/show_bug.cgi?id=30457.
// m_frame would be set to zero.
observeFrame(0);
} else
observeFrame(frame);
m_redirectCount = redirectCount;
m_deferLoadingCount = deferLoadingCount;
m_isHeadMethod = m_handle->firstRequest().httpMethod().upper() == "HEAD";
// We don't need to explicitly call notifyHeaderReceived, as the Content-Type
// will ultimately get parsed when sendResponseIfNeeded gets called.
if (!request.getOverrideContentType().empty()) {
m_contentType = String(request.getOverrideContentType());
m_isOverrideContentType = true;
}
if (!request.getSuggestedSaveName().empty())
m_contentDisposition = "filename=" + String(request.getSuggestedSaveName());
BlackBerry::Platform::FilterStream* wrappedStream = m_streamFactory->createNetworkStream(request, m_playerId);
ASSERT(wrappedStream);
BlackBerry::Platform::NetworkRequest::TargetType targetType = request.getTargetType();
if ((targetType == BlackBerry::Platform::NetworkRequest::TargetIsMainFrame
|| targetType == BlackBerry::Platform::NetworkRequest::TargetIsSubframe)
&& !m_isOverrideContentType) {
RSSFilterStream* filter = new RSSFilterStream();
filter->setWrappedStream(wrappedStream);
wrappedStream = filter;
}
setWrappedStream(wrappedStream);
}
int NetworkJob::cancelJob()
{
m_cancelled = true;
return streamCancel();
}
void NetworkJob::updateDeferLoadingCount(int delta)
{
m_deferLoadingCount += delta;
ASSERT(m_deferLoadingCount >= 0);
if (!isDeferringLoading()) {
// There might already be a timer set to call this, but it's safe to schedule it again.
m_deferredData.scheduleProcessDeferredData();
}
}
void NetworkJob::notifyStatusReceived(int status, const BlackBerry::Platform::String& message)
{
if (shouldDeferLoading())
m_deferredData.deferOpen(status, message);
else
handleNotifyStatusReceived(status, message);
}
void NetworkJob::handleNotifyStatusReceived(int status, const String& message)
{
// Check for messages out of order or after cancel.
if (m_responseSent || m_cancelled)
return;
if (isInfo(status))
return; // ignore
m_statusReceived = true;
// Convert non-HTTP status codes to generic HTTP codes.
m_extendedStatusCode = status;
if (!status)
m_response.setHTTPStatusCode(200);
else if (status < 0)
m_response.setHTTPStatusCode(404);
else
m_response.setHTTPStatusCode(status);
m_response.setHTTPStatusText(message);
if (isUnauthorized(m_extendedStatusCode))
purgeCredentials();
}
void NetworkJob::notifyHeadersReceived(const BlackBerry::Platform::NetworkRequest::HeaderList& headers)
{
bool cookiesEnabled = m_frame && m_frame->loader() && m_frame->loader()->client()
&& static_cast<FrameLoaderClientBlackBerry*>(m_frame->loader()->client())->cookiesEnabled();
BlackBerry::Platform::NetworkRequest::HeaderList::const_iterator endIt = headers.end();
for (BlackBerry::Platform::NetworkRequest::HeaderList::const_iterator it = headers.begin(); it != endIt; ++it) {
// Handle Set-Cookie headers immediately, even if loading is being deferred, since any request
// created while loading is deferred should include all cookies received. (This especially
// affects Set-Cookie headers sent with a 401 response - often this causes an auth dialog to be
// opened, which defers loading, but the followup request using the credentials from the dialog
// needs to include the cookie.)
//
// This is safe because handleSetCookieHeader only updates the cookiejar, it doesn't call back
// into the loader.
String keyString(it->first);
if (cookiesEnabled && equalIgnoringCase(keyString, "set-cookie"))
handleSetCookieHeader(it->second);
if (shouldDeferLoading())
m_deferredData.deferHeaderReceived(it->first, it->second);
else {
String valueString;
if (equalIgnoringCase(keyString, "Location")) {
// Location, like all headers, is supposed to be Latin-1. But some sites (wikipedia) send it in UTF-8.
// All byte strings that are valid UTF-8 are also valid Latin-1 (although outside ASCII, the meaning will
// differ), but the reverse isn't true. So try UTF-8 first and fall back to Latin-1 if it's invalid.
// (High Latin-1 should be url-encoded anyway.)
//
// FIXME: maybe we should do this with other headers?
// Skip it for now - we don't want to rewrite random bytes unless we're sure. (Definitely don't want to
// rewrite cookies, for instance.) Needs more investigation.
valueString = it->second;
if (valueString.isNull())
valueString = it->second;
} else
valueString = it->second;
handleNotifyHeaderReceived(keyString, valueString);
}
}
}
void NetworkJob::notifyMultipartHeaderReceived(const char* key, const char* value)
{
if (shouldDeferLoading())
m_deferredData.deferMultipartHeaderReceived(key, value);
else
handleNotifyMultipartHeaderReceived(key, value);
}
void NetworkJob::notifyAuthReceived(NetworkRequest::AuthType authType, NetworkRequest::AuthProtocol authProtocol, NetworkRequest::AuthScheme authScheme, const char* realm, AuthResult result)
{
ProtectionSpaceServerType serverType;
switch (authType) {
case NetworkRequest::AuthTypeHost:
switch (authProtocol) {
case NetworkRequest::AuthProtocolHTTP:
serverType = ProtectionSpaceServerHTTP;
break;
case NetworkRequest::AuthProtocolHTTPS:
serverType = ProtectionSpaceServerHTTPS;
break;
case NetworkRequest::AuthProtocolFTP:
serverType = ProtectionSpaceServerFTP;
break;
case NetworkRequest::AuthProtocolFTPS:
serverType = ProtectionSpaceServerFTPS;
break;
default:
ASSERT_NOT_REACHED();
return;
}
break;
case NetworkRequest::AuthTypeProxy:
switch (authProtocol) {
case NetworkRequest::AuthProtocolHTTP:
serverType = ProtectionSpaceProxyHTTP;
break;
case NetworkRequest::AuthProtocolHTTPS:
serverType = ProtectionSpaceProxyHTTPS;
break;
case NetworkRequest::AuthProtocolFTP:
case NetworkRequest::AuthProtocolFTPS:
serverType = ProtectionSpaceProxyFTP;
break;
default:
ASSERT_NOT_REACHED();
return;
}
break;
default:
ASSERT_NOT_REACHED();
return;
}
ProtectionSpaceAuthenticationScheme scheme;
switch (authScheme) {
case NetworkRequest::AuthSchemeDefault:
scheme = ProtectionSpaceAuthenticationSchemeDefault;
break;
case NetworkRequest::AuthSchemeHTTPBasic:
scheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
break;
case NetworkRequest::AuthSchemeHTTPDigest:
scheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
break;
case NetworkRequest::AuthSchemeNegotiate:
scheme = ProtectionSpaceAuthenticationSchemeNegotiate;
break;
case NetworkRequest::AuthSchemeNTLM:
scheme = ProtectionSpaceAuthenticationSchemeNTLM;
break;
default:
ASSERT_NOT_REACHED();
return;
}
// On success, update stored credentials if necessary
// On failure, purge credentials and send new request
// On retry, update stored credentials if necessary and send new request
if (result == AuthResultFailure)
purgeCredentials();
else {
// Update the credentials that will be stored to match the scheme that was actually used
AuthenticationChallenge& challenge = authType == NetworkRequest::AuthTypeProxy ? m_handle->getInternal()->m_proxyWebChallenge : m_handle->getInternal()->m_hostWebChallenge;
if (challenge.hasCredentials()) {
const ProtectionSpace& oldSpace = challenge.protectionSpace();
if (oldSpace.authenticationScheme() != scheme && oldSpace.serverType() == serverType) {
ProtectionSpace newSpace(oldSpace.host(), oldSpace.port(), oldSpace.serverType(), oldSpace.realm(), scheme);
updateCurrentWebChallenge(AuthenticationChallenge(newSpace, challenge.proposedCredential(), challenge.previousFailureCount(), challenge.failureResponse(), challenge.error()));
}
}
storeCredentials();
}
if (result != AuthResultSuccess) {
switch (sendRequestWithCredentials(serverType, scheme, realm, result != AuthResultRetry)) {
case SendRequestSucceeded:
m_newJobWithCredentialsStarted = true;
break;
case SendRequestCancelled:
streamFailedToGetCredentials(authType, authProtocol, authScheme);
// fall through
case SendRequestWaiting:
m_newJobWithCredentialsStarted = false;
break;
}
}
}
void NetworkJob::notifyStringHeaderReceived(const String& key, const String& value)
{
if (shouldDeferLoading())
m_deferredData.deferHeaderReceived(key, value);
else
handleNotifyHeaderReceived(key, value);
}
void NetworkJob::handleNotifyHeaderReceived(const String& key, const String& value)
{
// Check for messages out of order or after cancel.
if (!m_statusReceived || m_responseSent || m_cancelled)
return;
String lowerKey = key.lower();
if (lowerKey == "content-type")
m_contentType = value.lower();
else if (lowerKey == "content-disposition")
m_contentDisposition = value;
else if (equalIgnoringCase(key, BlackBerry::Platform::NetworkRequest::HEADER_BLACKBERRY_FTP))
handleFTPHeader(value);
if (m_response.httpHeaderFields().contains(key.utf8().data()) && isAppendableHeader(lowerKey)) {
// If there are several headers with same key, we should combine the following ones with the first.
m_response.setHTTPHeaderField(key, m_response.httpHeaderField(key) + ", " + value);
} else
m_response.setHTTPHeaderField(key, value);
}
void NetworkJob::handleNotifyMultipartHeaderReceived(const String& key, const String& value)
{
if (!m_multipartResponse) {
// Create a new response based on the original set of headers + the
// replacement headers. We only replace the same few headers that gecko
// does. See netwerk/streamconv/converters/nsMultiMixedConv.cpp.
m_multipartResponse = adoptPtr(new ResourceResponse);
m_multipartResponse->setURL(m_response.url());
// The list of BlackBerry::Platform::replaceHeaders that we do not copy from the original
// response when generating a response.
const WebCore::HTTPHeaderMap& map = m_response.httpHeaderFields();
for (WebCore::HTTPHeaderMap::const_iterator it = map.begin(); it != map.end(); ++it) {
bool needsCopyfromOriginalResponse = true;
int replaceHeadersIndex = 0;
while (BlackBerry::Platform::MultipartStream::replaceHeaders[replaceHeadersIndex]) {
if (it->key.lower() == BlackBerry::Platform::MultipartStream::replaceHeaders[replaceHeadersIndex]) {
needsCopyfromOriginalResponse = false;
break;
}
replaceHeadersIndex++;
}
if (needsCopyfromOriginalResponse)
m_multipartResponse->setHTTPHeaderField(it->key, it->value);
}
m_multipartResponse->setIsMultipartPayload(true);
}
if (key.lower() == "content-type") {
String contentType = value.lower();
m_multipartResponse->setMimeType(extractMIMETypeFromMediaType(contentType));
m_multipartResponse->setTextEncodingName(extractCharsetFromMediaType(contentType));
}
m_multipartResponse->setHTTPHeaderField(key, value);
}
void NetworkJob::handleSetCookieHeader(const String& value)
{
KURL url = m_response.url();
CookieManager& manager = cookieManager();
if ((manager.cookiePolicy() == CookieStorageAcceptPolicyOnlyFromMainDocumentDomain)
&& (m_handle->firstRequest().firstPartyForCookies() != url)
&& manager.getCookie(url, WithHttpOnlyCookies).isEmpty())
return;
manager.setCookies(url, value);
}
void NetworkJob::notifyDataReceivedPlain(const char* buf, size_t len)
{
if (shouldDeferLoading())
m_deferredData.deferDataReceived(buf, len);
else
handleNotifyDataReceived(buf, len);
}
void NetworkJob::handleNotifyDataReceived(const char* buf, size_t len)
{
// Check for messages out of order or after cancel.
if ((!m_isFile && !m_statusReceived) || m_cancelled)
return;
if (!buf || !len)
return;
// The loadFile API sets the override content type,
// this will always be used as the content type and should not be overridden.
if (!m_dataReceived && !m_isOverrideContentType) {
bool shouldSniff = true;
// Don't bother sniffing the content type of a file that
// is on a file system if it has a MIME mappable file extension.
// The file extension is likely to be correct.
if (m_isFile) {
String urlFilename = m_response.url().lastPathComponent();
size_t pos = urlFilename.reverseFind('.');
if (pos != notFound) {
String extension = urlFilename.substring(pos + 1);
String mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
if (!mimeType.isEmpty())
shouldSniff = false;
}
}
if (shouldSniff) {
MIMESniffer sniffer = MIMESniffer(m_contentType.latin1().data(), MIMETypeRegistry::isSupportedImageResourceMIMEType(m_contentType));
if (const char* type = sniffer.sniff(buf, std::min(len, sniffer.dataSize())))
m_sniffedMimeType = String(type);
}
}
m_dataReceived = true;
// Protect against reentrancy.
updateDeferLoadingCount(1);
if (shouldSendClientData()) {
sendResponseIfNeeded();
sendMultipartResponseIfNeeded();
if (isClientAvailable()) {
RecursionGuard guard(m_callingClient);
m_handle->client()->didReceiveData(m_handle.get(), buf, len, len);
}
}
updateDeferLoadingCount(-1);
}
void NetworkJob::notifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
{
if (shouldDeferLoading())
m_deferredData.deferDataSent(bytesSent, totalBytesToBeSent);
else
handleNotifyDataSent(bytesSent, totalBytesToBeSent);
}
void NetworkJob::handleNotifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
{
if (m_cancelled)
return;
// Protect against reentrancy.
updateDeferLoadingCount(1);
if (isClientAvailable()) {
RecursionGuard guard(m_callingClient);
m_handle->client()->didSendData(m_handle.get(), bytesSent, totalBytesToBeSent);
}
updateDeferLoadingCount(-1);
}
void NetworkJob::notifyClose(int status)
{
if (shouldDeferLoading())
m_deferredData.deferClose(status);
else
handleNotifyClose(status);
}
void NetworkJob::handleNotifyClose(int status)
{
#ifndef NDEBUG
m_isRunning = false;
#endif
if (!m_cancelled) {
if (!m_statusReceived) {
// Connection failed before sending notifyStatusReceived: use generic NetworkError.
notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusNetworkError, BlackBerry::Platform::String::emptyString());
}
if (shouldReleaseClientResource()) {
if (isRedirect(m_extendedStatusCode) && (m_redirectCount >= s_redirectMaximum))
m_extendedStatusCode = BlackBerry::Platform::FilterStream::StatusTooManyRedirects;
sendResponseIfNeeded();
if (isClientAvailable()) {
if (isError(status))
m_extendedStatusCode = status;
RecursionGuard guard(m_callingClient);
if (shouldNotifyClientFailed()) {
String domain = m_extendedStatusCode < 0 ? ResourceError::platformErrorDomain : ResourceError::httpErrorDomain;
ResourceError error(domain, m_extendedStatusCode, m_response.url().string(), m_response.httpStatusText());
m_handle->client()->didFail(m_handle.get(), error);
} else
m_handle->client()->didFinishLoading(m_handle.get(), 0);
}
}
}
// Whoever called notifyClose still have a reference to the job, so
// schedule the deletion with a timer.
m_deleteJobTimer.startOneShot(0);
// Detach from the ResourceHandle in any case.
m_handle = 0;
m_multipartResponse = nullptr;
}
bool NetworkJob::shouldReleaseClientResource()
{
if ((m_needsRetryAsFTPDirectory && retryAsFTPDirectory()) || (isRedirect(m_extendedStatusCode) && handleRedirect()) || m_newJobWithCredentialsStarted || m_isAuthenticationChallenging)
return false;
return true;
}
bool NetworkJob::shouldNotifyClientFailed() const
{
return m_extendedStatusCode < 0 || (isError(m_extendedStatusCode) && !m_dataReceived && !m_isHeadMethod);
}
bool NetworkJob::retryAsFTPDirectory()
{
m_needsRetryAsFTPDirectory = false;
ASSERT(m_handle);
ResourceRequest newRequest = m_handle->firstRequest();
KURL url = newRequest.url();
url.setPath(url.path() + "/");
newRequest.setURL(url);
newRequest.setMustHandleInternally(true);
// Update the UI.
handleNotifyHeaderReceived("Location", url.string());
return startNewJobWithRequest(newRequest);
}
bool NetworkJob::startNewJobWithRequest(ResourceRequest& newRequest, bool increaseRedirectCount, bool rereadCookies)
{
// m_frame can be null if this is a PingLoader job (See NetworkJob::initialize).
// In this case we don't start new request.
if (!m_frame)
return false;
if (isClientAvailable()) {
RecursionGuard guard(m_callingClient);
m_handle->client()->willSendRequest(m_handle.get(), newRequest, m_response);
// m_cancelled can become true if the url fails the policy check.
// newRequest can be cleared when the redirect is rejected.
if (m_cancelled || newRequest.isEmpty())
return false;
}
// Pass the ownership of the ResourceHandle to the new NetworkJob.
RefPtr<ResourceHandle> handle = m_handle;
cancelJob();
int status = NetworkManager::instance()->startJob(m_playerId,
m_pageGroupName,
handle,
newRequest,
m_streamFactory,
m_frame,
m_deferLoadingCount,
increaseRedirectCount ? m_redirectCount + 1 : m_redirectCount,
rereadCookies);
return status == BlackBerry::Platform::FilterStream::StatusSuccess;
}
bool NetworkJob::handleRedirect()
{
ASSERT(m_handle);
if (!m_handle || m_redirectCount >= s_redirectMaximum)
return false;
String location = m_response.httpHeaderField("Location");
if (location.isNull())
return false;
KURL newURL(m_response.url(), location);
if (!newURL.isValid())
return false;
if (newURL.protocolIsData()) {
m_extendedStatusCode = BlackBerry::Platform::FilterStream::StatusInvalidRedirectToData;
return false;
}
ResourceRequest newRequest = m_handle->firstRequest();
newRequest.setURL(newURL);
newRequest.setMustHandleInternally(true);
String method = newRequest.httpMethod().upper();
if (method != "GET" && method != "HEAD") {
newRequest.setHTTPMethod("GET");
newRequest.setHTTPBody(0);
newRequest.clearHTTPContentLength();
newRequest.clearHTTPContentType();
}
// If this request is challenged, store the credentials now (if they are null this will do nothing)
storeCredentials();
// Do not send existing credentials with the new request.
m_handle->getInternal()->m_currentWebChallenge.nullify();
m_handle->getInternal()->m_proxyWebChallenge.nullify();
m_handle->getInternal()->m_hostWebChallenge.nullify();
return startNewJobWithRequest(newRequest, /* increaseRedirectCount */ true, /* rereadCookies */ true);
}
void NetworkJob::sendResponseIfNeeded()
{
if (m_responseSent)
return;
m_responseSent = true;
if (shouldNotifyClientFailed())
return;
String urlFilename;
if (!m_response.url().protocolIsData())
urlFilename = m_response.url().lastPathComponent();
// Get the MIME type that was set by the content sniffer
// if there's no custom sniffer header, try to set it from the Content-Type header
// if this fails, guess it from extension.
String mimeType = m_sniffedMimeType;
if (m_isFTP && m_isFTPDir)
mimeType = "application/x-ftp-directory";
else if (mimeType.isNull())
mimeType = extractMIMETypeFromMediaType(m_contentType);
if (mimeType.isNull())
mimeType = MIMETypeRegistry::getMIMETypeForPath(urlFilename);
if (!m_dataReceived && mimeType == "application/octet-stream") {
// For empty content, if can't guess its mimetype from filename, we manually
// set the mimetype to "text/plain" in case it goes to download.
mimeType = "text/plain";
}
m_response.setMimeType(mimeType);
// Set encoding from Content-Type header.
m_response.setTextEncodingName(extractCharsetFromMediaType(m_contentType));
// Set content length from header.
String contentLength = m_response.httpHeaderField("Content-Length");
if (!contentLength.isNull())
m_response.setExpectedContentLength(contentLength.toInt64());
String suggestedFilename = filenameFromHTTPContentDisposition(m_contentDisposition);
if (suggestedFilename.isEmpty()) {
// Check and see if an extension already exists.
String mimeExtension = MIMETypeRegistry::getPreferredExtensionForMIMEType(mimeType);
if (urlFilename.isEmpty()) {
if (mimeExtension.isEmpty()) // No extension found for the mimeType.
suggestedFilename = String(BlackBerry::Platform::LocalizeResource::getString(BlackBerry::Platform::FILENAME_UNTITLED));
else
suggestedFilename = String(BlackBerry::Platform::LocalizeResource::getString(BlackBerry::Platform::FILENAME_UNTITLED)) + "." + mimeExtension;
} else {
if (urlFilename.reverseFind('.') == notFound && !mimeExtension.isEmpty())
suggestedFilename = urlFilename + '.' + mimeExtension;
else
suggestedFilename = urlFilename;
}
}
m_response.setSuggestedFilename(suggestedFilename);
if (isClientAvailable()) {
RecursionGuard guard(m_callingClient);
m_handle->client()->didReceiveResponse(m_handle.get(), m_response);
}
}
void NetworkJob::sendMultipartResponseIfNeeded()
{
if (m_multipartResponse && isClientAvailable()) {
m_handle->client()->didReceiveResponse(m_handle.get(), *m_multipartResponse);
m_multipartResponse = nullptr;
}
}
bool NetworkJob::handleFTPHeader(const String& header)
{
size_t spacePos = header.find(' ');
if (spacePos == notFound)
return false;
String statusCode = header.left(spacePos);
switch (statusCode.toInt()) {
case 213:
m_isFTPDir = false;
break;
case 530:
purgeCredentials();
if (m_response.url().protocolIs("ftps"))
sendRequestWithCredentials(ProtectionSpaceServerFTPS, ProtectionSpaceAuthenticationSchemeDefault, "ftp");
else
sendRequestWithCredentials(ProtectionSpaceServerFTP, ProtectionSpaceAuthenticationSchemeDefault, "ftp");
break;
case 230:
storeCredentials();
break;
case 550:
// The user might have entered an URL which point to a directory but forgot type '/',
// e.g., ftp://ftp.trolltech.com/qt/source where 'source' is a directory. We need to
// added '/' and try again.
if (m_handle && !m_handle->firstRequest().url().path().endsWith("/"))
m_needsRetryAsFTPDirectory = true;
break;
}
return true;
}
NetworkJob::SendRequestResult NetworkJob::sendRequestWithCredentials(ProtectionSpaceServerType type, ProtectionSpaceAuthenticationScheme scheme, const String& realm, bool requireCredentials)
{
ASSERT(m_handle);
if (!m_handle)
return SendRequestCancelled;
KURL newURL = m_response.url();
if (!newURL.isValid())
return SendRequestCancelled;
// IMPORTANT: if a new source of credentials is added to this method, be sure to handle it in
// purgeCredentials as well!
String host;
int port;
BlackBerry::Platform::ProxyInfo proxyInfo;
if (type == ProtectionSpaceProxyHTTP || type == ProtectionSpaceProxyHTTPS) {
proxyInfo = BlackBerry::Platform::Settings::instance()->proxyInfo(newURL.string());
ASSERT(!proxyInfo.address.empty());
if (proxyInfo.address.empty()) {
// Fall back to the response url if there's no proxy
// FIXME: is this the best way to handle this?
host = m_response.url().host();
port = m_response.url().port();
} else {
// proxyInfo returns host:port, without a protocol. KURL can't parse this, so stick http
// on the front.
// (We could split into host and port by hand, but that gets hard to parse with IPv6 urls,
// so better to reuse KURL's parsing.)
StringBuilder proxyAddress;
if (type == ProtectionSpaceProxyHTTP)
proxyAddress.append("http://");
else
proxyAddress.append("https://");
proxyAddress.append(proxyInfo.address);
KURL proxyURL(KURL(), proxyAddress.toString());
host = proxyURL.host();
port = proxyURL.port();
}
} else {
host = m_response.url().host();
port = m_response.url().port();
}
ProtectionSpace protectionSpace(host, port, type, realm, scheme);
// We've got the scheme and realm. Now we need a username and password.
Credential credential;
if (!requireCredentials) {
// Don't overwrite any existing credentials with the empty credential
updateCurrentWebChallenge(AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError()), /* allowOverwrite */ false);
} else if (!(credential = CredentialStorage::get(protectionSpace)).isEmpty()
#if ENABLE(BLACKBERRY_CREDENTIAL_PERSIST)
|| !(credential = CredentialStorage::getFromPersistentStorage(protectionSpace)).isEmpty()
#endif
) {
// First search the CredentialStorage and Persistent Credential Storage
AuthenticationChallenge challenge(protectionSpace, credential, 0, m_response, ResourceError());
challenge.setStored(true);
updateCurrentWebChallenge(challenge);
} else {
ASSERT(credential.isEmpty());
if (m_handle->firstRequest().targetType() == ResourceRequest::TargetIsFavicon) {
// The favicon loading is triggerred after the main resource has been loaded
// and parsed, so if we cancel the authentication challenge when loading the main
// resource, we should also cancel loading the favicon when it starts to
// load. If not we will receive another challenge which may confuse the user.
return SendRequestCancelled;
}
// CredentialStore is empty. Ask the user via dialog.
String username;
String password;
if (!proxyInfo.address.empty()) {
username = proxyInfo.username;
password = proxyInfo.password;
} else {
username = m_handle->getInternal()->m_user;
password = m_handle->getInternal()->m_pass;
}
// Before asking the user for credentials, we check if the URL contains that.
if (username.isEmpty() && password.isEmpty()) {
if (m_handle->firstRequest().targetType() != ResourceRequest::TargetIsMainFrame && BlackBerry::Platform::Settings::instance()->isChromeProcess())
return SendRequestCancelled;
if (!m_frame || !m_frame->page())
return SendRequestCancelled;
// DO overwrite any existing credentials with the empty credential
updateCurrentWebChallenge(AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError()));
m_isAuthenticationChallenging = true;
updateDeferLoadingCount(1);
AuthenticationChallengeManager::instance()->authenticationChallenge(newURL, protectionSpace,
Credential(), this, m_frame->page()->chrome()->client()->platformPageClient());
return SendRequestWaiting;
}
credential = Credential(username, password, CredentialPersistenceForSession);
updateCurrentWebChallenge(AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError()));
}
notifyChallengeResult(newURL, protectionSpace, AuthenticationChallengeSuccess, credential);
return m_newJobWithCredentialsStarted ? SendRequestSucceeded : SendRequestCancelled;
}
void NetworkJob::storeCredentials()
{
if (!m_handle)
return;
storeCredentials(m_handle->getInternal()->m_hostWebChallenge);
storeCredentials(m_handle->getInternal()->m_proxyWebChallenge);
}
void NetworkJob::storeCredentials(AuthenticationChallenge& challenge)
{
if (challenge.isNull())
return;
if (challenge.isStored())
return;
// Obviously we can't have successfully authenticated with empty credentials. (To store empty
// credentials, use purgeCredentials.)
// FIXME: We should assert here, but there is one path (when the credentials are read from the
// proxy config entirely in the platform layer) where storeCredentials is called with an empty
// challenge. The credentials should be passed back from the platform layer for storage in this
// case - see PR 287791.
if (challenge.proposedCredential().user().isEmpty() || challenge.proposedCredential().password().isEmpty())
return;
CredentialStorage::set(challenge.proposedCredential(), challenge.protectionSpace(), m_response.url());
challenge.setStored(true);
if (challenge.protectionSpace().serverType() == ProtectionSpaceProxyHTTP || challenge.protectionSpace().serverType() == ProtectionSpaceProxyHTTPS) {
StringBuilder proxyAddress;
proxyAddress.append(challenge.protectionSpace().host());
proxyAddress.append(":");
proxyAddress.appendNumber(challenge.protectionSpace().port());
BlackBerry::Platform::ProxyInfo proxyInfo;
proxyInfo.address = proxyAddress.toString();
proxyInfo.username = challenge.proposedCredential().user();
proxyInfo.password = challenge.proposedCredential().password();
BlackBerry::Platform::Settings::instance()->storeProxyCredentials(proxyInfo);
if (m_frame && m_frame->page())
m_frame->page()->chrome()->client()->platformPageClient()->syncProxyCredential(challenge.proposedCredential());
}
}
void NetworkJob::purgeCredentials()
{
if (!m_handle)
return;
purgeCredentials(m_handle->getInternal()->m_hostWebChallenge);
purgeCredentials(m_handle->getInternal()->m_proxyWebChallenge);
m_handle->getInternal()->m_currentWebChallenge.nullify();
m_handle->getInternal()->m_proxyWebChallenge.nullify();
m_handle->getInternal()->m_hostWebChallenge.nullify();
}
void NetworkJob::purgeCredentials(AuthenticationChallenge& challenge)
{
if (challenge.isNull())
return;
const String& purgeUsername = challenge.proposedCredential().user();
const String& purgePassword = challenge.proposedCredential().password();
// Since this credential didn't work, remove it from all sources which would return it
// IMPORTANT: every source that is checked for a password in sendRequestWithCredentials should
// be handled here!
if (challenge.protectionSpace().serverType() == ProtectionSpaceProxyHTTP || challenge.protectionSpace().serverType() == ProtectionSpaceProxyHTTPS) {
BlackBerry::Platform::ProxyInfo proxyInfo = BlackBerry::Platform::Settings::instance()->proxyInfo(m_handle->firstRequest().url().string());
if (!proxyInfo.address.empty() && purgeUsername == proxyInfo.username.c_str() && purgePassword == proxyInfo.password.c_str()) {
proxyInfo.username.clear();
proxyInfo.password.clear();
BlackBerry::Platform::Settings::instance()->storeProxyCredentials(proxyInfo);
}
} else if (m_handle->getInternal()->m_user == purgeUsername && m_handle->getInternal()->m_pass == purgePassword) {
m_handle->getInternal()->m_user = "";
m_handle->getInternal()->m_pass = "";
}
// Do not compare credential objects with == here, since we don't care about the persistence.
const Credential& storedCredential = CredentialStorage::get(challenge.protectionSpace());
if (storedCredential.user() == purgeUsername && storedCredential.password() == purgePassword) {
CredentialStorage::remove(challenge.protectionSpace());
challenge.setStored(false);
}
#if ENABLE(BLACKBERRY_CREDENTIAL_PERSIST)
const Credential& persistedCredential = credentialBackingStore().getLogin(challenge.protectionSpace());
if (persistedCredential.user() == purgeUsername && persistedCredential.password() == purgePassword)
credentialBackingStore().removeLogin(challenge.protectionSpace(), purgeUsername);
#endif
}
bool NetworkJob::shouldSendClientData() const
{
return (!isRedirect(m_extendedStatusCode) || !m_response.httpHeaderFields().contains("Location"))
&& !m_needsRetryAsFTPDirectory;
}
void NetworkJob::fireDeleteJobTimer(Timer<NetworkJob>*)
{
NetworkManager::instance()->deleteJob(this);
}
void NetworkJob::notifyChallengeResult(const KURL& url, const ProtectionSpace& protectionSpace, AuthenticationChallengeResult result, const Credential& credential)
{
ASSERT(url.isValid());
ASSERT(url == m_response.url());
ASSERT(!protectionSpace.host().isEmpty());
if (m_isAuthenticationChallenging) {
m_isAuthenticationChallenging = false;
if (result == AuthenticationChallengeSuccess)
cancelJob();
updateDeferLoadingCount(-1);
}
if (result != AuthenticationChallengeSuccess) {
NetworkRequest::AuthType authType;
NetworkRequest::AuthProtocol authProtocol;
NetworkRequest::AuthScheme authScheme;
protectionSpaceToPlatformAuth(protectionSpace, authType, authProtocol, authScheme);
streamFailedToGetCredentials(authType, authProtocol, authScheme);
return;
}
updateCurrentWebChallenge(AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError()), /* allowOverwrite */ false);
ResourceRequest newRequest = m_handle->firstRequest();
newRequest.setURL(url);
newRequest.setMustHandleInternally(true);
m_newJobWithCredentialsStarted = startNewJobWithRequest(newRequest, /* increaseRedirectCount */ false, /* rereadCookies */ true);
}
void NetworkJob::frameDestroyed()
{
if (m_frame && !m_cancelled)
cancelJob();
FrameDestructionObserver::frameDestroyed();
}
void NetworkJob::willDetachPage()
{
if (m_frame && !m_cancelled)
cancelJob();
}
void NetworkJob::updateCurrentWebChallenge(const AuthenticationChallenge& challenge, bool allowOverwrite)
{
if (allowOverwrite || !m_handle->getInternal()->m_currentWebChallenge.hasCredentials())
m_handle->getInternal()->m_currentWebChallenge = challenge;
if (challenge.protectionSpace().serverType() == ProtectionSpaceProxyHTTP || challenge.protectionSpace().serverType() == ProtectionSpaceProxyHTTPS) {
if (allowOverwrite || !m_handle->getInternal()->m_proxyWebChallenge.hasCredentials())
m_handle->getInternal()->m_proxyWebChallenge = challenge;
} else {
if (allowOverwrite || !m_handle->getInternal()->m_hostWebChallenge.hasCredentials())
m_handle->getInternal()->m_hostWebChallenge = challenge;
}
}
const BlackBerry::Platform::String NetworkJob::mimeType() const
{
return m_response.mimeType();
}
} // namespace WebCore