60cc86c3f209e57b5a25a26f8fbe93a70c25c2d8
[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 "CookieManager.h"
25 #include "CredentialStorage.h"
26 #include "Frame.h"
27 #include "FrameLoaderClientBlackBerry.h"
28 #include "HTTPParsers.h"
29 #include "KURL.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"
36
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>
45
46 namespace WebCore {
47
48 static const int s_redirectMaximum = 10;
49
50 inline static bool isInfo(int statusCode)
51 {
52     return 100 <= statusCode && statusCode < 200;
53 }
54
55 inline static bool isRedirect(int statusCode)
56 {
57     return 300 <= statusCode && statusCode < 400 && statusCode != 304;
58 }
59
60 inline static bool isUnauthorized(int statusCode)
61 {
62     return statusCode == 401;
63 }
64
65 static void escapeDecode(const char* src, int length, Vector<char>& out)
66 {
67     out.resize(length);
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) {
73             int digit1 = 0;
74             char character = *src++;
75             if (isASCIIHexDigit(character))
76                 digit1 = toASCIIHexValue(character);
77
78             int digit2 = 0;
79             character = *src++;
80             if (isASCIIHexDigit(character))
81                 digit2 = toASCIIHexValue(character);
82
83             *dst++ = (digit1 << 4) | digit2;
84         } else
85             *dst++ = inputChar;
86     }
87     out.resize(dst - out.data());
88 }
89
90 NetworkJob::NetworkJob()
91     : m_playerId(0)
92     , m_loadDataTimer(this, &NetworkJob::fireLoadDataTimer)
93     , m_loadAboutTimer(this, &NetworkJob::fireLoadAboutTimer)
94     , m_deleteJobTimer(this, &NetworkJob::fireDeleteJobTimer)
95     , m_streamFactory(0)
96     , m_isFile(false)
97     , m_isData(false)
98     , m_isAbout(false)
99     , m_isFTP(false)
100     , m_isFTPDir(true)
101 #ifndef NDEBUG
102     , m_isRunning(true) // Always started immediately after creation.
103 #endif
104     , m_cancelled(false)
105     , m_statusReceived(false)
106     , m_dataReceived(false)
107     , m_responseSent(false)
108     , m_callingClient(false)
109     , m_isXHR(false)
110     , m_needsRetryAsFTPDirectory(false)
111     , m_isOverrideContentType(false)
112     , m_extendedStatusCode(0)
113     , m_redirectCount(0)
114     , m_deferredData(*this)
115     , m_deferLoadingCount(0)
116     , m_frame(0)
117 {
118 }
119
120 bool NetworkJob::initialize(int playerId,
121                             const String& pageGroupName,
122                             const KURL& url,
123                             const BlackBerry::Platform::NetworkRequest& request,
124                             PassRefPtr<ResourceHandle> handle,
125                             BlackBerry::Platform::NetworkStreamFactory* streamFactory,
126                             const Frame& frame,
127                             int deferLoadingCount,
128                             int redirectCount)
129 {
130     m_playerId = playerId;
131     m_pageGroupName = pageGroupName;
132
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");
138
139     m_handle = handle;
140
141     m_streamFactory = streamFactory;
142     m_frame = &frame;
143
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.
148         m_frame = 0;
149     }
150
151     m_redirectCount = redirectCount;
152     m_deferLoadingCount = deferLoadingCount;
153
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;
159     }
160
161     // No need to create the streams for data and about.
162     if (m_isData || m_isAbout)
163         return true;
164
165     BlackBerry::Platform::FilterStream* wrappedStream = m_streamFactory->createNetworkStream(request, m_playerId);
166     if (!wrappedStream)
167         return false;
168     setWrappedStream(wrappedStream);
169
170     m_isXHR = request.getTargetType() == BlackBerry::Platform::NetworkRequest::TargetIsXMLHTTPRequest;
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::notifyWMLOverride()
244 {
245     if (shouldDeferLoading())
246         m_deferredData.deferWMLOverride();
247     else
248         handleNotifyWMLOverride();
249 }
250
251 void NetworkJob::notifyHeadersReceived(BlackBerry::Platform::NetworkRequest::HeaderList& headers)
252 {
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());
257         else {
258             String keyString(it->first.c_str());
259             String valueString;
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.)
265                 //
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();
272             } else
273                 valueString = it->second.c_str();
274
275             handleNotifyHeaderReceived(keyString, valueString);
276         }
277     }
278 }
279
280 void NetworkJob::notifyMultipartHeaderReceived(const char* key, const char* value)
281 {
282     if (shouldDeferLoading())
283         m_deferredData.deferMultipartHeaderReceived(key, value);
284     else
285         handleNotifyMultipartHeaderReceived(key, value);
286 }
287
288 void NetworkJob::notifyStringHeaderReceived(const String& key, const String& value)
289 {
290     if (shouldDeferLoading())
291         m_deferredData.deferHeaderReceived(key, value);
292     else
293         handleNotifyHeaderReceived(key, value);
294 }
295
296 void NetworkJob::handleNotifyHeaderReceived(const String& key, const String& value)
297 {
298     // Check for messages out of order or after cancel.
299     if (!m_statusReceived || m_responseSent || m_cancelled)
300         return;
301
302     String lowerKey = key.lower();
303     if (lowerKey == "content-type")
304         m_contentType = value.lower();
305
306     if (lowerKey == "content-disposition")
307         m_contentDisposition = value;
308
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);
313     }
314
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);
319
320     if (equalIgnoringCase(key, BlackBerry::Platform::NetworkRequest::HEADER_BLACKBERRY_FTP))
321         handleFTPHeader(value);
322
323     m_response.setHTTPHeaderField(key, value);
324 }
325
326 void NetworkJob::handleNotifyMultipartHeaderReceived(const String& key, const String& value)
327 {
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());
334
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();
338
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;
345                     break;
346                 }
347                 replaceHeadersIndex++;
348             }
349             if (needsCopyfromOriginalResponse)
350                 m_multipartResponse->setHTTPHeaderField(it->first, it->second);
351         }
352
353         m_multipartResponse->setIsMultipartPayload(true);
354     } else {
355         if (key.lower() == "content-type") {
356             String contentType = value.lower();
357             m_multipartResponse->setMimeType(extractMIMETypeFromMediaType(contentType));
358             m_multipartResponse->setTextEncodingName(extractCharsetFromMediaType(contentType));
359         }
360         m_multipartResponse->setHTTPHeaderField(key, value);
361     }
362 }
363
364 void NetworkJob::handleSetCookieHeader(const String& value)
365 {
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())
371         return;
372     manager.setCookies(url, value);
373 }
374
375 void NetworkJob::notifyDataReceivedPlain(const char* buf, size_t len)
376 {
377     if (shouldDeferLoading())
378         m_deferredData.deferDataReceived(buf, len);
379     else
380         handleNotifyDataReceived(buf, len);
381 }
382
383 void NetworkJob::handleNotifyDataReceived(const char* buf, size_t len)
384 {
385     // Check for messages out of order or after cancel.
386     if ((!m_isFile && !m_statusReceived) || m_cancelled)
387         return;
388
389     if (!buf || !len)
390         return;
391
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;
396
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.
400         if (m_isFile) {
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())
407                     shouldSniff = false;
408             }
409         }
410
411         if (shouldSniff) {
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);
415         }
416     }
417
418     m_dataReceived = true;
419
420     // Protect against reentrancy.
421     updateDeferLoadingCount(1);
422
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);
429         }
430     }
431
432     updateDeferLoadingCount(-1);
433 }
434
435 void NetworkJob::notifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
436 {
437     if (shouldDeferLoading())
438         m_deferredData.deferDataSent(bytesSent, totalBytesToBeSent);
439     else
440         handleNotifyDataSent(bytesSent, totalBytesToBeSent);
441 }
442
443 void NetworkJob::handleNotifyDataSent(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
444 {
445     if (m_cancelled)
446         return;
447
448     // Protect against reentrancy.
449     updateDeferLoadingCount(1);
450
451     if (isClientAvailable()) {
452         RecursionGuard guard(m_callingClient);
453         m_handle->client()->didSendData(m_handle.get(), bytesSent, totalBytesToBeSent);
454     }
455
456     updateDeferLoadingCount(-1);
457 }
458
459 void NetworkJob::notifyClose(int status)
460 {
461     if (shouldDeferLoading())
462         m_deferredData.deferClose(status);
463     else
464         handleNotifyClose(status);
465 }
466
467 void NetworkJob::handleNotifyClose(int status)
468 {
469 #ifndef NDEBUG
470     m_isRunning = false;
471 #endif
472     if (!m_cancelled) {
473         if (!m_statusReceived) {
474             // Connection failed before sending notifyStatusReceived: use generic NetworkError.
475             notifyStatusReceived(BlackBerry::Platform::FilterStream::StatusNetworkError, 0);
476         }
477
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))
482             storeCredentials();
483         else if (isUnauthorized(m_extendedStatusCode))
484             purgeCredentials();
485
486         if (shouldNotifyClientFinished()) {
487             if (isRedirect(m_extendedStatusCode) && (m_redirectCount >= s_redirectMaximum))
488                 m_extendedStatusCode = BlackBerry::Platform::FilterStream::StatusTooManyRedirects;
489
490             sendResponseIfNeeded();
491             if (isClientAvailable()) {
492
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);
498                 } else
499                     m_handle->client()->didFinishLoading(m_handle.get(), 0);
500             }
501         }
502     }
503
504     // Whoever called notifyClose still have a reference to the job, so
505     // schedule the deletion with a timer.
506     m_deleteJobTimer.startOneShot(0);
507
508     // Detach from the ResourceHandle in any case.
509     m_handle = 0;
510     m_multipartResponse = nullptr;
511 }
512
513 bool NetworkJob::shouldNotifyClientFinished()
514 {
515     if (m_redirectCount >= s_redirectMaximum)
516         return true;
517
518     if (m_needsRetryAsFTPDirectory && retryAsFTPDirectory())
519         return false;
520
521     if (isRedirect(m_extendedStatusCode) && handleRedirect())
522         return false;
523
524     return true;
525 }
526
527 bool NetworkJob::retryAsFTPDirectory()
528 {
529     m_needsRetryAsFTPDirectory = false;
530
531     ASSERT(m_handle);
532     ResourceRequest newRequest = m_handle->firstRequest();
533     KURL url = newRequest.url();
534     url.setPath(url.path() + "/");
535     newRequest.setURL(url);
536     newRequest.setMustHandleInternally(true);
537
538     // Update the UI.
539     handleNotifyHeaderReceived("Location", url.string());
540
541     return startNewJobWithRequest(newRequest);
542 }
543
544 bool NetworkJob::startNewJobWithRequest(ResourceRequest& newRequest, bool increasRedirectCount)
545 {
546     if (isClientAvailable()) {
547         RecursionGuard guard(m_callingClient);
548         m_handle->client()->willSendRequest(m_handle.get(), newRequest, m_response);
549
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())
553             return false;
554     }
555
556     // Pass the ownership of the ResourceHandle to the new NetworkJob.
557     RefPtr<ResourceHandle> handle = m_handle;
558     m_handle = 0;
559     m_multipartResponse = nullptr;
560
561     NetworkManager::instance()->startJob(m_playerId,
562         m_pageGroupName,
563         handle,
564         newRequest,
565         m_streamFactory,
566         *m_frame,
567         m_deferLoadingCount,
568         increasRedirectCount ? m_redirectCount + 1 : m_redirectCount);
569     return true;
570 }
571
572 bool NetworkJob::handleRedirect()
573 {
574     ASSERT(m_handle);
575     if (!m_handle)
576         return false;
577
578     String location = m_response.httpHeaderField("Location");
579     if (location.isNull())
580         return false;
581
582     KURL newURL(m_response.url(), location);
583     if (!newURL.isValid())
584         return false;
585
586     ResourceRequest newRequest = m_handle->firstRequest();
587     newRequest.setURL(newURL);
588     newRequest.setMustHandleInternally(true);
589
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());
596     }
597
598     // Do not send existing credentials with the new request.
599     m_handle->getInternal()->m_currentWebChallenge.nullify();
600
601     return startNewJobWithRequest(newRequest, true);
602 }
603
604 void NetworkJob::sendResponseIfNeeded()
605 {
606     if (m_responseSent)
607         return;
608
609     m_responseSent = true;
610
611     if (isError(m_extendedStatusCode) && !m_dataReceived)
612         return;
613
614     String urlFilename = m_response.url().lastPathComponent();
615
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);
627
628     // Set encoding from Content-Type header.
629     m_response.setTextEncodingName(extractCharsetFromMediaType(m_contentType));
630
631     // Set content length from header.
632     String contentLength = m_response.httpHeaderField("Content-Length");
633     if (!contentLength.isNull())
634         m_response.setExpectedContentLength(contentLength.toInt64());
635
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");
647                 else
648                     suggestedFilename = String("Untitled") + "." + mimeExtension;
649             } else {
650                 if (urlFilename.reverseFind('.') == notFound && !mimeExtension.isEmpty())
651                    suggestedFilename = urlFilename + '.' + mimeExtension;
652                 else
653                    suggestedFilename = urlFilename;
654             }
655         }
656         m_response.setSuggestedFilename(suggestedFilename);
657     }
658
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");
662
663     if (isClientAvailable()) {
664         RecursionGuard guard(m_callingClient);
665         m_handle->client()->didReceiveResponse(m_handle.get(), m_response);
666     }
667 }
668
669 void NetworkJob::sendMultipartResponseIfNeeded()
670 {
671     if (m_multipartResponse && isClientAvailable()) {
672         m_handle->client()->didReceiveResponse(m_handle.get(), *m_multipartResponse);
673         m_multipartResponse = nullptr;
674     }
675 }
676
677 void NetworkJob::parseData()
678 {
679     Vector<char> result;
680
681     String contentType("text/plain;charset=US-ASCII");
682
683     String data(m_response.url().string().substring(5));
684     Vector<String> hparts;
685     bool isBase64 = false;
686
687     size_t index = data.find(',');
688     if (index != notFound && index > 0) {
689         contentType = data.left(index).lower();
690         data = data.substring(index + 1);
691
692         contentType.split(';', hparts);
693         Vector<String>::iterator i;
694         for (i = hparts.begin(); i != hparts.end(); ++i) {
695             if (i->stripWhiteSpace().lower() == "base64") {
696                 isBase64 = true;
697                 String value = *i;
698                 do {
699                     if (*i == value) {
700                         int position = i - hparts.begin();
701                         hparts.remove(position);
702                         i = hparts.begin() + position;
703                     } else
704                         ++i;
705                 } while (i != hparts.end());
706                 break;
707             }
708         }
709         contentType = String();
710         for (i = hparts.begin(); i != hparts.end(); ++i) {
711             if (i > hparts.begin())
712                 contentType += ",";
713
714             contentType += *i;
715         }
716     } else if (!index)
717         data = data.substring(1); // Broken header.
718
719     {
720         CString latin = data.latin1();
721         escapeDecode(latin.data(), latin.length(), result);
722     }
723
724     if (isBase64) {
725         String s(result.data(), result.size());
726         CString latin = s.removeCharacters(isSpaceOrNewline).latin1();
727         result.clear();
728         result.append(latin.data(), latin.length());
729         Vector<char> bytesOut;
730         if (base64Decode(result, bytesOut))
731             result.swap(bytesOut);
732         else
733             result.clear();
734     }
735
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);
741 }
742
743 bool NetworkJob::handleAuthHeader(const ProtectionSpaceServerType space, const String& header)
744 {
745     if (!m_handle)
746         return false;
747
748     if (!m_handle->getInternal()->m_currentWebChallenge.isNull())
749         return false;
750
751     if (header.isEmpty())
752         return false;
753
754     if (equalIgnoringCase(header, "ntlm"))
755         sendRequestWithCredentials(space, ProtectionSpaceAuthenticationSchemeNTLM, "NTLM");
756
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());
761         return false;
762     }
763
764     String scheme = header.left(spacePos);
765
766     ProtectionSpaceAuthenticationScheme protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeDefault;
767     if (equalIgnoringCase(scheme, "basic"))
768         protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeHTTPBasic;
769     else if (equalIgnoringCase(scheme, "digest"))
770         protectionSpaceScheme = ProtectionSpaceAuthenticationSchemeHTTPDigest;
771     else {
772         notImplemented();
773         return false;
774     }
775
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());
779         return false;
780     }
781     size_t beginPos = realmPos + 6;
782     String realm  = header.right(header.length() - beginPos);
783     if (realm.startsWith("\"")) {
784         beginPos += 1;
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());
788             return false;
789         }
790         realm = header.substring(beginPos, endPos - beginPos);
791     }
792
793     // Get the user's credentials and resend the request.
794     sendRequestWithCredentials(space, protectionSpaceScheme, realm);
795
796     return true;
797 }
798
799 bool NetworkJob::handleFTPHeader(const String& header)
800 {
801     size_t spacePos = header.find(' ');
802     if (spacePos == notFound)
803         return false;
804     String statusCode = header.left(spacePos);
805     switch (statusCode.toInt()) {
806     case 213:
807         m_isFTPDir = false;
808         break;
809     case 530:
810         purgeCredentials();
811         sendRequestWithCredentials(ProtectionSpaceServerFTP, ProtectionSpaceAuthenticationSchemeDefault, "ftp");
812         break;
813     case 230:
814         storeCredentials();
815         break;
816     case 550:
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;
822         break;
823     }
824
825     return true;
826 }
827
828 bool NetworkJob::sendRequestWithCredentials(ProtectionSpaceServerType type, ProtectionSpaceAuthenticationScheme scheme, const String& realm)
829 {
830     ASSERT(m_handle);
831     if (!m_handle)
832         return false;
833
834     KURL newURL = m_response.url();
835     if (!newURL.isValid())
836         return false;
837
838     int port = 0;
839     if (type == ProtectionSpaceProxyHTTP) {
840         std::stringstream toPort(BlackBerry::Platform::Client::get()->getProxyPort());
841         toPort >> port;
842     } else
843         port = m_response.url().port();
844
845     ProtectionSpace protectionSpace((type == ProtectionSpaceProxyHTTP) ? BlackBerry::Platform::Client::get()->getProxyAddress().c_str() : m_response.url().host()
846             , port, type, realm, scheme);
847
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);
854     } else {
855         // CredentialStore is empty. Ask the user via dialog.
856         String username;
857         String password;
858
859         if (!m_frame || !m_frame->loader() || !m_frame->loader()->client())
860             return false;
861
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();
866
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 = "";
871         } else
872             m_frame->loader()->client()->authenticationChallenge(realm, username, password);
873
874         if (username.isEmpty() && password.isEmpty())
875             return false;
876
877         credential = Credential(username, password, CredentialPersistenceForSession);
878
879         m_handle->getInternal()->m_currentWebChallenge = AuthenticationChallenge(protectionSpace, credential, 0, m_response, ResourceError());
880     }
881
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);
888 }
889
890 void NetworkJob::storeCredentials()
891 {
892     if (!m_handle)
893         return;
894
895     AuthenticationChallenge& challenge = m_handle->getInternal()->m_currentWebChallenge;
896     if (challenge.isNull())
897         return;
898
899     if (challenge.isStored())
900         return;
901
902     CredentialStorage::set(challenge.proposedCredential(), challenge.protectionSpace(), m_response.url());
903     challenge.setStored(true);
904 }
905
906 void NetworkJob::purgeCredentials()
907 {
908     if (!m_handle)
909         return;
910
911     AuthenticationChallenge& challenge = m_handle->getInternal()->m_currentWebChallenge;
912     if (challenge.isNull())
913         return;
914
915     CredentialStorage::remove(challenge.protectionSpace());
916     challenge.setStored(false);
917 }
918
919 bool NetworkJob::shouldSendClientData() const
920 {
921     return (!isRedirect(m_extendedStatusCode) || !m_response.httpHeaderFields().contains("Location"))
922            && !m_needsRetryAsFTPDirectory;
923 }
924
925 void NetworkJob::fireDeleteJobTimer(Timer<NetworkJob>*)
926 {
927     NetworkManager::instance()->deleteJob(this);
928 }
929
930 void NetworkJob::handleAbout()
931 {
932     // First 6 chars are "about:".
933     String aboutWhat(m_response.url().string().substring(6));
934
935     String result;
936
937     bool handled = false;
938     if (aboutWhat.isEmpty() || equalIgnoringCase(aboutWhat, "blank")) {
939         handled = true;
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>"));
944         handled = true;
945     } else if (aboutWhat.startsWith("cache?query=", false)) {
946         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
947         ASSERT(client);
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>"));
954         handled = true;
955     } else if (equalIgnoringCase(aboutWhat, "cache")) {
956         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
957         ASSERT(client);
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>"));
961         handled = true;
962 #if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
963     } else if (equalIgnoringCase(aboutWhat, "cache/disable")) {
964         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
965         ASSERT(client);
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>"));
968         handled = true;
969     } else if (equalIgnoringCase(aboutWhat, "cache/enable")) {
970         BlackBerry::Platform::Client* client = BlackBerry::Platform::Client::get();
971         ASSERT(client);
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>"));
974         handled = true;
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>"));
979         handled = true;
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>"));
984         handled = true;
985     } else if (BlackBerry::Platform::debugSetting() > 0 && equalIgnoringCase(aboutWhat, "config")) {
986         result = configPage();
987         handled = true;
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>"));
1003         handled = true;
1004     } else if (equalIgnoringCase(aboutWhat, "memory")) {
1005         result = memoryPage();
1006         handled = true;
1007 #endif
1008     }
1009
1010     if (handled) {
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);
1017     } else {
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);
1021     }
1022 }
1023
1024 } // namespace WebCore