2 * Copyright (C) 2009, 2010, 2011 Research In Motion Limited. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "NetworkJob.h"
22 #include "AboutData.h"
24 #include "CookieManager.h"
25 #include "CredentialStorage.h"
27 #include "FrameLoaderClientBlackBerry.h"
28 #include "HTTPParsers.h"
30 #include "MIMESniffing.h"
31 #include "MIMETypeRegistry.h"
32 #include "NetworkManager.h"
33 #include "ResourceHandleClient.h"
34 #include "ResourceHandleInternal.h"
35 #include "ResourceRequest.h"
37 #include <BlackBerryPlatformClient.h>
38 #include <BlackBerryPlatformLog.h>
39 #include <BlackBerryPlatformWebKitCredits.h>
40 #include <BuildInformation.h>
41 #include <network/MultipartStream.h>
42 #include <network/NetworkRequest.h>
43 #include <network/NetworkStreamFactory.h>
44 #include <wtf/ASCIICType.h>
48 static const int s_redirectMaximum = 10;
50 inline static bool isInfo(int statusCode)
52 return 100 <= statusCode && statusCode < 200;
55 inline static bool isRedirect(int statusCode)
57 return 300 <= statusCode && statusCode < 400 && statusCode != 304;
60 inline static bool isUnauthorized(int statusCode)
62 return statusCode == 401;
65 static void escapeDecode(const char* src, int length, Vector<char>& out)
68 const char* const srcEnd = src + length;
69 char* dst = out.data();
70 for (; src < srcEnd; ) {
71 char inputChar = *src++;
72 if (inputChar == '%' && src + 2 <= srcEnd) {
74 char character = *src++;
75 if (isASCIIHexDigit(character))
76 digit1 = toASCIIHexValue(character);
80 if (isASCIIHexDigit(character))
81 digit2 = toASCIIHexValue(character);
83 *dst++ = (digit1 << 4) | digit2;
87 out.resize(dst - out.data());
90 NetworkJob::NetworkJob()
92 , m_loadDataTimer(this, &NetworkJob::fireLoadDataTimer)
93 , m_loadAboutTimer(this, &NetworkJob::fireLoadAboutTimer)
94 , m_deleteJobTimer(this, &NetworkJob::fireDeleteJobTimer)
102 , m_isRunning(true) // Always started immediately after creation.
105 , m_statusReceived(false)
106 , m_dataReceived(false)
107 , m_responseSent(false)
108 , m_callingClient(false)
110 , m_needsRetryAsFTPDirectory(false)
111 , m_isOverrideContentType(false)
112 , m_extendedStatusCode(0)
114 , m_deferredData(*this)
115 , m_deferLoadingCount(0)
120 bool NetworkJob::initialize(int playerId,
121 const String& pageGroupName,
123 const BlackBerry::Platform::NetworkRequest& request,
124 PassRefPtr<ResourceHandle> handle,
125 BlackBerry::Platform::NetworkStreamFactory* streamFactory,
127 int deferLoadingCount,
130 m_playerId = playerId;
131 m_pageGroupName = pageGroupName;
133 m_response.setURL(url);
134 m_isFile = url.protocolIs("file") || url.protocolIs("local");
135 m_isData = url.protocolIs("data");
136 m_isAbout = url.protocolIs("about");
137 m_isFTP = url.protocolIs("ftp");
141 m_streamFactory = streamFactory;
144 if (m_frame && m_frame->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) {
145 // In the case the frame will be detached soon, we still need to ping the server, but it is
146 // no longer safe to reference the Frame object.
147 // See http://trac.webkit.org/changeset/65910 and https://bugs.webkit.org/show_bug.cgi?id=30457.
151 m_redirectCount = redirectCount;
152 m_deferLoadingCount = deferLoadingCount;
154 // We don't need to explicitly call notifyHeaderReceived, as the Content-Type
155 // will ultimately get parsed when sendResponseIfNeeded gets called.
156 if (!request.getOverrideContentType().empty()) {
157 m_contentType = String(request.getOverrideContentType().c_str());
158 m_isOverrideContentType = true;
161 // No need to create the streams for data and about.
162 if (m_isData || m_isAbout)
165 BlackBerry::Platform::FilterStream* wrappedStream = m_streamFactory->createNetworkStream(request, m_playerId);
168 setWrappedStream(wrappedStream);
170 m_isXHR = request.getTargetType() == BlackBerry::Platform::NetworkRequest::TargetIsXMLHTTPRequest;
175 void NetworkJob::loadAboutURL()
177 m_loadAboutTimer.startOneShot(0);
180 int NetworkJob::cancelJob()
184 // Cancel jobs loading local data by killing the timer, and jobs
185 // getting data from the network by calling the inherited URLStream::cancel.
186 if (m_loadDataTimer.isActive()) {
187 m_loadDataTimer.stop();
188 notifyClose(BlackBerry::Platform::FilterStream::StatusCancelled);
192 if (m_loadAboutTimer.isActive()) {
193 m_loadAboutTimer.stop();
194 notifyClose(BlackBerry::Platform::FilterStream::StatusCancelled);
198 return streamCancel();
201 void NetworkJob::updateDeferLoadingCount(int delta)
203 m_deferLoadingCount += delta;
204 ASSERT(m_deferLoadingCount >= 0);
206 if (!isDeferringLoading()) {
207 // There might already be a timer set to call this, but it's safe to schedule it again.
208 m_deferredData.scheduleProcessDeferredData();
212 void NetworkJob::notifyStatusReceived(int status, const char* message)
214 if (shouldDeferLoading())
215 m_deferredData.deferOpen(status, message);
217 handleNotifyStatusReceived(status, message);
220 void NetworkJob::handleNotifyStatusReceived(int status, const String& message)
222 // Check for messages out of order or after cancel.
223 if ((m_statusReceived && m_extendedStatusCode != 401) || m_responseSent || m_cancelled)
229 m_statusReceived = true;
231 // Convert non-HTTP status codes to generic HTTP codes.
232 m_extendedStatusCode = status;
234 m_response.setHTTPStatusCode(200);
236 m_response.setHTTPStatusCode(404);
238 m_response.setHTTPStatusCode(status);
240 m_response.setHTTPStatusText(message);
243 void NetworkJob::notifyWMLOverride()
245 if (shouldDeferLoading())
246 m_deferredData.deferWMLOverride();
248 handleNotifyWMLOverride();
251 void NetworkJob::notifyHeadersReceived(BlackBerry::Platform::NetworkRequest::HeaderList& headers)
253 BlackBerry::Platform::NetworkRequest::HeaderList::const_iterator endIt = headers.end();
254 for (BlackBerry::Platform::NetworkRequest::HeaderList::const_iterator it = headers.begin(); it != endIt; ++it) {
255 if (shouldDeferLoading())
256 m_deferredData.deferHeaderReceived(it->first.c_str(), it->second.c_str());
258 String keyString(it->first.c_str());
260 if (equalIgnoringCase(keyString, "Location")) {
261 // Location, like all headers, is supposed to be Latin-1. But some sites (wikipedia) send it in UTF-8.
262 // All byte strings that are valid UTF-8 are also valid Latin-1 (although outside ASCII, the meaning will
263 // differ), but the reverse isn't true. So try UTF-8 first and fall back to Latin-1 if it's invalid.
264 // (High Latin-1 should be url-encoded anyway.)
266 // FIXME: maybe we should do this with other headers?
267 // Skip it for now - we don't want to rewrite random bytes unless we're sure. (Definitely don't want to
268 // rewrite cookies, for instance.) Needs more investigation.
269 valueString = String::fromUTF8(it->second.c_str());
270 if (valueString.isNull())
271 valueString = it->second.c_str();
273 valueString = it->second.c_str();
275 handleNotifyHeaderReceived(keyString, valueString);
280 void NetworkJob::notifyMultipartHeaderReceived(const char* key, const char* value)
282 if (shouldDeferLoading())
283 m_deferredData.deferMultipartHeaderReceived(key, value);
285 handleNotifyMultipartHeaderReceived(key, value);
288 void NetworkJob::notifyStringHeaderReceived(const String& key, const String& value)
290 if (shouldDeferLoading())
291 m_deferredData.deferHeaderReceived(key, value);
293 handleNotifyHeaderReceived(key, value);
296 void NetworkJob::handleNotifyHeaderReceived(const String& key, const String& value)
298 // Check for messages out of order or after cancel.
299 if (!m_statusReceived || m_responseSent || m_cancelled)
302 String lowerKey = key.lower();
303 if (lowerKey == "content-type")
304 m_contentType = value.lower();
306 if (lowerKey == "content-disposition")
307 m_contentDisposition = value;
309 if (lowerKey == "set-cookie") {
310 if (m_frame && m_frame->loader() && m_frame->loader()->client()
311 && static_cast<FrameLoaderClientBlackBerry*>(m_frame->loader()->client())->cookiesEnabled())
312 handleSetCookieHeader(value);
315 if (lowerKey == "www-authenticate")
316 handleAuthHeader(ProtectionSpaceServerHTTP, value);
317 else if (lowerKey == "proxy-authenticate" && !BlackBerry::Platform::Client::get()->getProxyAddress().empty())
318 handleAuthHeader(ProtectionSpaceProxyHTTP, value);
320 if (equalIgnoringCase(key, BlackBerry::Platform::NetworkRequest::HEADER_BLACKBERRY_FTP))
321 handleFTPHeader(value);
323 m_response.setHTTPHeaderField(key, value);
326 void NetworkJob::handleNotifyMultipartHeaderReceived(const String& key, const String& value)
328 if (!m_multipartResponse) {
329 // Create a new response based on the original set of headers + the
330 // replacement headers. We only replace the same few headers that gecko
331 // does. See netwerk/streamconv/converters/nsMultiMixedConv.cpp.
332 m_multipartResponse = adoptPtr(new ResourceResponse);
333 m_multipartResponse->setURL(m_response.url());
335 // The list of BlackBerry::Platform::replaceHeaders that we do not copy from the original
336 // response when generating a response.
337 const WebCore::HTTPHeaderMap& map = m_response.httpHeaderFields();
339 for (WebCore::HTTPHeaderMap::const_iterator it = map.begin(); it != map.end(); ++it) {
340 bool needsCopyfromOriginalResponse = true;
341 int replaceHeadersIndex = 0;
342 while (BlackBerry::Platform::MultipartStream::replaceHeaders[replaceHeadersIndex]) {
343 if (it->first.lower() == BlackBerry::Platform::MultipartStream::replaceHeaders[replaceHeadersIndex]) {
344 needsCopyfromOriginalResponse = false;
347 replaceHeadersIndex++;
349 if (needsCopyfromOriginalResponse)
350 m_multipartResponse->setHTTPHeaderField(it->first, it->second);
353 m_multipartResponse->setIsMultipartPayload(true);
355 if (key.lower() == "content-type") {
356 String contentType = value.lower();
357 m_multipartResponse->setMimeType(extractMIMETypeFromMediaType(contentType));
358 m_multipartResponse->setTextEncodingName(extractCharsetFromMediaType(contentType));
360 m_multipartResponse->setHTTPHeaderField(key, value);
364 void NetworkJob::handleSetCookieHeader(const String& value)
366 KURL url = m_response.url();
367 CookieManager& manager = cookieManager();
368 if ((manager.cookiePolicy() == CookieStorageAcceptPolicyOnlyFromMainDocumentDomain)
369 && (m_handle->firstRequest().firstPartyForCookies() != url)
370 && manager.getCookie(url, WithHttpOnlyCookies).isEmpty())
372 manager.setCookies(url, value);
375 void NetworkJob::notifyDataReceivedPlain(const char* buf, size_t len)
377 if (shouldDeferLoading())
378 m_deferredData.deferDataReceived(buf, len);
380 handleNotifyDataReceived(buf, len);
383 void NetworkJob::handleNotifyDataReceived(const char* buf, size_t len)
385 // Check for messages out of order or after cancel.
386 if ((!m_isFile && !m_statusReceived) || m_cancelled)
392 // The loadFile API sets the override content type,
393 // this will always be used as the content type and should not be overridden.
394 if (!m_dataReceived && !m_isOverrideContentType) {
395 bool shouldSniff = true;
397 // Don't bother sniffing the content type of a file that
398 // is on a file system if it has a MIME mappable file extension.
399 // The file extension is likely to be correct.
401 String urlFilename = m_response.url().lastPathComponent();
402 size_t pos = urlFilename.reverseFind('.');
403 if (pos != WTF::notFound) {
404 String extension = urlFilename.substring(pos + 1);
405 String mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
406 if (!mimeType.isEmpty())
412 MIMESniffer sniffer = MIMESniffer(m_contentType.latin1().data(), MIMETypeRegistry::isSupportedImageResourceMIMEType(m_contentType));
413 if (const char* type = sniffer.sniff(buf, std::min(len, sniffer.dataSize())))
414 m_sniffedMimeType = String(type);
418 m_dataReceived = true;
420 // Protect against reentrancy.
421 updateDeferLoadingCount(1);
423 if (shouldSendClientData()) {
424 sendResponseIfNeeded();
425 sendMultipartResponseIfNeeded();
426 if (isClientAvailable()) {
427 RecursionGuard guard(m_callingClient);
428 m_handle->client()->didReceiveData(m_handle.get(), buf, len, len);
432 updateDeferLoadingCount(-1);
435 void NetworkJob::notifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
437 if (shouldDeferLoading())
438 m_deferredData.deferDataSent(bytesSent, totalBytesToBeSent);
440 handleNotifyDataSent(bytesSent, totalBytesToBeSent);
443 void NetworkJob::handleNotifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
448 // Protect against reentrancy.
449 updateDeferLoadingCount(1);
451 if (isClientAvailable()) {
452 RecursionGuard guard(m_callingClient);
453 m_handle->client()->didSendData(m_handle.get(), bytesSent, totalBytesToBeSent);
456 updateDeferLoadingCount(-1);
459 void NetworkJob::notifyClose(int status)
461 if (shouldDeferLoading())
462 m_deferredData.deferClose(status);
464 handleNotifyClose(status);
467 void NetworkJob::handleNotifyClose(int status)
473 if (!m_statusReceived) {
474 // Connection failed before sending notifyStatusReceived: use generic NetworkError.
475 notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusNetworkError, 0);
478 // If an HTTP authentication-enabled request is successful, save
479 // the credentials for later reuse. If the request fails, delete
480 // the saved credentials.
481 if (!isError(m_extendedStatusCode))
483 else if (isUnauthorized(m_extendedStatusCode))
486 if (shouldNotifyClientFinished()) {
487 if (isRedirect(m_extendedStatusCode) && (m_redirectCount >= s_redirectMaximum))
488 m_extendedStatusCode = BlackBerry::Platform::FilterStream::StatusTooManyRedirects;
490 sendResponseIfNeeded();
491 if (isClientAvailable()) {
493 RecursionGuard guard(m_callingClient);
494 if (isError(m_extendedStatusCode) && !m_dataReceived) {
495 String domain = m_extendedStatusCode < 0 ? ResourceError::platformErrorDomain : ResourceError::httpErrorDomain;
496 ResourceError error(domain, m_extendedStatusCode, m_response.url().string(), m_response.httpStatusText());
497 m_handle->client()->didFail(m_handle.get(), error);
499 m_handle->client()->didFinishLoading(m_handle.get(), 0);
504 // Whoever called notifyClose still have a reference to the job, so
505 // schedule the deletion with a timer.
506 m_deleteJobTimer.startOneShot(0);
508 // Detach from the ResourceHandle in any case.
510 m_multipartResponse = nullptr;
513 bool NetworkJob::shouldNotifyClientFinished()
515 if (m_redirectCount >= s_redirectMaximum)
518 if (m_needsRetryAsFTPDirectory && retryAsFTPDirectory())
521 if (isRedirect(m_extendedStatusCode) && handleRedirect())
527 bool NetworkJob::retryAsFTPDirectory()
529 m_needsRetryAsFTPDirectory = false;
532 ResourceRequest newRequest = m_handle->firstRequest();
533 KURL url = newRequest.url();
534 url.setPath(url.path() + "/");
535 newRequest.setURL(url);
536 newRequest.setMustHandleInternally(true);
539 handleNotifyHeaderReceived("Location", url.string());
541 return startNewJobWithRequest(newRequest);
544 bool NetworkJob::startNewJobWithRequest(ResourceRequest& newRequest, bool increasRedirectCount)
546 if (isClientAvailable()) {
547 RecursionGuard guard(m_callingClient);
548 m_handle->client()->willSendRequest(m_handle.get(), newRequest, m_response);
550 // m_cancelled can become true if the url fails the policy check.
551 // newRequest can be cleared when the redirect is rejected.
552 if (m_cancelled || newRequest.isEmpty())
556 // Pass the ownership of the ResourceHandle to the new NetworkJob.
557 RefPtr<ResourceHandle> handle = m_handle;
559 m_multipartResponse = nullptr;
561 NetworkManager::instance()->startJob(m_playerId,
568 increasRedirectCount ? m_redirectCount + 1 : m_redirectCount);
572 bool NetworkJob::handleRedirect()
578 String location = m_response.httpHeaderField("Location");
579 if (location.isNull())
582 KURL newURL(m_response.url(), location);
583 if (!newURL.isValid())
586 ResourceRequest newRequest = m_handle->firstRequest();
587 newRequest.setURL(newURL);
588 newRequest.setMustHandleInternally(true);
590 String method = newRequest.httpMethod().upper();
591 if ((method != "GET") && (method != "HEAD")) {
592 newRequest.setHTTPMethod("GET");
593 newRequest.setHTTPBody(0);
594 newRequest.setHTTPHeaderField("Content-Length", String());
595 newRequest.setHTTPHeaderField("Content-Type", String());
598 // Do not send existing credentials with the new request.
599 m_handle->getInternal()->m_currentWebChallenge.nullify();
601 return startNewJobWithRequest(newRequest, true);
604 void NetworkJob::sendResponseIfNeeded()
609 m_responseSent = true;
611 if (isError(m_extendedStatusCode) && !m_dataReceived)
614 String urlFilename = m_response.url().lastPathComponent();
616 // Get the MIME type that was set by the content sniffer
617 // if there's no custom sniffer header, try to set it from the Content-Type header
618 // if this fails, guess it from extension.
619 String mimeType = m_sniffedMimeType;
620 if (m_isFTP && m_isFTPDir)
621 mimeType = "application/x-ftp-directory";
622 if (mimeType.isNull())
623 mimeType = extractMIMETypeFromMediaType(m_contentType);
624 if (mimeType.isNull())
625 mimeType = MIMETypeRegistry::getMIMETypeForPath(urlFilename);
626 m_response.setMimeType(mimeType);
628 // Set encoding from Content-Type header.
629 m_response.setTextEncodingName(extractCharsetFromMediaType(m_contentType));
631 // Set content length from header.
632 String contentLength = m_response.httpHeaderField("Content-Length");
633 if (!contentLength.isNull())
634 m_response.setExpectedContentLength(contentLength.toInt64());
636 // Set suggested filename for downloads from the Content-Disposition header; if this fails,
637 // fill it in from the url and sniffed mime type;Skip this for data and about URLs,
638 // because they have no Content-Disposition header and the format is wrong to be a filename.
639 if (!m_isData && !m_isAbout) {
640 String suggestedFilename = filenameFromHTTPContentDisposition(m_contentDisposition);
641 if (suggestedFilename.isEmpty()) {
642 // Check and see if an extension already exists.
643 String mimeExtension = MIMETypeRegistry::getPreferredExtensionForMIMEType(mimeType);
644 if (urlFilename.isEmpty()) {
645 if (mimeExtension.isEmpty()) // No extension found for the mimeType.
646 suggestedFilename = String("Untitled");
648 suggestedFilename = String("Untitled") + "." + mimeExtension;
650 if (urlFilename.reverseFind('.') == notFound && !mimeExtension.isEmpty())
651 suggestedFilename = urlFilename + '.' + mimeExtension;
653 suggestedFilename = urlFilename;
656 m_response.setSuggestedFilename(suggestedFilename);
659 // Make sure local files aren't cached, since this just duplicates them.
660 if (m_isFile || m_isData || m_isAbout)
661 m_response.setHTTPHeaderField("Cache-Control", "no-cache");
663 if (isClientAvailable()) {
664 RecursionGuard guard(m_callingClient);
665 m_handle->client()->didReceiveResponse(m_handle.get(), m_response);
669 void NetworkJob::sendMultipartResponseIfNeeded()
671 if (m_multipartResponse && isClientAvailable()) {
672 m_handle->client()->didReceiveResponse(m_handle.get(), *m_multipartResponse);
673 m_multipartResponse = nullptr;
677 void NetworkJob::parseData()
681 String contentType("text/plain;charset=US-ASCII");
683 String data(m_response.url().string().substring(5));
684 Vector<String> hparts;
685 bool isBase64 = false;
687 size_t index = data.find(',');
688 if (index != notFound && index > 0) {
689 contentType = data.left(index).lower();
690 data = data.substring(index + 1);
692 contentType.split(';', hparts);
693 Vector<String>::iterator i;
694 for (i = hparts.begin(); i != hparts.end(); ++i) {
695 if (i->stripWhiteSpace().lower() == "base64") {
700 int position = i - hparts.begin();
701 hparts.remove(position);
702 i = hparts.begin() + position;
705 } while (i != hparts.end());
709 contentType = String();
710 for (i = hparts.begin(); i != hparts.end(); ++i) {
711 if (i > hparts.begin())
717 data = data.substring(1); // Broken header.
720 CString latin = data.latin1();
721 escapeDecode(latin.data(), latin.length(), result);
725 String s(result.data(), result.size());
726 CString latin = s.removeCharacters(isSpaceOrNewline).latin1();
728 result.append(latin.data(), latin.length());
729 Vector<char> bytesOut;
730 if (base64Decode(result, bytesOut))
731 result.swap(bytesOut);
736 notifyStatusReceived(result.isEmpty() ? 404 : 200, 0);
737 notifyStringHeaderReceived("Content-Type", contentType);
738 notifyStringHeaderReceived("Content-Length", String::number(result.size()));
739 notifyDataReceivedPlain(result.data(), result.size());
740 notifyClose(BlackBerry::Platform::FilterStream::StatusSuccess);
743 bool NetworkJob::handleAuthHeader(const ProtectionSpaceServerType space, const String& header)
748 if (!m_handle->getInternal()->m_currentWebChallenge.isNull())
751 if (header.isEmpty())
754 if (equalIgnoringCase(header, "ntlm"))
755 sendRequestWithCredentials(space, ProtectionSpaceAuthenticationSchemeNTLM, "NTLM");
757 // Extract the auth scheme and realm from the header.
758 size_t spacePos = header.find(' ');
759 if (spacePos == notFound) {
760 LOG(Network, "%s-Authenticate field '%s' badly formatted: missing scheme.", space == ProtectionSpaceServerHTTP ? "WWW" : "Proxy", header.utf8().data());
764 String scheme = header.left(spacePos);
766 ProtectionSpaceAuthenticationScheme protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeDefault;
767 if (equalIgnoringCase(scheme, "basic"))
768 protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
769 else if (equalIgnoringCase(scheme, "digest"))
770 protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
776 size_t realmPos = header.findIgnoringCase("realm=", spacePos);
777 if (realmPos == notFound) {
778 LOG(Network, "%s-Authenticate field '%s' badly formatted: missing realm.", space == ProtectionSpaceServerHTTP ? "WWW" : "Proxy", header.utf8().data());
781 size_t beginPos = realmPos + 6;
782 String realm = header.right(header.length() - beginPos);
783 if (realm.startsWith("\"")) {
785 size_t endPos = header.find("\"", beginPos);
786 if (endPos == notFound) {
787 LOG(Network, "%s-Authenticate field '%s' badly formatted: invalid realm.", space == ProtectionSpaceServerHTTP ? "WWW" : "Proxy", header.utf8().data());
790 realm = header.substring(beginPos, endPos - beginPos);
793 // Get the user's credentials and resend the request.
794 sendRequestWithCredentials(space, protectionSpaceScheme, realm);
799 bool NetworkJob::handleFTPHeader(const String& header)
801 size_t spacePos = header.find(' ');
802 if (spacePos == notFound)
804 String statusCode = header.left(spacePos);
805 switch (statusCode.toInt()) {
811 sendRequestWithCredentials(ProtectionSpaceServerFTP, ProtectionSpaceAuthenticationSchemeDefault, "ftp");
817 // The user might have entered an URL which point to a directory but forgot type '/',
818 // e.g., ftp://ftp.trolltech.com/qt/source where 'source' is a directory. We need to
819 // added '/' and try again.
820 if (m_handle && !m_handle->firstRequest().url().path().endsWith("/"))
821 m_needsRetryAsFTPDirectory = true;
828 bool NetworkJob::sendRequestWithCredentials(ProtectionSpaceServerType type, ProtectionSpaceAuthenticationScheme scheme, const String& realm)
834 KURL newURL = m_response.url();
835 if (!newURL.isValid())
839 if (type == ProtectionSpaceProxyHTTP) {
840 std::stringstream toPort(BlackBerry::Platform::Client::get()->getProxyPort());
843 port = m_response.url().port();
845 ProtectionSpace protectionSpace((type == ProtectionSpaceProxyHTTP) ? BlackBerry::Platform::Client::get()->getProxyAddress().c_str() : m_response.url().host()
846 , port, type, realm, scheme);
848 // We've got the scheme and realm. Now we need a username and password.
849 // First search the CredentialStorage.
850 Credential credential = CredentialStorage::get(protectionSpace);
851 if (!credential.isEmpty()) {
852 m_handle->getInternal()->m_currentWebChallenge = AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError());
853 m_handle->getInternal()->m_currentWebChallenge.setStored(true);
855 // CredentialStore is empty. Ask the user via dialog.
859 if (!m_frame || !m_frame->loader() || !m_frame->loader()->client())
862 // Before asking the user for credentials, we check if the URL contains that.
863 if (!m_handle->getInternal()->m_user.isEmpty() && !m_handle->getInternal()->m_pass.isEmpty()) {
864 username = m_handle->getInternal()->m_user.utf8().data();
865 password = m_handle->getInternal()->m_pass.utf8().data();
867 // Prevent them from been used again if they are wrong.
868 // If they are correct, they will the put into CredentialStorage.
869 m_handle->getInternal()->m_user = "";
870 m_handle->getInternal()->m_pass = "";
872 m_frame->loader()->client()->authenticationChallenge(realm, username, password);
874 if (username.isEmpty() && password.isEmpty())
877 credential = Credential(username, password, CredentialPersistenceForSession);
879 m_handle->getInternal()->m_currentWebChallenge = AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError());
882 // FIXME: Resend the resource request. Cloned from handleRedirect(). Not sure
883 // if we need everything that follows...
884 ResourceRequest newRequest = m_handle->firstRequest();
885 newRequest.setURL(newURL);
886 newRequest.setMustHandleInternally(true);
887 return startNewJobWithRequest(newRequest);
890 void NetworkJob::storeCredentials()
895 AuthenticationChallenge& challenge = m_handle->getInternal()->m_currentWebChallenge;
896 if (challenge.isNull())
899 if (challenge.isStored())
902 CredentialStorage::set(challenge.proposedCredential(), challenge.protectionSpace(), m_response.url());
903 challenge.setStored(true);
906 void NetworkJob::purgeCredentials()
911 AuthenticationChallenge& challenge = m_handle->getInternal()->m_currentWebChallenge;
912 if (challenge.isNull())
915 CredentialStorage::remove(challenge.protectionSpace());
916 challenge.setStored(false);
919 bool NetworkJob::shouldSendClientData() const
921 return (!isRedirect(m_extendedStatusCode) || !m_response.httpHeaderFields().contains("Location"))
922 && !m_needsRetryAsFTPDirectory;
925 void NetworkJob::fireDeleteJobTimer(Timer<NetworkJob>*)
927 NetworkManager::instance()->deleteJob(this);
930 void NetworkJob::handleAbout()
932 // First 6 chars are "about:".
933 String aboutWhat(m_response.url().string().substring(6));
937 bool handled = false;
938 if (aboutWhat.isEmpty() || equalIgnoringCase(aboutWhat, "blank")) {
940 } else if (equalIgnoringCase(aboutWhat, "credits")) {
941 result.append(String("<html><head><title>Open Source Credits</title> <style> .about {padding:14px;} </style> <meta name=\"viewport\" content=\"width=device-width, user-scalable=no\"></head><body>"));
942 result.append(String(BlackBerry::Platform::WEBKITCREDITS));
943 result.append(String("</body></html>"));
945 } else if (aboutWhat.startsWith("cache?query=", false)) {
946 BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
948 std::string key(aboutWhat.substring(12, aboutWhat.length() - 12).utf8().data()); // 12 is length of "cache?query=".
949 result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>"));
950 result.append(String(key.data()));
951 result.append(String("<hr>"));
952 result.append(String(client->generateHtmlFragmentForCacheHeaders(key).data()));
953 result.append(String("</body></html>"));
955 } else if (equalIgnoringCase(aboutWhat, "cache")) {
956 BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
958 result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>"));
959 result.append(String(client->generateHtmlFragmentForCacheKeys().data()));
960 result.append(String("</body></html>"));
962 #if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
963 } else if (equalIgnoringCase(aboutWhat, "cache/disable")) {
964 BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
966 client->setDiskCacheEnabled(false);
967 result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>Http disk cache is disabled.</body></html>"));
969 } else if (equalIgnoringCase(aboutWhat, "cache/enable")) {
970 BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
972 client->setDiskCacheEnabled(true);
973 result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>Http disk cache is enabled.</body></html>"));
975 } else if (equalIgnoringCase(aboutWhat, "cookie")) {
976 result.append(String("<html><head><title>BlackBerry Browser cookie information</title></head><body>"));
977 result.append(cookieManager().generateHtmlFragmentForCookies());
978 result.append(String("</body></html>"));
980 } else if (equalIgnoringCase(aboutWhat, "version")) {
981 result.append(String("<html><meta name=\"viewport\" content=\"width=device-width, user-scalable=no\"></head><body>"));
982 result.append(String(BlackBerry::Platform::BUILDTIME));
983 result.append(String("</body></html>"));
985 } else if (BlackBerry::Platform::debugSetting() > 0 && equalIgnoringCase(aboutWhat, "config")) {
986 result = configPage();
988 } else if (BlackBerry::Platform::debugSetting() > 0 && equalIgnoringCase(aboutWhat, "build")) {
989 result.append(String("<html><head><title>BlackBerry Browser Build Information</title></head><body><table>"));
990 result.append(String("<tr><td>Build Computer: </td><td>"));
991 result.append(String(BlackBerry::Platform::BUILDCOMPUTER));
992 result.append(String("</td></tr>"));
993 result.append(String("<tr><td>Build User: </td><td>"));
994 result.append(String(BlackBerry::Platform::BUILDUSER));
995 result.append(String("</td></tr>"));
996 result.append(String("<tr><td>Build Time: </td><td>"));
997 result.append(String(BlackBerry::Platform::BUILDTIME));
998 result.append(String("</td></tr><tr><td></td><td></td></tr>"));
999 result.append(String(BlackBerry::Platform::BUILDINFO_WEBKIT));
1000 result.append(String(BlackBerry::Platform::BUILDINFO_PLATFORM));
1001 result.append(String(BlackBerry::Platform::BUILDINFO_LIBWEBVIEW));
1002 result.append(String("</table></body></html>"));
1004 } else if (equalIgnoringCase(aboutWhat, "memory")) {
1005 result = memoryPage();
1011 CString resultString = result.utf8();
1012 notifyStatusReceived(404, 0);
1013 notifyStringHeaderReceived("Content-Length", String::number(resultString.length()));
1014 notifyStringHeaderReceived("Content-Type", "text/html");
1015 notifyDataReceivedPlain(resultString.data(), resultString.length());
1016 notifyClose(BlackBerry::Platform::FilterStream::StatusSuccess);
1018 // If we can not handle it, we take it as an error of invalid URL.
1019 notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusErrorInvalidUrl, 0);
1020 notifyClose(BlackBerry::Platform::FilterStream::StatusErrorInvalidUrl);
1024 } // namespace WebCore