[BlackBerry] WWW-Authenticate header on 200 response pops up authentication dialog
[WebKit-https.git] / Source / WebCore / platform / network / blackberry / NetworkJob.cpp
1 /*
2  * Copyright (C) 2009, 2010, 2011 Research In Motion Limited. All rights reserved.
3  *
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.
8  *
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.
13  *
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
17  */
18
19 #include "config.h"
20 #include "NetworkJob.h"
21
22 #include "AboutData.h"
23 #include "Base64.h"
24 #include "Chrome.h"
25 #include "ChromeClient.h"
26 #include "CookieManager.h"
27 #include "CredentialStorage.h"
28 #include "Frame.h"
29 #include "FrameLoaderClientBlackBerry.h"
30 #include "HTTPParsers.h"
31 #include "KURL.h"
32 #include "MIMESniffing.h"
33 #include "MIMETypeRegistry.h"
34 #include "NetworkManager.h"
35 #include "Page.h"
36 #include "ResourceHandleClient.h"
37 #include "ResourceHandleInternal.h"
38 #include "ResourceRequest.h"
39
40 #include <BlackBerryPlatformClient.h>
41 #include <BlackBerryPlatformLog.h>
42 #include <BlackBerryPlatformWebKitCredits.h>
43 #include <BuildInformation.h>
44 #include <network/MultipartStream.h>
45 #include <network/NetworkRequest.h>
46 #include <network/NetworkStreamFactory.h>
47 #include <wtf/ASCIICType.h>
48
49 namespace WebCore {
50
51 static const int s_redirectMaximum = 10;
52
53 inline static bool isInfo(int statusCode)
54 {
55     return 100 <= statusCode && statusCode < 200;
56 }
57
58 inline static bool isRedirect(int statusCode)
59 {
60     return 300 <= statusCode && statusCode < 400 && statusCode != 304;
61 }
62
63 inline static bool isUnauthorized(int statusCode)
64 {
65     return statusCode == 401;
66 }
67
68 static void escapeDecode(const char* src, int length, Vector<char>& out)
69 {
70     out.resize(length);
71     const char* const srcEnd = src + length;
72     char* dst = out.data();
73     for (; src < srcEnd; ) {
74         char inputChar = *src++;
75         if (inputChar == '%' && src + 2 <= srcEnd) {
76             int digit1 = 0;
77             char character = *src++;
78             if (isASCIIHexDigit(character))
79                 digit1 = toASCIIHexValue(character);
80
81             int digit2 = 0;
82             character = *src++;
83             if (isASCIIHexDigit(character))
84                 digit2 = toASCIIHexValue(character);
85
86             *dst++ = (digit1 << 4) | digit2;
87         } else
88             *dst++ = inputChar;
89     }
90     out.resize(dst - out.data());
91 }
92
93 NetworkJob::NetworkJob()
94     : m_playerId(0)
95     , m_loadDataTimer(this, &NetworkJob::fireLoadDataTimer)
96     , m_loadAboutTimer(this, &NetworkJob::fireLoadAboutTimer)
97     , m_deleteJobTimer(this, &NetworkJob::fireDeleteJobTimer)
98     , m_streamFactory(0)
99     , m_isFile(false)
100     , m_isData(false)
101     , m_isAbout(false)
102     , m_isFTP(false)
103     , m_isFTPDir(true)
104 #ifndef NDEBUG
105     , m_isRunning(true) // Always started immediately after creation.
106 #endif
107     , m_cancelled(false)
108     , m_statusReceived(false)
109     , m_dataReceived(false)
110     , m_responseSent(false)
111     , m_callingClient(false)
112     , m_needsRetryAsFTPDirectory(false)
113     , m_isOverrideContentType(false)
114     , m_extendedStatusCode(0)
115     , m_redirectCount(0)
116     , m_deferredData(*this)
117     , m_deferLoadingCount(0)
118     , m_frame(0)
119 {
120 }
121
122 bool NetworkJob::initialize(int playerId,
123                             const String& pageGroupName,
124                             const KURL& url,
125                             const BlackBerry::Platform::NetworkRequest& request,
126                             PassRefPtr<ResourceHandle> handle,
127                             BlackBerry::Platform::NetworkStreamFactory* streamFactory,
128                             const Frame& frame,
129                             int deferLoadingCount,
130                             int redirectCount)
131 {
132     m_playerId = playerId;
133     m_pageGroupName = pageGroupName;
134
135     m_response.setURL(url);
136     m_isFile = url.protocolIs("file") || url.protocolIs("local");
137     m_isData = url.protocolIs("data");
138     m_isAbout = url.protocolIs("about");
139     m_isFTP = url.protocolIs("ftp");
140
141     m_handle = handle;
142
143     m_streamFactory = streamFactory;
144     m_frame = &frame;
145
146     if (m_frame && m_frame->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) {
147         // In the case the frame will be detached soon, we still need to ping the server, but it is
148         // no longer safe to reference the Frame object.
149         // See http://trac.webkit.org/changeset/65910 and https://bugs.webkit.org/show_bug.cgi?id=30457.
150         m_frame = 0;
151     }
152
153     m_redirectCount = redirectCount;
154     m_deferLoadingCount = deferLoadingCount;
155
156     // We don't need to explicitly call notifyHeaderReceived, as the Content-Type
157     // will ultimately get parsed when sendResponseIfNeeded gets called.
158     if (!request.getOverrideContentType().empty()) {
159         m_contentType = String(request.getOverrideContentType().c_str());
160         m_isOverrideContentType = true;
161     }
162
163     // No need to create the streams for data and about.
164     if (m_isData || m_isAbout)
165         return true;
166
167     if (!request.getSuggestedSaveName().empty()) {
168         m_contentDisposition = "filename=";
169         m_contentDisposition += request.getSuggestedSaveName().c_str();
170     }
171
172     BlackBerry::Platform::FilterStream* wrappedStream = m_streamFactory->createNetworkStream(request, m_playerId);
173     if (!wrappedStream)
174         return false;
175     setWrappedStream(wrappedStream);
176
177     return true;
178 }
179
180 void NetworkJob::loadAboutURL()
181 {
182     m_loadAboutTimer.startOneShot(0);
183 }
184
185 int NetworkJob::cancelJob()
186 {
187     m_cancelled = true;
188
189     // Cancel jobs loading local data by killing the timer, and jobs
190     // getting data from the network by calling the inherited URLStream::cancel.
191     if (m_loadDataTimer.isActive()) {
192         m_loadDataTimer.stop();
193         notifyClose(BlackBerry::Platform::FilterStream::StatusCancelled);
194         return 0;
195     }
196
197     if (m_loadAboutTimer.isActive()) {
198         m_loadAboutTimer.stop();
199         notifyClose(BlackBerry::Platform::FilterStream::StatusCancelled);
200         return 0;
201     }
202
203     return streamCancel();
204 }
205
206 void NetworkJob::updateDeferLoadingCount(int delta)
207 {
208     m_deferLoadingCount += delta;
209     ASSERT(m_deferLoadingCount >= 0);
210
211     if (!isDeferringLoading()) {
212         // There might already be a timer set to call this, but it's safe to schedule it again.
213         m_deferredData.scheduleProcessDeferredData();
214     }
215 }
216
217 void NetworkJob::notifyStatusReceived(int status, const char* message)
218 {
219     if (shouldDeferLoading())
220         m_deferredData.deferOpen(status, message);
221     else
222         handleNotifyStatusReceived(status, message);
223 }
224
225 void NetworkJob::handleNotifyStatusReceived(int status, const String& message)
226 {
227     // Check for messages out of order or after cancel.
228     if ((m_statusReceived && m_extendedStatusCode != 401) || m_responseSent || m_cancelled)
229         return;
230
231     if (isInfo(status))
232         return; // ignore
233
234     m_statusReceived = true;
235
236     // Convert non-HTTP status codes to generic HTTP codes.
237     m_extendedStatusCode = status;
238     if (!status)
239         m_response.setHTTPStatusCode(200);
240     else if (status < 0)
241         m_response.setHTTPStatusCode(404);
242     else
243         m_response.setHTTPStatusCode(status);
244
245     m_response.setHTTPStatusText(message);
246 }
247
248 void NetworkJob::notifyHeadersReceived(BlackBerry::Platform::NetworkRequest::HeaderList& headers)
249 {
250     BlackBerry::Platform::NetworkRequest::HeaderList::const_iterator endIt = headers.end();
251     for (BlackBerry::Platform::NetworkRequest::HeaderList::const_iterator it = headers.begin(); it != endIt; ++it) {
252         if (shouldDeferLoading())
253             m_deferredData.deferHeaderReceived(it->first.c_str(), it->second.c_str());
254         else {
255             String keyString(it->first.c_str());
256             String valueString;
257             if (equalIgnoringCase(keyString, "Location")) {
258                 // Location, like all headers, is supposed to be Latin-1. But some sites (wikipedia) send it in UTF-8.
259                 // All byte strings that are valid UTF-8 are also valid Latin-1 (although outside ASCII, the meaning will
260                 // differ), but the reverse isn't true. So try UTF-8 first and fall back to Latin-1 if it's invalid.
261                 // (High Latin-1 should be url-encoded anyway.)
262                 //
263                 // FIXME: maybe we should do this with other headers?
264                 // Skip it for now - we don't want to rewrite random bytes unless we're sure. (Definitely don't want to
265                 // rewrite cookies, for instance.) Needs more investigation.
266                 valueString = String::fromUTF8(it->second.c_str());
267                 if (valueString.isNull())
268                     valueString = it->second.c_str();
269             } else
270                 valueString = it->second.c_str();
271
272             handleNotifyHeaderReceived(keyString, valueString);
273         }
274     }
275 }
276
277 void NetworkJob::notifyMultipartHeaderReceived(const char* key, const char* value)
278 {
279     if (shouldDeferLoading())
280         m_deferredData.deferMultipartHeaderReceived(key, value);
281     else
282         handleNotifyMultipartHeaderReceived(key, value);
283 }
284
285 void NetworkJob::notifyStringHeaderReceived(const String& key, const String& value)
286 {
287     if (shouldDeferLoading())
288         m_deferredData.deferHeaderReceived(key, value);
289     else
290         handleNotifyHeaderReceived(key, value);
291 }
292
293 void NetworkJob::handleNotifyHeaderReceived(const String& key, const String& value)
294 {
295     // Check for messages out of order or after cancel.
296     if (!m_statusReceived || m_responseSent || m_cancelled)
297         return;
298
299     String lowerKey = key.lower();
300     if (lowerKey == "content-type")
301         m_contentType = value.lower();
302     else if (lowerKey == "content-disposition")
303         m_contentDisposition = value;
304     else if (lowerKey == "set-cookie") {
305         // FIXME: If a tab is closed, sometimes network data will come in after the frame has been detached from its page but before it is deleted.
306         // If this happens, m_frame->page() will return 0, and m_frame->loader()->client() will be in a bad state and calling into it will crash.
307         // For now we check for this explicitly by checking m_frame->page(). But we should find out why the network job hasn't been cancelled when the frame was detached.
308         // See RIM PR 134207
309         if (m_frame && m_frame->page() && m_frame->loader() && m_frame->loader()->client()
310             && static_cast<FrameLoaderClientBlackBerry*>(m_frame->loader()->client())->cookiesEnabled())
311             handleSetCookieHeader(value);
312
313         if (m_response.httpHeaderFields().contains("Set-Cookie")) {
314             m_response.setHTTPHeaderField(key, m_response.httpHeaderField(key) + "\r\n" + value);
315             return;
316         }
317     } else if (m_extendedStatusCode == 401 && lowerKey == "www-authenticate")
318         handleAuthHeader(ProtectionSpaceServerHTTP, value);
319     else if (m_extendedStatusCode == 407 && lowerKey == "proxy-authenticate" && !BlackBerry::Platform::Client::get()->getProxyAddress().empty())
320         handleAuthHeader(ProtectionSpaceProxyHTTP, value);
321     else if (equalIgnoringCase(key, BlackBerry::Platform::NetworkRequest::HEADER_BLACKBERRY_FTP))
322         handleFTPHeader(value);
323
324     m_response.setHTTPHeaderField(key, value);
325 }
326
327 void NetworkJob::handleNotifyMultipartHeaderReceived(const String& key, const String& value)
328 {
329     if (!m_multipartResponse) {
330         // Create a new response based on the original set of headers + the
331         // replacement headers. We only replace the same few headers that gecko
332         // does. See netwerk/streamconv/converters/nsMultiMixedConv.cpp.
333         m_multipartResponse = adoptPtr(new ResourceResponse);
334         m_multipartResponse->setURL(m_response.url());
335
336         // The list of BlackBerry::Platform::replaceHeaders that we do not copy from the original
337         // response when generating a response.
338         const WebCore::HTTPHeaderMap& map = m_response.httpHeaderFields();
339
340         for (WebCore::HTTPHeaderMap::const_iterator it = map.begin(); it != map.end(); ++it) {
341             bool needsCopyfromOriginalResponse = true;
342             int replaceHeadersIndex = 0;
343             while (BlackBerry::Platform::MultipartStream::replaceHeaders[replaceHeadersIndex]) {
344                 if (it->first.lower() == BlackBerry::Platform::MultipartStream::replaceHeaders[replaceHeadersIndex]) {
345                     needsCopyfromOriginalResponse = false;
346                     break;
347                 }
348                 replaceHeadersIndex++;
349             }
350             if (needsCopyfromOriginalResponse)
351                 m_multipartResponse->setHTTPHeaderField(it->first, it->second);
352         }
353
354         m_multipartResponse->setIsMultipartPayload(true);
355     } else {
356         if (key.lower() == "content-type") {
357             String contentType = value.lower();
358             m_multipartResponse->setMimeType(extractMIMETypeFromMediaType(contentType));
359             m_multipartResponse->setTextEncodingName(extractCharsetFromMediaType(contentType));
360         }
361         m_multipartResponse->setHTTPHeaderField(key, value);
362     }
363 }
364
365 void NetworkJob::handleSetCookieHeader(const String& value)
366 {
367     KURL url = m_response.url();
368     CookieManager& manager = cookieManager();
369     if ((manager.cookiePolicy() == CookieStorageAcceptPolicyOnlyFromMainDocumentDomain)
370       && (m_handle->firstRequest().firstPartyForCookies() != url)
371       && manager.getCookie(url, WithHttpOnlyCookies).isEmpty())
372         return;
373     manager.setCookies(url, value);
374 }
375
376 void NetworkJob::notifyDataReceivedPlain(const char* buf, size_t len)
377 {
378     if (shouldDeferLoading())
379         m_deferredData.deferDataReceived(buf, len);
380     else
381         handleNotifyDataReceived(buf, len);
382 }
383
384 void NetworkJob::handleNotifyDataReceived(const char* buf, size_t len)
385 {
386     // Check for messages out of order or after cancel.
387     if ((!m_isFile && !m_statusReceived) || m_cancelled)
388         return;
389
390     if (!buf || !len)
391         return;
392
393     // The loadFile API sets the override content type,
394     // this will always be used as the content type and should not be overridden.
395     if (!m_dataReceived && !m_isOverrideContentType) {
396         bool shouldSniff = true;
397
398         // Don't bother sniffing the content type of a file that
399         // is on a file system if it has a MIME mappable file extension.
400         // The file extension is likely to be correct.
401         if (m_isFile) {
402             String urlFilename = m_response.url().lastPathComponent();
403             size_t pos = urlFilename.reverseFind('.');
404             if (pos != notFound) {
405                 String extension = urlFilename.substring(pos + 1);
406                 String mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
407                 if (!mimeType.isEmpty())
408                     shouldSniff = false;
409             }
410         }
411
412         if (shouldSniff) {
413             MIMESniffer sniffer = MIMESniffer(m_contentType.latin1().data(), MIMETypeRegistry::isSupportedImageResourceMIMEType(m_contentType));
414             if (const char* type = sniffer.sniff(buf, std::min(len, sniffer.dataSize())))
415                 m_sniffedMimeType = String(type);
416         }
417     }
418
419     m_dataReceived = true;
420
421     // Protect against reentrancy.
422     updateDeferLoadingCount(1);
423
424     if (shouldSendClientData()) {
425         sendResponseIfNeeded();
426         sendMultipartResponseIfNeeded();
427         if (isClientAvailable()) {
428             RecursionGuard guard(m_callingClient);
429             m_handle->client()->didReceiveData(m_handle.get(), buf, len, len);
430         }
431     }
432
433     updateDeferLoadingCount(-1);
434 }
435
436 void NetworkJob::notifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
437 {
438     if (shouldDeferLoading())
439         m_deferredData.deferDataSent(bytesSent, totalBytesToBeSent);
440     else
441         handleNotifyDataSent(bytesSent, totalBytesToBeSent);
442 }
443
444 void NetworkJob::handleNotifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
445 {
446     if (m_cancelled)
447         return;
448
449     // Protect against reentrancy.
450     updateDeferLoadingCount(1);
451
452     if (isClientAvailable()) {
453         RecursionGuard guard(m_callingClient);
454         m_handle->client()->didSendData(m_handle.get(), bytesSent, totalBytesToBeSent);
455     }
456
457     updateDeferLoadingCount(-1);
458 }
459
460 void NetworkJob::notifyClose(int status)
461 {
462     if (shouldDeferLoading())
463         m_deferredData.deferClose(status);
464     else
465         handleNotifyClose(status);
466 }
467
468 void NetworkJob::handleNotifyClose(int status)
469 {
470 #ifndef NDEBUG
471     m_isRunning = false;
472 #endif
473     if (!m_cancelled) {
474         if (!m_statusReceived) {
475             // Connection failed before sending notifyStatusReceived: use generic NetworkError.
476             notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusNetworkError, 0);
477         }
478
479         // If an HTTP authentication-enabled request is successful, save
480         // the credentials for later reuse. If the request fails, delete
481         // the saved credentials.
482         if (!isError(m_extendedStatusCode))
483             storeCredentials();
484         else if (isUnauthorized(m_extendedStatusCode))
485             purgeCredentials();
486
487         if (shouldNotifyClientFinished()) {
488             if (isRedirect(m_extendedStatusCode) && (m_redirectCount >= s_redirectMaximum))
489                 m_extendedStatusCode = BlackBerry::Platform::FilterStream::StatusTooManyRedirects;
490
491             sendResponseIfNeeded();
492             if (isClientAvailable()) {
493
494                 RecursionGuard guard(m_callingClient);
495                 if (isError(m_extendedStatusCode) && !m_dataReceived && m_handle->firstRequest().httpMethod() != "HEAD") {
496                     String domain = m_extendedStatusCode < 0 ? ResourceError::platformErrorDomain : ResourceError::httpErrorDomain;
497                     ResourceError error(domain, m_extendedStatusCode, m_response.url().string(), m_response.httpStatusText());
498                     m_handle->client()->didFail(m_handle.get(), error);
499                 } else
500                     m_handle->client()->didFinishLoading(m_handle.get(), 0);
501             }
502         }
503     }
504
505     // Whoever called notifyClose still have a reference to the job, so
506     // schedule the deletion with a timer.
507     m_deleteJobTimer.startOneShot(0);
508
509     // Detach from the ResourceHandle in any case.
510     m_handle = 0;
511     m_multipartResponse = nullptr;
512 }
513
514 bool NetworkJob::shouldNotifyClientFinished()
515 {
516     if (m_redirectCount >= s_redirectMaximum)
517         return true;
518
519     if (m_needsRetryAsFTPDirectory && retryAsFTPDirectory())
520         return false;
521
522     if (isRedirect(m_extendedStatusCode) && handleRedirect())
523         return false;
524
525     return true;
526 }
527
528 bool NetworkJob::retryAsFTPDirectory()
529 {
530     m_needsRetryAsFTPDirectory = false;
531
532     ASSERT(m_handle);
533     ResourceRequest newRequest = m_handle->firstRequest();
534     KURL url = newRequest.url();
535     url.setPath(url.path() + "/");
536     newRequest.setURL(url);
537     newRequest.setMustHandleInternally(true);
538
539     // Update the UI.
540     handleNotifyHeaderReceived("Location", url.string());
541
542     return startNewJobWithRequest(newRequest);
543 }
544
545 bool NetworkJob::startNewJobWithRequest(ResourceRequest& newRequest, bool increasRedirectCount)
546 {
547     if (isClientAvailable()) {
548         RecursionGuard guard(m_callingClient);
549         m_handle->client()->willSendRequest(m_handle.get(), newRequest, m_response);
550
551         // m_cancelled can become true if the url fails the policy check.
552         // newRequest can be cleared when the redirect is rejected.
553         if (m_cancelled || newRequest.isEmpty())
554             return false;
555     }
556
557     // Pass the ownership of the ResourceHandle to the new NetworkJob.
558     RefPtr<ResourceHandle> handle = m_handle;
559     m_handle = 0;
560     m_multipartResponse = nullptr;
561
562     NetworkManager::instance()->startJob(m_playerId,
563         m_pageGroupName,
564         handle,
565         newRequest,
566         m_streamFactory,
567         *m_frame,
568         m_deferLoadingCount,
569         increasRedirectCount ? m_redirectCount + 1 : m_redirectCount);
570     return true;
571 }
572
573 bool NetworkJob::handleRedirect()
574 {
575     ASSERT(m_handle);
576     if (!m_handle)
577         return false;
578
579     String location = m_response.httpHeaderField("Location");
580     if (location.isNull())
581         return false;
582
583     KURL newURL(m_response.url(), location);
584     if (!newURL.isValid())
585         return false;
586
587     ResourceRequest newRequest = m_handle->firstRequest();
588     newRequest.setURL(newURL);
589     newRequest.setMustHandleInternally(true);
590
591     String method = newRequest.httpMethod().upper();
592     if (method != "GET" && method != "HEAD") {
593         newRequest.setHTTPMethod("GET");
594         newRequest.setHTTPBody(0);
595         newRequest.clearHTTPContentLength();
596         newRequest.clearHTTPContentType();
597     }
598
599     // Do not send existing credentials with the new request.
600     m_handle->getInternal()->m_currentWebChallenge.nullify();
601
602     return startNewJobWithRequest(newRequest, true);
603 }
604
605 void NetworkJob::sendResponseIfNeeded()
606 {
607     if (m_responseSent)
608         return;
609
610     m_responseSent = true;
611
612     if (isError(m_extendedStatusCode) && !m_dataReceived)
613         return;
614
615     String urlFilename = m_response.url().lastPathComponent();
616
617     // Get the MIME type that was set by the content sniffer
618     // if there's no custom sniffer header, try to set it from the Content-Type header
619     // if this fails, guess it from extension.
620     String mimeType = m_sniffedMimeType;
621     if (m_isFTP && m_isFTPDir)
622         mimeType = "application/x-ftp-directory";
623     if (mimeType.isNull())
624         mimeType = extractMIMETypeFromMediaType(m_contentType);
625     if (mimeType.isNull())
626         mimeType = MIMETypeRegistry::getMIMETypeForPath(urlFilename);
627     m_response.setMimeType(mimeType);
628
629     // Set encoding from Content-Type header.
630     m_response.setTextEncodingName(extractCharsetFromMediaType(m_contentType));
631
632     // Set content length from header.
633     String contentLength = m_response.httpHeaderField("Content-Length");
634     if (!contentLength.isNull())
635         m_response.setExpectedContentLength(contentLength.toInt64());
636
637     // Set suggested filename for downloads from the Content-Disposition header; if this fails,
638     // fill it in from the url and sniffed mime type;Skip this for data and about URLs,
639     // because they have no Content-Disposition header and the format is wrong to be a filename.
640     if (!m_isData && !m_isAbout) {
641         String suggestedFilename = filenameFromHTTPContentDisposition(m_contentDisposition);
642         if (suggestedFilename.isEmpty()) {
643             // Check and see if an extension already exists.
644             String mimeExtension = MIMETypeRegistry::getPreferredExtensionForMIMEType(mimeType);
645             if (urlFilename.isEmpty()) {
646                 if (mimeExtension.isEmpty()) // No extension found for the mimeType.
647                     suggestedFilename = String("Untitled");
648                 else
649                     suggestedFilename = String("Untitled") + "." + mimeExtension;
650             } else {
651                 if (urlFilename.reverseFind('.') == notFound && !mimeExtension.isEmpty())
652                    suggestedFilename = urlFilename + '.' + mimeExtension;
653                 else
654                    suggestedFilename = urlFilename;
655             }
656         }
657         m_response.setSuggestedFilename(suggestedFilename);
658     }
659
660     // Don't cache resources for "about:"
661     if (m_isAbout)
662         m_response.setHTTPHeaderField("Cache-Control", "no-cache");
663
664     if (isClientAvailable()) {
665         RecursionGuard guard(m_callingClient);
666         m_handle->client()->didReceiveResponse(m_handle.get(), m_response);
667     }
668 }
669
670 void NetworkJob::sendMultipartResponseIfNeeded()
671 {
672     if (m_multipartResponse && isClientAvailable()) {
673         m_handle->client()->didReceiveResponse(m_handle.get(), *m_multipartResponse);
674         m_multipartResponse = nullptr;
675     }
676 }
677
678 void NetworkJob::parseData()
679 {
680     Vector<char> result;
681
682     String contentType("text/plain;charset=US-ASCII");
683
684     String data(m_response.url().string().substring(5));
685     Vector<String> hparts;
686     bool isBase64 = false;
687
688     size_t index = data.find(',');
689     if (index != notFound && index > 0) {
690         contentType = data.left(index).lower();
691         data = data.substring(index + 1);
692
693         contentType.split(';', hparts);
694         Vector<String>::iterator i;
695         for (i = hparts.begin(); i != hparts.end(); ++i) {
696             if (i->stripWhiteSpace().lower() == "base64") {
697                 isBase64 = true;
698                 String value = *i;
699                 do {
700                     if (*i == value) {
701                         int position = i - hparts.begin();
702                         hparts.remove(position);
703                         i = hparts.begin() + position;
704                     } else
705                         ++i;
706                 } while (i != hparts.end());
707                 break;
708             }
709         }
710         contentType = String();
711         for (i = hparts.begin(); i != hparts.end(); ++i) {
712             if (i > hparts.begin())
713                 contentType += ",";
714
715             contentType += *i;
716         }
717     } else if (!index)
718         data = data.substring(1); // Broken header.
719
720     {
721         CString latin = data.latin1();
722         escapeDecode(latin.data(), latin.length(), result);
723     }
724
725     if (isBase64) {
726         String s(result.data(), result.size());
727         CString latin = s.removeCharacters(isSpaceOrNewline).latin1();
728         result.clear();
729         result.append(latin.data(), latin.length());
730         Vector<char> bytesOut;
731         if (base64Decode(result, bytesOut))
732             result.swap(bytesOut);
733         else
734             result.clear();
735     }
736
737     notifyStatusReceived(result.isEmpty() ? 404 : 200, 0);
738     notifyStringHeaderReceived("Content-Type", contentType);
739     notifyStringHeaderReceived("Content-Length", String::number(result.size()));
740     notifyDataReceivedPlain(result.data(), result.size());
741     notifyClose(BlackBerry::Platform::FilterStream::StatusSuccess);
742 }
743
744 bool NetworkJob::handleAuthHeader(const ProtectionSpaceServerType space, const String& header)
745 {
746     if (!m_handle)
747         return false;
748
749     if (!m_handle->getInternal()->m_currentWebChallenge.isNull())
750         return false;
751
752     if (header.isEmpty())
753         return false;
754
755     if (equalIgnoringCase(header, "ntlm"))
756         sendRequestWithCredentials(space, ProtectionSpaceAuthenticationSchemeNTLM, "NTLM");
757
758     // Extract the auth scheme and realm from the header.
759     size_t spacePos = header.find(' ');
760     if (spacePos == notFound) {
761         LOG(Network, "%s-Authenticate field '%s' badly formatted: missing scheme.", space == ProtectionSpaceServerHTTP ? "WWW" : "Proxy", header.utf8().data());
762         return false;
763     }
764
765     String scheme = header.left(spacePos);
766
767     ProtectionSpaceAuthenticationScheme protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeDefault;
768     if (equalIgnoringCase(scheme, "basic"))
769         protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
770     else if (equalIgnoringCase(scheme, "digest"))
771         protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
772     else {
773         notImplemented();
774         return false;
775     }
776
777     size_t realmPos = header.findIgnoringCase("realm=", spacePos);
778     if (realmPos == notFound) {
779         LOG(Network, "%s-Authenticate field '%s' badly formatted: missing realm.", space == ProtectionSpaceServerHTTP ? "WWW" : "Proxy", header.utf8().data());
780         return false;
781     }
782     size_t beginPos = realmPos + 6;
783     String realm  = header.right(header.length() - beginPos);
784     if (realm.startsWith("\"")) {
785         beginPos += 1;
786         size_t endPos = header.find("\"", beginPos);
787         if (endPos == notFound) {
788             LOG(Network, "%s-Authenticate field '%s' badly formatted: invalid realm.", space == ProtectionSpaceServerHTTP ? "WWW" : "Proxy", header.utf8().data());
789             return false;
790         }
791         realm = header.substring(beginPos, endPos - beginPos);
792     }
793
794     // Get the user's credentials and resend the request.
795     sendRequestWithCredentials(space, protectionSpaceScheme, realm);
796
797     return true;
798 }
799
800 bool NetworkJob::handleFTPHeader(const String& header)
801 {
802     size_t spacePos = header.find(' ');
803     if (spacePos == notFound)
804         return false;
805     String statusCode = header.left(spacePos);
806     switch (statusCode.toInt()) {
807     case 213:
808         m_isFTPDir = false;
809         break;
810     case 530:
811         purgeCredentials();
812         sendRequestWithCredentials(ProtectionSpaceServerFTP, ProtectionSpaceAuthenticationSchemeDefault, "ftp");
813         break;
814     case 230:
815         storeCredentials();
816         break;
817     case 550:
818         // The user might have entered an URL which point to a directory but forgot type '/',
819         // e.g., ftp://ftp.trolltech.com/qt/source where 'source' is a directory. We need to
820         // added '/' and try again.
821         if (m_handle && !m_handle->firstRequest().url().path().endsWith("/"))
822             m_needsRetryAsFTPDirectory = true;
823         break;
824     }
825
826     return true;
827 }
828
829 bool NetworkJob::sendRequestWithCredentials(ProtectionSpaceServerType type, ProtectionSpaceAuthenticationScheme scheme, const String& realm)
830 {
831     ASSERT(m_handle);
832     if (!m_handle)
833         return false;
834
835     KURL newURL = m_response.url();
836     if (!newURL.isValid())
837         return false;
838
839     int port = 0;
840     if (type == ProtectionSpaceProxyHTTP) {
841         std::stringstream toPort(BlackBerry::Platform::Client::get()->getProxyPort());
842         toPort >> port;
843     } else
844         port = m_response.url().port();
845
846     ProtectionSpace protectionSpace((type == ProtectionSpaceProxyHTTP) ? BlackBerry::Platform::Client::get()->getProxyAddress().c_str() : m_response.url().host()
847             , port, type, realm, scheme);
848
849     // We've got the scheme and realm. Now we need a username and password.
850     // First search the CredentialStorage.
851     Credential credential = CredentialStorage::get(protectionSpace);
852     if (!credential.isEmpty()) {
853         m_handle->getInternal()->m_currentWebChallenge = AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError());
854         m_handle->getInternal()->m_currentWebChallenge.setStored(true);
855     } else {
856         // CredentialStore is empty. Ask the user via dialog.
857         String username;
858         String password;
859
860         if (!m_frame || !m_frame->loader() || !m_frame->loader()->client())
861             return false;
862
863         if (type == ProtectionSpaceProxyHTTP) {
864             username = BlackBerry::Platform::Client::get()->getProxyUsername().c_str();
865             password = BlackBerry::Platform::Client::get()->getProxyPassword().c_str();
866         }
867
868         if (username.isEmpty() || password.isEmpty()) {
869             // Before asking the user for credentials, we check if the URL contains that.
870             if (!m_handle->getInternal()->m_user.isEmpty() && !m_handle->getInternal()->m_pass.isEmpty()) {
871                 username = m_handle->getInternal()->m_user.utf8().data();
872                 password = m_handle->getInternal()->m_pass.utf8().data();
873
874                 // Prevent them from been used again if they are wrong.
875                 // If they are correct, they will the put into CredentialStorage.
876                 m_handle->getInternal()->m_user = "";
877                 m_handle->getInternal()->m_pass = "";
878             } else {
879                 Credential inputCredential = m_frame->page()->chrome()->client()->platformPageClient()->authenticationChallenge(newURL, protectionSpace);
880                 username = inputCredential.user();
881                 password = inputCredential.password();
882             }
883         }
884
885         if (username.isEmpty() && password.isEmpty())
886             return false;
887
888         credential = Credential(username, password, CredentialPersistenceForSession);
889
890         m_handle->getInternal()->m_currentWebChallenge = AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError());
891     }
892
893     // FIXME: Resend the resource request. Cloned from handleRedirect(). Not sure
894     // if we need everything that follows...
895     ResourceRequest newRequest = m_handle->firstRequest();
896     newRequest.setURL(newURL);
897     newRequest.setMustHandleInternally(true);
898     return startNewJobWithRequest(newRequest);
899 }
900
901 void NetworkJob::storeCredentials()
902 {
903     if (!m_handle)
904         return;
905
906     AuthenticationChallenge& challenge = m_handle->getInternal()->m_currentWebChallenge;
907     if (challenge.isNull())
908         return;
909
910     if (challenge.isStored())
911         return;
912
913     CredentialStorage::set(challenge.proposedCredential(), challenge.protectionSpace(), m_response.url());
914     challenge.setStored(true);
915 }
916
917 void NetworkJob::purgeCredentials()
918 {
919     if (!m_handle)
920         return;
921
922     AuthenticationChallenge& challenge = m_handle->getInternal()->m_currentWebChallenge;
923     if (challenge.isNull())
924         return;
925
926     CredentialStorage::remove(challenge.protectionSpace());
927     challenge.setStored(false);
928 }
929
930 bool NetworkJob::shouldSendClientData() const
931 {
932     return (!isRedirect(m_extendedStatusCode) || !m_response.httpHeaderFields().contains("Location"))
933            && !m_needsRetryAsFTPDirectory;
934 }
935
936 void NetworkJob::fireDeleteJobTimer(Timer<NetworkJob>*)
937 {
938     NetworkManager::instance()->deleteJob(this);
939 }
940
941 void NetworkJob::handleAbout()
942 {
943     // First 6 chars are "about:".
944     String aboutWhat(m_response.url().string().substring(6));
945
946     String result;
947
948     bool handled = false;
949     if (equalIgnoringCase(aboutWhat, "blank")) {
950         handled = true;
951     } else if (equalIgnoringCase(aboutWhat, "credits")) {
952         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>"));
953         result.append(String(BlackBerry::Platform::WEBKITCREDITS));
954         result.append(String("</body></html>"));
955         handled = true;
956     } else if (aboutWhat.startsWith("cache?query=", false)) {
957         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
958         ASSERT(client);
959         std::string key(aboutWhat.substring(12, aboutWhat.length() - 12).utf8().data()); // 12 is length of "cache?query=".
960         result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>"));
961         result.append(String(key.data()));
962         result.append(String("<hr>"));
963         result.append(String(client->generateHtmlFragmentForCacheHeaders(key).data()));
964         result.append(String("</body></html>"));
965         handled = true;
966     } else if (equalIgnoringCase(aboutWhat, "cache")) {
967         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
968         ASSERT(client);
969         result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>"));
970         result.append(String(client->generateHtmlFragmentForCacheKeys().data()));
971         result.append(String("</body></html>"));
972         handled = true;
973 #if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
974     } else if (equalIgnoringCase(aboutWhat, "cache/disable")) {
975         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
976         ASSERT(client);
977         client->setDiskCacheEnabled(false);
978         result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>Http disk cache is disabled.</body></html>"));
979         handled = true;
980     } else if (equalIgnoringCase(aboutWhat, "cache/enable")) {
981         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
982         ASSERT(client);
983         client->setDiskCacheEnabled(true);
984         result.append(String("<html><head><title>BlackBerry Browser Disk Cache</title></head><body>Http disk cache is enabled.</body></html>"));
985         handled = true;
986     } else if (equalIgnoringCase(aboutWhat, "cookie")) {
987         result.append(String("<html><head><title>BlackBerry Browser cookie information</title></head><body>"));
988         result.append(cookieManager().generateHtmlFragmentForCookies());
989         result.append(String("</body></html>"));
990         handled = true;
991     } else if (equalIgnoringCase(aboutWhat, "version")) {
992         result.append(String("<html><meta name=\"viewport\" content=\"width=device-width, user-scalable=no\"></head><body>"));
993         result.append(String(BlackBerry::Platform::BUILDTIME));
994         result.append(String("</body></html>"));
995         handled = true;
996     } else if (BlackBerry::Platform::debugSetting() > 0 && equalIgnoringCase(aboutWhat, "config")) {
997         result = configPage();
998         handled = true;
999     } else if (BlackBerry::Platform::debugSetting() > 0 && equalIgnoringCase(aboutWhat, "build")) {
1000         result.append(String("<html><head><title>BlackBerry Browser Build Information</title></head><body><table>"));
1001         result.append(String("<tr><td>Build Computer:  </td><td>"));
1002         result.append(String(BlackBerry::Platform::BUILDCOMPUTER));
1003         result.append(String("</td></tr>"));
1004         result.append(String("<tr><td>Build User:  </td><td>"));
1005         result.append(String(BlackBerry::Platform::BUILDUSER));
1006         result.append(String("</td></tr>"));
1007         result.append(String("<tr><td>Build Time:  </td><td>"));
1008         result.append(String(BlackBerry::Platform::BUILDTIME));
1009         result.append(String("</td></tr><tr><td></td><td></td></tr>"));
1010         result.append(String(BlackBerry::Platform::BUILDINFO_WEBKIT));
1011         result.append(String(BlackBerry::Platform::BUILDINFO_PLATFORM));
1012         result.append(String(BlackBerry::Platform::BUILDINFO_LIBWEBVIEW));
1013         result.append(String("</table></body></html>"));
1014         handled = true;
1015     } else if (equalIgnoringCase(aboutWhat, "memory")) {
1016         result = memoryPage();
1017         handled = true;
1018 #endif
1019     }
1020     if (handled) {
1021         CString resultString = result.utf8();
1022         notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusSuccess, 0);
1023         notifyStringHeaderReceived("Content-Length", String::number(resultString.length()));
1024         notifyStringHeaderReceived("Content-Type", "text/html");
1025         notifyDataReceivedPlain(resultString.data(), resultString.length());
1026         notifyClose(BlackBerry::Platform::FilterStream::StatusSuccess);
1027     } else {
1028         // If we can not handle it, we take it as an error of invalid URL.
1029         notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusErrorInvalidUrl, 0);
1030         notifyClose(BlackBerry::Platform::FilterStream::StatusErrorInvalidUrl);
1031     }
1032 }
1033
1034 } // namespace WebCore