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