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