/*
 * 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_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_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 == "") {
        // 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