diff options
Diffstat (limited to 'Source/WebKit/android/WebCoreSupport/WebRequest.cpp')
-rw-r--r-- | Source/WebKit/android/WebCoreSupport/WebRequest.cpp | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/Source/WebKit/android/WebCoreSupport/WebRequest.cpp b/Source/WebKit/android/WebCoreSupport/WebRequest.cpp new file mode 100644 index 0000000..a7321da --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebRequest.cpp @@ -0,0 +1,531 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "WebRequest.h" + +#include "JNIUtility.h" +#include "MainThread.h" +#include "UrlInterceptResponse.h" +#include "WebCoreFrameBridge.h" +#include "WebRequestContext.h" +#include "WebResourceRequest.h" +#include "WebUrlLoaderClient.h" +#include "jni.h" + +#include <cutils/log.h> +#include <string> +#include <utils/AssetManager.h> + +extern android::AssetManager* globalAssetManager(); + +// TODO: +// - Finish the file upload. Testcase is mobile buzz +// - Add network throttle needed by Android plugins + +// TODO: Turn off asserts crashing before release +// http://b/issue?id=2951985 +#undef ASSERT +#define ASSERT(assertion, ...) do \ + if (!(assertion)) { \ + android_printLog(ANDROID_LOG_ERROR, __FILE__, __VA_ARGS__); \ + } \ +while (0) + +namespace android { + +namespace { + const int kInitialReadBufSize = 32768; +} + +WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest) + : m_urlLoader(loader) + , m_androidUrl(false) + , m_url(webResourceRequest.url()) + , m_userAgent(webResourceRequest.userAgent()) + , m_loadState(Created) + , m_authRequestCount(0) + , m_cacheMode(0) + , m_runnableFactory(this) + , m_wantToPause(false) + , m_isPaused(false) + , m_isSync(false) +{ + GURL gurl(m_url); + + m_request = new URLRequest(gurl, this); + + m_request->SetExtraRequestHeaders(webResourceRequest.requestHeaders()); + m_request->set_referrer(webResourceRequest.referrer()); + m_request->set_method(webResourceRequest.method()); + m_request->set_load_flags(webResourceRequest.loadFlags()); +} + +// This is a special URL for Android. Query the Java InputStream +// for data and send to WebCore +WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest, UrlInterceptResponse* intercept) + : m_urlLoader(loader) + , m_interceptResponse(intercept) + , m_androidUrl(true) + , m_url(webResourceRequest.url()) + , m_userAgent(webResourceRequest.userAgent()) + , m_loadState(Created) + , m_authRequestCount(0) + , m_cacheMode(0) + , m_runnableFactory(this) + , m_wantToPause(false) + , m_isPaused(false) + , m_isSync(false) +{ +} + +WebRequest::~WebRequest() +{ + ASSERT(m_loadState == Finished, "dtor called on a WebRequest in a different state than finished (%d)", m_loadState); + + m_loadState = Deleted; +} + +const std::string& WebRequest::getUrl() const +{ + return m_url; +} + +const std::string& WebRequest::getUserAgent() const +{ + return m_userAgent; +} + +#ifdef LOG_REQUESTS +namespace { +int remaining = 0; +} +#endif + +void WebRequest::finish(bool success) +{ + m_runnableFactory.RevokeAll(); + ASSERT(m_loadState < Finished, "(%p) called finish on an already finished WebRequest (%d) (%s)", this, m_loadState, m_url.c_str()); + if (m_loadState >= Finished) + return; +#ifdef LOG_REQUESTS + time_t finish; + time(&finish); + finish = finish - m_startTime; + struct tm * timeinfo; + char buffer[80]; + timeinfo = localtime(&finish); + strftime(buffer, 80, "Time: %M:%S",timeinfo); + android_printLog(ANDROID_LOG_DEBUG, "KM", "(%p) finish (%d) (%s) (%d) (%s)", this, --remaining, buffer, success, m_url.c_str()); +#endif + + // Make sure WebUrlLoaderClient doesn't delete us in the middle of this method. + scoped_refptr<WebRequest> guard(this); + + m_loadState = Finished; + if (success) { + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didFinishLoading)); + } else { + if (m_interceptResponse == NULL) { + OwnPtr<WebResponse> webResponse(new WebResponse(m_request.get())); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didFail, webResponse.release())); + } else { + OwnPtr<WebResponse> webResponse(new WebResponse(m_url, m_interceptResponse->mimeType(), 0, + m_interceptResponse->encoding(), m_interceptResponse->status())); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didFail, webResponse.release())); + } + } + m_networkBuffer = 0; + m_request = 0; + m_urlLoader = 0; +} + +void WebRequest::appendFileToUpload(const std::string& filename) +{ + // AppendFileToUpload is only valid before calling start + ASSERT(m_loadState == Created, "appendFileToUpload called on a WebRequest not in CREATED state: (%s)", m_url.c_str()); + FilePath filePath(filename); + m_request->AppendFileToUpload(filePath); +} + +void WebRequest::appendBytesToUpload(WTF::Vector<char>* data) +{ + // AppendBytesToUpload is only valid before calling start + ASSERT(m_loadState == Created, "appendBytesToUpload called on a WebRequest not in CREATED state: (%s)", m_url.c_str()); + m_request->AppendBytesToUpload(data->data(), data->size()); + delete data; +} + +void WebRequest::setRequestContext(WebRequestContext* context) +{ + m_cacheMode = context->getCacheMode(); + if (m_request) + m_request->set_context(context); +} + +void WebRequest::updateLoadFlags(int& loadFlags) +{ + if (m_cacheMode == 1) { // LOAD_CACHE_ELSE_NETWORK + loadFlags |= net::LOAD_PREFERRING_CACHE; + loadFlags &= ~net::LOAD_VALIDATE_CACHE; + } + if (m_cacheMode == 2) // LOAD_NO_CACHE + loadFlags |= net::LOAD_BYPASS_CACHE; + if (m_cacheMode == 3) // LOAD_CACHE_ONLY + loadFlags |= net::LOAD_ONLY_FROM_CACHE; + + if (m_isSync) + loadFlags |= net::LOAD_IGNORE_LIMITS; +} + +void WebRequest::start() +{ + ASSERT(m_loadState == Created, "Start called on a WebRequest not in CREATED state: (%s)", m_url.c_str()); +#ifdef LOG_REQUESTS + android_printLog(ANDROID_LOG_DEBUG, "KM", "(%p) start (%d) (%s)", this, ++remaining, m_url.c_str()); + time(&m_startTime); +#endif + + m_loadState = Started; + + if (m_interceptResponse != NULL) + return handleInterceptedURL(); + + // Handle data urls before we send it off to the http stack + if (m_request->url().SchemeIs("data")) + return handleDataURL(m_request->url()); + + if (m_request->url().SchemeIs("browser")) + return handleBrowserURL(m_request->url()); + + // Update load flags with settings from WebSettings + int loadFlags = m_request->load_flags(); + updateLoadFlags(loadFlags); + m_request->set_load_flags(loadFlags); + + m_request->Start(); +} + +void WebRequest::cancel() +{ + ASSERT(m_loadState >= Started, "Cancel called on a not started WebRequest: (%s)", m_url.c_str()); + ASSERT(m_loadState != Cancelled, "Cancel called on an already cancelled WebRequest: (%s)", m_url.c_str()); + + // There is a possible race condition between the IO thread finishing the request and + // the WebCore thread cancelling it. If the request has already finished, do + // nothing to avoid sending duplicate finish messages to WebCore. + if (m_loadState > Cancelled) { + return; + } + ASSERT(m_request, "Request set to 0 before it is finished"); + + m_loadState = Cancelled; + + m_request->Cancel(); + finish(true); +} + +void WebRequest::pauseLoad(bool pause) +{ + ASSERT(m_loadState >= GotData, "PauseLoad in state other than RESPONSE and GOTDATA"); + if (pause) { + if (!m_isPaused) + m_wantToPause = true; + } else { + m_wantToPause = false; + if (m_isPaused) { + m_isPaused = false; + MessageLoop::current()->PostTask(FROM_HERE, m_runnableFactory.NewRunnableMethod(&WebRequest::startReading)); + } + } +} + +void WebRequest::handleInterceptedURL() +{ + m_loadState = Response; + + const std::string& mime = m_interceptResponse->mimeType(); + // Get the MIME type from the URL. "text/html" is a last resort, hopefully overridden. + std::string mimeType("text/html"); + if (mime == "") { + // Gmail appends the MIME to the end of the URL, with a ? separator. + size_t mimeTypeIndex = m_url.find_last_of('?'); + if (mimeTypeIndex != std::string::npos) { + mimeType.assign(m_url.begin() + mimeTypeIndex + 1, m_url.end()); + } else { + // Get the MIME type from the file extension, if any. + FilePath path(m_url); + net::GetMimeTypeFromFile(path, &mimeType); + } + } else { + // Set from the intercept response. + mimeType = mime; + } + + + OwnPtr<WebResponse> webResponse(new WebResponse(m_url, mimeType, 0, m_interceptResponse->encoding(), m_interceptResponse->status())); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release())); + + do { + // data is deleted in WebUrlLoaderClient::didReceiveAndroidFileData + // data is sent to the webcore thread + OwnPtr<std::vector<char> > data(new std::vector<char>); + data->reserve(kInitialReadBufSize); + + // Read returns false on error and size of 0 on eof. + if (!m_interceptResponse->readStream(data.get()) || data->size() == 0) + break; + + m_loadState = GotData; + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveAndroidFileData, data.release())); + } while (true); + + finish(m_interceptResponse->status() == 200); +} + +void WebRequest::handleDataURL(GURL url) +{ + OwnPtr<std::string> data(new std::string); + std::string mimeType; + std::string charset; + + if (net::DataURL::Parse(url, &mimeType, &charset, data.get())) { + // PopulateURLResponse from chrome implementation + // weburlloader_impl.cc + m_loadState = Response; + OwnPtr<WebResponse> webResponse(new WebResponse(url.spec(), mimeType, data->size(), charset, 200)); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release())); + + if (!data->empty()) { + m_loadState = GotData; + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveDataUrl, data.release())); + } + } else { + // handle the failed case + } + + finish(true); +} + +void WebRequest::handleBrowserURL(GURL url) +{ + std::string data("data:text/html;charset=utf-8,"); + if (url.spec() == "browser:incognito") { + AssetManager* assetManager = globalAssetManager(); + Asset* asset = assetManager->open("webkit/incognito_mode_start_page.html", Asset::ACCESS_BUFFER); + if (asset) { + data.append((const char*)asset->getBuffer(false), asset->getLength()); + delete asset; + } + } + GURL dataURL(data.c_str()); + handleDataURL(dataURL); +} + +// Called upon a server-initiated redirect. The delegate may call the +// request's Cancel method to prevent the redirect from being followed. +// Since there may be multiple chained redirects, there may also be more +// than one redirect call. +// +// When this function is called, the request will still contain the +// original URL, the destination of the redirect is provided in 'new_url'. +// If the delegate does not cancel the request and |*defer_redirect| is +// false, then the redirect will be followed, and the request's URL will be +// changed to the new URL. Otherwise if the delegate does not cancel the +// request and |*defer_redirect| is true, then the redirect will be +// followed once FollowDeferredRedirect is called on the URLRequest. +// +// The caller must set |*defer_redirect| to false, so that delegates do not +// need to set it if they are happy with the default behavior of not +// deferring redirect. +void WebRequest::OnReceivedRedirect(URLRequest* newRequest, const GURL& newUrl, bool* deferRedirect) +{ + ASSERT(m_loadState < Response, "Redirect after receiving response"); + ASSERT(newRequest && newRequest->status().is_success(), "Invalid redirect"); + + OwnPtr<WebResponse> webResponse(new WebResponse(newRequest)); + webResponse->setUrl(newUrl.spec()); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::willSendRequest, webResponse.release())); + + // Defer the redirect until followDeferredRedirect() is called. + *deferRedirect = true; +} + +// Called when we receive an authentication failure. The delegate should +// call request->SetAuth() with the user's credentials once it obtains them, +// or request->CancelAuth() to cancel the login and display the error page. +// When it does so, the request will be reissued, restarting the sequence +// of On* callbacks. +void WebRequest::OnAuthRequired(URLRequest* request, net::AuthChallengeInfo* authInfo) +{ + ASSERT(m_loadState == Started, "OnAuthRequired called on a WebRequest not in STARTED state (state=%d)", m_loadState); + + scoped_refptr<net::AuthChallengeInfo> authInfoPtr(authInfo); + bool firstTime = (m_authRequestCount == 0); + ++m_authRequestCount; + + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::authRequired, authInfoPtr, firstTime)); +} + +// Called when we received an SSL certificate error. The delegate will provide +// the user the options to proceed, cancel, or view certificates. +void WebRequest::OnSSLCertificateError(URLRequest* request, int cert_error, net::X509Certificate* cert) +{ + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::reportSslCertError, cert_error, cert)); +} + +// After calling Start(), the delegate will receive an OnResponseStarted +// callback when the request has completed. If an error occurred, the +// request->status() will be set. On success, all redirects have been +// followed and the final response is beginning to arrive. At this point, +// meta data about the response is available, including for example HTTP +// response headers if this is a request for a HTTP resource. +void WebRequest::OnResponseStarted(URLRequest* request) +{ + ASSERT(m_loadState == Started, "Got response after receiving response"); + + m_loadState = Response; + if (request && request->status().is_success()) { + OwnPtr<WebResponse> webResponse(new WebResponse(request)); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release())); + + // Start reading the response + startReading(); + } else { + finish(false); + } +} + +void WebRequest::setAuth(const string16& username, const string16& password) +{ + ASSERT(m_loadState == Started, "setAuth called on a WebRequest not in STARTED state (state=%d)", m_loadState); + + m_request->SetAuth(username, password); +} + +void WebRequest::cancelAuth() +{ + ASSERT(m_loadState == Started, "cancelAuth called on a WebRequest not in STARTED state (state=%d)", m_loadState); + + m_request->CancelAuth(); +} + +void WebRequest::followDeferredRedirect() +{ + ASSERT(m_loadState < Response, "Redirect after receiving response"); + + m_request->FollowDeferredRedirect(); +} + +void WebRequest::proceedSslCertError() +{ + m_request->ContinueDespiteLastError(); +} + +void WebRequest::cancelSslCertError(int cert_error) +{ + m_request->SimulateError(cert_error); +} + +void WebRequest::startReading() +{ + ASSERT(m_networkBuffer == 0, "startReading called with a nonzero buffer"); + ASSERT(m_isPaused == 0, "startReading called in paused state"); + ASSERT(m_loadState == Response || m_loadState == GotData, "StartReading in state other than RESPONSE and GOTDATA"); + if (m_loadState > GotData) // We have been cancelled between reads + return; + + if (m_wantToPause) { + m_isPaused = true; + return; + } + + int bytesRead = 0; + + if (!read(&bytesRead)) { + if (m_request && m_request->status().is_io_pending()) + return; // Wait for OnReadCompleted() + return finish(false); + } + + // bytesRead == 0 indicates finished + if (!bytesRead) + return finish(true); + + m_loadState = GotData; + // Read ok, forward buffer to webcore + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(m_urlLoader.get(), &WebUrlLoaderClient::didReceiveData, m_networkBuffer, bytesRead)); + m_networkBuffer = 0; + MessageLoop::current()->PostTask(FROM_HERE, m_runnableFactory.NewRunnableMethod(&WebRequest::startReading)); +} + +bool WebRequest::read(int* bytesRead) +{ + ASSERT(m_loadState == Response || m_loadState == GotData, "read in state other than RESPONSE and GOTDATA"); + ASSERT(m_networkBuffer == 0, "Read called with a nonzero buffer"); + + // TODO: when asserts work, check that the buffer is 0 here + m_networkBuffer = new net::IOBuffer(kInitialReadBufSize); + return m_request->Read(m_networkBuffer, kInitialReadBufSize, bytesRead); +} + +// This is called when there is data available + +// Called when the a Read of the response body is completed after an +// IO_PENDING status from a Read() call. +// The data read is filled into the buffer which the caller passed +// to Read() previously. +// +// If an error occurred, request->status() will contain the error, +// and bytes read will be -1. +void WebRequest::OnReadCompleted(URLRequest* request, int bytesRead) +{ + ASSERT(m_loadState == Response || m_loadState == GotData, "OnReadCompleted in state other than RESPONSE and GOTDATA"); + + if (request->status().is_success()) { + m_loadState = GotData; + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveData, m_networkBuffer, bytesRead)); + m_networkBuffer = 0; + + // Get the rest of the data + startReading(); + } else { + finish(false); + } +} + +} // namespace android |