| /* |
| * 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 "WebCoreJni.h" |
| #include "WebRequestContext.h" |
| #include "WebResourceRequest.h" |
| #include "WebUrlLoaderClient.h" |
| #include "jni.h" |
| |
| #include <cutils/log.h> |
| #include <openssl/x509.h> |
| #include <string> |
| #include <androidfw/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; |
| const char* kXRequestedWithHeader = "X-Requested-With"; |
| |
| struct RequestPackageName { |
| std::string value; |
| RequestPackageName(); |
| }; |
| |
| RequestPackageName::RequestPackageName() { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| jclass bridgeClass = env->FindClass("android/webkit/JniUtil"); |
| jmethodID method = env->GetStaticMethodID(bridgeClass, "getPackageName", "()Ljava/lang/String;"); |
| value = jstringToStdString(env, static_cast<jstring>(env->CallStaticObjectMethod(bridgeClass, method))); |
| env->DeleteLocalRef(bridgeClass); |
| } |
| |
| base::LazyInstance<RequestPackageName> s_packageName(base::LINKER_INITIALIZED); |
| |
| } |
| |
| WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest) |
| : m_urlLoader(loader) |
| , m_url(webResourceRequest.url()) |
| , m_userAgent(webResourceRequest.userAgent()) |
| , m_referer(webResourceRequest.referrer()) |
| , 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 net::URLRequest(gurl, this); |
| |
| m_request->SetExtraRequestHeaders(webResourceRequest.requestHeaders()); |
| m_request->SetExtraRequestHeaderByName(kXRequestedWithHeader, s_packageName.Get().value, false); |
| 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_url(webResourceRequest.url()) |
| , m_userAgent(webResourceRequest.userAgent()) |
| , m_referer(webResourceRequest.referrer()) |
| , 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; |
| } |
| |
| const std::string& WebRequest::getReferer() const |
| { |
| return m_referer; |
| } |
| |
| #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 == "") { |
| // 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(net::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(net::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; |
| |
| bool suppressDialog = (request->load_flags() & net::LOAD_DO_NOT_PROMPT_FOR_LOGIN); |
| |
| m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( |
| m_urlLoader.get(), &WebUrlLoaderClient::authRequired, authInfoPtr, firstTime, suppressDialog)); |
| } |
| |
| // 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(net::URLRequest* request, int cert_error, net::X509Certificate* cert) |
| { |
| scoped_refptr<net::X509Certificate> scoped_cert = cert; |
| m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( |
| m_urlLoader.get(), &WebUrlLoaderClient::reportSslCertError, cert_error, scoped_cert)); |
| } |
| |
| void WebRequest::OnCertificateRequested(net::URLRequest* request, net::SSLCertRequestInfo* cert_request_info) |
| { |
| scoped_refptr<net::SSLCertRequestInfo> scoped_cert_request_info = cert_request_info; |
| m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( |
| m_urlLoader.get(), &WebUrlLoaderClient::requestClientCert, scoped_cert_request_info)); |
| } |
| |
| |
| // 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(net::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::sslClientCert(EVP_PKEY* pkey, scoped_refptr<net::X509Certificate> chain) |
| { |
| base::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> privateKey(pkey); |
| if (privateKey.get() == NULL || chain.get() == NULL) { |
| m_request->ContinueWithCertificate(NULL); |
| return; |
| } |
| GURL gurl(m_url); |
| net::OpenSSLPrivateKeyStore::GetInstance()->StorePrivateKey(gurl, privateKey.release()); |
| m_request->ContinueWithCertificate(chain.release()); |
| } |
| |
| 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(net::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 |