/* * 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. */ #define LOG_TAG "WebUrlLoaderClient" #include "config.h" #include "WebUrlLoaderClient.h" #include "ChromiumIncludes.h" #include "OwnPtr.h" #include "ResourceHandle.h" #include "ResourceHandleClient.h" #include "ResourceResponse.h" #include "WebCoreFrameBridge.h" #include "WebRequest.h" #include "WebResourceRequest.h" #include #include using base::Lock; using base::AutoLock; namespace android { base::Thread* WebUrlLoaderClient::ioThread() { static base::Thread* networkThread = 0; static Lock networkThreadLock; // Multiple threads appear to access the ioThread so we must ensure the // critical section ordering. AutoLock lock(networkThreadLock); if (!networkThread) networkThread = new base::Thread("network"); if (!networkThread) return 0; if (networkThread->IsRunning()) return networkThread; base::Thread::Options options; options.message_loop_type = MessageLoop::TYPE_IO; if (!networkThread->StartWithOptions(options)) { delete networkThread; networkThread = 0; } return networkThread; } base::Lock* WebUrlLoaderClient::syncLock() { static Lock s_syncLock; return &s_syncLock; } base::ConditionVariable* WebUrlLoaderClient::syncCondition() { static base::ConditionVariable s_syncCondition(syncLock()); return &s_syncCondition; } WebUrlLoaderClient::~WebUrlLoaderClient() { } bool WebUrlLoaderClient::isActive() const { if (m_cancelling) return false; if (!m_resourceHandle) return false; if (!m_resourceHandle->client()) return false; if (m_finished) return false; return true; } WebUrlLoaderClient::WebUrlLoaderClient(WebFrame* webFrame, WebCore::ResourceHandle* resourceHandle, const WebCore::ResourceRequest& resourceRequest) : m_webFrame(webFrame) , m_resourceHandle(resourceHandle) , m_isMainResource(false) , m_isMainFrame(false) , m_isCertMimeType(false) , m_cancelling(false) , m_sync(false) , m_finished(false) { bool block = webFrame->blockNetworkLoads() && (resourceRequest.url().protocolIs("http") || resourceRequest.url().protocolIs("https")); WebResourceRequest webResourceRequest(resourceRequest, block); UrlInterceptResponse* intercept = webFrame->shouldInterceptRequest(resourceRequest.url().string()); if (intercept) { m_request = new WebRequest(this, webResourceRequest, intercept); return; } m_request = new WebRequest(this, webResourceRequest); // Set uploads before start is called on the request if (resourceRequest.httpBody() && !(webResourceRequest.method() == "GET" || webResourceRequest.method() == "HEAD")) { Vector::iterator iter; Vector elements = resourceRequest.httpBody()->elements(); for (iter = elements.begin(); iter != elements.end(); iter++) { FormDataElement element = *iter; switch (element.m_type) { case FormDataElement::data: if (!element.m_data.isEmpty()) { // WebKit sometimes gives up empty data to append. These aren't // necessary so we just optimize those out here. base::Thread* thread = ioThread(); if (thread) { Vector* data = new Vector(element.m_data); thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::appendBytesToUpload, data)); } } break; case FormDataElement::encodedFile: { // Chromium check if it is a directory by checking // element.m_fileLength, that doesn't work in Android std::string filename = element.m_filename.utf8().data(); if (filename.size()) { // Change from a url string to a filename if (filename.find("file://") == 0) // Found at pos 0 filename.erase(0, 7); base::Thread* thread = ioThread(); if (thread) thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::appendFileToUpload, filename)); } } break; #if ENABLE(BLOB) case FormDataElement::encodedBlob: ALOG_ASSERT(false, "Unexpected use of FormDataElement::encodedBlob"); break; #endif // ENABLE(BLOB) default: ALOG_ASSERT(false, "Unexpected default case in WebUrlLoaderClient.cpp"); break; } } } } bool WebUrlLoaderClient::start(bool isMainResource, bool isMainFrame, bool sync, WebRequestContext* context) { base::Thread* thread = ioThread(); if (!thread) { return false; } m_isMainResource = isMainResource; m_isMainFrame = isMainFrame; m_sync = sync; if (m_sync) { AutoLock autoLock(*syncLock()); m_request->setSync(sync); m_request->setRequestContext(context); thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::start)); // Run callbacks until the queue is exhausted and m_finished is true. // Sometimes, a sync load can wait forever and lock up the WebCore thread, // here we use TimedWait() with multiple tries to avoid locking. const int kMaxNumTimeout = 3; const int kCallbackWaitingTime = 10; int num_timeout = 0; while(!m_finished) { while (!m_queue.empty()) { OwnPtr task(m_queue.front()); m_queue.pop_front(); task->Run(); } if (m_finished) break; syncCondition()->TimedWait(base::TimeDelta::FromSeconds(kCallbackWaitingTime)); if (m_queue.empty()) { ALOGE("Synchronous request timed out after %d seconds for the %dth try, URL: %s", kCallbackWaitingTime, num_timeout, m_request->getUrl().c_str()); num_timeout++; if (num_timeout >= kMaxNumTimeout) { cancel(); m_resourceHandle = 0; return false; } } } // This may be the last reference to us, so we may be deleted now. // Don't access any more member variables after releasing this reference. m_resourceHandle = 0; } else { // Asynchronous start. // Important to set this before the thread starts so it has a reference and can't be deleted // before the task starts running on the IO thread. m_request->setRequestContext(context); thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::start)); } return true; } namespace { // Check if the mime type is for certificate installation. // The items must be consistent with the sCertificateTypeMap // in frameworks/base/core/java/android/webkit/CertTool.java. bool isMimeTypeForCert(const std::string& mimeType) { static std::hash_set sCertificateTypeSet; if (sCertificateTypeSet.empty()) { sCertificateTypeSet.insert("application/x-x509-ca-cert"); sCertificateTypeSet.insert("application/x-x509-user-cert"); sCertificateTypeSet.insert("application/x-pkcs12"); } return sCertificateTypeSet.find(mimeType) != sCertificateTypeSet.end(); } } void WebUrlLoaderClient::downloadFile() { if (m_response) { std::string contentDisposition; m_response->getHeader("content-disposition", &contentDisposition); m_webFrame->downloadStart(m_response->getUrl(), m_request->getUserAgent(), contentDisposition, m_response->getMimeType(), m_request->getReferer(), m_response->getExpectedSize()); m_isCertMimeType = isMimeTypeForCert(m_response->getMimeType()); // Currently, only certificate mime type needs to receive the data. // Other mime type, e.g. wav, will send the url to other application // which will load the data by url. if (!m_isCertMimeType) cancel(); } else { ALOGE("Unexpected call to downloadFile() before didReceiveResponse(). URL: %s", m_request->getUrl().c_str()); // TODO: Turn off asserts crashing before release // http://b/issue?id=2951985 CRASH(); } } void WebUrlLoaderClient::cancel() { if (!isActive()) return; m_cancelling = true; base::Thread* thread = ioThread(); if (thread) thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::cancel)); } void WebUrlLoaderClient::pauseLoad(bool pause) { if (!isActive()) return; base::Thread* thread = ioThread(); if (thread) thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::pauseLoad, pause)); } void WebUrlLoaderClient::setAuth(const std::string& username, const std::string& password) { if (!isActive()) return; base::Thread* thread = ioThread(); if (!thread) { return; } string16 username16 = ASCIIToUTF16(username); string16 password16 = ASCIIToUTF16(password); thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::setAuth, username16, password16)); } void WebUrlLoaderClient::cancelAuth() { if (!isActive()) return; base::Thread* thread = ioThread(); if (!thread) { return; } thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::cancelAuth)); } void WebUrlLoaderClient::proceedSslCertError() { base::Thread* thread = ioThread(); if (isActive() && thread) thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::proceedSslCertError)); this->Release(); } void WebUrlLoaderClient::cancelSslCertError(int cert_error) { base::Thread* thread = ioThread(); if (isActive() && thread) thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::cancelSslCertError, cert_error)); this->Release(); } void WebUrlLoaderClient::finish() { m_finished = true; if (!m_sync) { // This is the last reference to us, so we will be deleted now. // We only release the reference here if start() was called asynchronously! m_resourceHandle = 0; } m_request = 0; } namespace { // Trampoline to wrap a Chromium Task* in a WebKit-style static function + void*. static void RunTask(void* v) { OwnPtr task(static_cast(v)); task->Run(); } } // This is called from the IO thread, and dispatches the callback to the main thread. void WebUrlLoaderClient::maybeCallOnMainThread(Task* task) { if (m_sync) { AutoLock autoLock(*syncLock()); if (m_queue.empty()) { syncCondition()->Broadcast(); } m_queue.push_back(task); } else { // Let WebKit handle it. callOnMainThread(RunTask, task); } } // Response methods void WebUrlLoaderClient::didReceiveResponse(PassOwnPtr webResponse) { if (!isActive()) return; m_response = webResponse; m_resourceHandle->client()->didReceiveResponse(m_resourceHandle.get(), m_response->createResourceResponse()); // Set the main page's certificate to WebView. if (m_isMainResource && m_isMainFrame) { const net::SSLInfo& ssl_info = m_response->getSslInfo(); if (ssl_info.is_valid()) { std::vector chain_bytes; ssl_info.cert->GetChainDEREncodedBytes(&chain_bytes); m_webFrame->setCertificate(chain_bytes[0]); } // Look for X-Auto-Login on the main resource to log in the user. std::string login; if (m_response->getHeader("x-auto-login", &login)) m_webFrame->autoLogin(login); } } void WebUrlLoaderClient::didReceiveData(scoped_refptr buf, int size) { if (m_isMainResource && m_isCertMimeType) { m_webFrame->didReceiveData(buf->data(), size); } if (!isActive() || !size) return; // didReceiveData will take a copy of the data if (m_resourceHandle && m_resourceHandle->client()) m_resourceHandle->client()->didReceiveData(m_resourceHandle.get(), buf->data(), size, size); } // For data url's void WebUrlLoaderClient::didReceiveDataUrl(PassOwnPtr str) { if (!isActive() || !str->size()) return; // didReceiveData will take a copy of the data m_resourceHandle->client()->didReceiveData(m_resourceHandle.get(), str->data(), str->size(), str->size()); } // For special android files void WebUrlLoaderClient::didReceiveAndroidFileData(PassOwnPtr > vector) { if (!isActive() || !vector->size()) return; // didReceiveData will take a copy of the data m_resourceHandle->client()->didReceiveData(m_resourceHandle.get(), vector->begin(), vector->size(), vector->size()); } void WebUrlLoaderClient::didFail(PassOwnPtr webResponse) { if (isActive()) m_resourceHandle->client()->didFail(m_resourceHandle.get(), webResponse->createResourceError()); // Always finish a request, if not it will leak finish(); } void WebUrlLoaderClient::willSendRequest(PassOwnPtr webResponse) { if (!isActive()) return; KURL url = webResponse->createKurl(); OwnPtr resourceRequest(new WebCore::ResourceRequest(url)); m_resourceHandle->client()->willSendRequest(m_resourceHandle.get(), *resourceRequest, webResponse->createResourceResponse()); // WebKit may have killed the request. if (!isActive()) return; // Like Chrome, we only follow the redirect if WebKit left the URL unmodified. if (url == resourceRequest->url()) { ioThread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::followDeferredRedirect)); } else { cancel(); } } void WebUrlLoaderClient::didFinishLoading() { if (isActive()) m_resourceHandle->client()->didFinishLoading(m_resourceHandle.get(), 0); if (m_isMainResource && m_isCertMimeType) { m_webFrame->didFinishLoading(); } // Always finish a request, if not it will leak finish(); } void WebUrlLoaderClient::authRequired(scoped_refptr authChallengeInfo, bool firstTime, bool suppressDialog) { if (!isActive()) return; std::string host = base::SysWideToUTF8(authChallengeInfo->host_and_port); std::string realm = base::SysWideToUTF8(authChallengeInfo->realm); m_webFrame->didReceiveAuthenticationChallenge(this, host, realm, firstTime, suppressDialog); } void WebUrlLoaderClient::reportSslCertError(int cert_error, net::X509Certificate* cert) { if (!isActive()) return; std::vector chain_bytes; cert->GetChainDEREncodedBytes(&chain_bytes); this->AddRef(); m_webFrame->reportSslCertError(this, cert_error, chain_bytes[0], m_request->getUrl()); } void WebUrlLoaderClient::sslClientCert(EVP_PKEY* pkey, net::X509Certificate* chain) { base::Thread* thread = ioThread(); scoped_refptr scopedChain(chain); if (isActive() && thread) thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::sslClientCert, pkey, scopedChain)); this->Release(); } void WebUrlLoaderClient::requestClientCert(net::SSLCertRequestInfo* cert_request_info) { if (!isActive()) return; std::string host_and_port = cert_request_info->host_and_port; this->AddRef(); m_webFrame->requestClientCert(this, host_and_port); } } // namespace android