// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "config.h"
#include "Request.h"

#include "bindings/core/v8/Dictionary.h"
#include "core/dom/ExecutionContext.h"
#include "core/fetch/FetchUtils.h"
#include "core/fetch/ResourceLoaderOptions.h"
#include "core/loader/ThreadableLoader.h"
#include "core/xml/XMLHttpRequest.h"
#include "modules/serviceworkers/FetchManager.h"
#include "modules/serviceworkers/RequestInit.h"
#include "platform/NotImplemented.h"
#include "platform/network/HTTPParsers.h"
#include "platform/network/ResourceRequest.h"
#include "platform/weborigin/Referrer.h"
#include "public/platform/WebServiceWorkerRequest.h"

namespace blink {

namespace {

PassRefPtrWillBeRawPtr<Request> createRequestWithRequestData(PassRefPtrWillBeRawPtr<FetchRequestData> request, const RequestInit& init, FetchRequestData::Mode mode, FetchRequestData::Credentials credentials, ExceptionState& exceptionState)
{
    // "6. Let |mode| be |init|'s mode member if it is present, and
    // |fallbackMode| otherwise."
    // "7. If |mode| is non-null, set |request|'s mode to |mode|."
    if (init.mode == "same-origin") {
        request->setMode(FetchRequestData::SameOriginMode);
    } else if (init.mode == "no-cors") {
        request->setMode(mode = FetchRequestData::NoCORSMode);
    } else if (init.mode == "cors") {
        request->setMode(FetchRequestData::CORSMode);
    } else {
        // Instead of using null as a special fallback value, we pass the
        // current mode in Request::create(). So we just set here.
        request->setMode(mode);
    }

    // "8. Let |credentials| be |init|'s credentials member if it is present,
    // and |fallbackCredentials| otherwise."
    // "9. If |credentials| is non-null, set |request|'s credentials mode to
    // |credentials|.
    if (init.credentials == "omit") {
        request->setCredentials(FetchRequestData::OmitCredentials);
    } else if (init.credentials == "same-origin") {
        request->setCredentials(FetchRequestData::SameOriginCredentials);
    } else if (init.credentials == "include") {
        request->setCredentials(FetchRequestData::IncludeCredentials);
    } else {
        // Instead of using null as a special fallback value, we pass the
        // current credentials in Request::create(). So we just set here.
        request->setCredentials(credentials);
    }

    // "10. If |init|'s method member is present, let |method| be it and run
    // these substeps:"
    if (!init.method.isEmpty()) {
        // "1. If |method| is not a useful method, throw a TypeError."
        if (!FetchUtils::isUsefulMethod(init.method)) {
            exceptionState.throwTypeError("'" + init.method + "' HTTP method is unsupported.");
            return nullptr;
        }
        if (!isValidHTTPToken(init.method)) {
            exceptionState.throwTypeError("'" + init.method + "' is not a valid HTTP method.");
            return nullptr;
        }
        // FIXME: "2. Add case correction as in XMLHttpRequest?"
        // "3. Set |request|'s method to |method|."
        request->setMethod(XMLHttpRequest::uppercaseKnownHTTPMethod(AtomicString(init.method)));
    }
    // "11. Let |r| be a new Request object associated with |request|, Headers
    // object, and FetchBodyStream object."
    RefPtrWillBeRawPtr<Request> r = Request::create(request);

    // "12. Let |headers| be a copy of |r|'s Headers object."
    // "13. If |init|'s headers member is present, set |headers| to |init|'s
    // headers member."
    // We don't create a copy of r's Headers object when init's headers member
    // is present.
    RefPtrWillBeRawPtr<Headers> headers = nullptr;
    if (!init.headers && init.headersDictionary.isUndefinedOrNull()) {
        headers = r->headers()->createCopy();
    }
    // "14. Empty |r|'s request's header list."
    r->request()->headerList()->clearList();

    // "15. If |r|'s request's mode is no CORS, run these substeps:
    if (r->request()->mode() == FetchRequestData::NoCORSMode) {
        // "1. If |r|'s request's method is not a simple method, throw a
        // TypeError."
        if (!FetchUtils::isSimpleMethod(r->request()->method())) {
            exceptionState.throwTypeError("'" + r->request()->method() + "' is unsupported in no-cors mode.");
            return nullptr;
        }
        // "Set |r|'s Headers object's guard to |request-no-CORS|.
        r->headers()->setGuard(Headers::RequestNoCORSGuard);
    }

    // "16. Fill |r|'s Headers object with |headers|. Rethrow any exceptions."
    if (init.headers) {
        ASSERT(init.headersDictionary.isUndefinedOrNull());
        r->headers()->fillWith(init.headers.get(), exceptionState);
    } else if (!init.headersDictionary.isUndefinedOrNull()) {
        r->headers()->fillWith(init.headersDictionary, exceptionState);
    } else {
        ASSERT(headers);
        r->headers()->fillWith(headers.get(), exceptionState);
    }
    if (exceptionState.hadException())
        return nullptr;
    // FIXME: Support body.
    // "20. Return |r|."
    return r.release();
}


} // namespace

DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(Request);

PassRefPtrWillBeRawPtr<Request> Request::create(ExecutionContext* context, const String& input, ExceptionState& exceptionState)
{
    return create(context, input, Dictionary(), exceptionState);
}

PassRefPtrWillBeRawPtr<Request> Request::create(ExecutionContext* context, const String& input, const Dictionary& init, ExceptionState& exceptionState)
{
    // "1. Let |request| be |input|'s associated request, if |input| is a
    // Request object, and a new request otherwise."
    RefPtrWillBeRawPtr<FetchRequestData> request(FetchRequestData::create(context));
    // "2. Set |request| to a restricted copy of itself."
    request = request->createRestrictedCopy(context, SecurityOrigin::create(context->url()));
    // "5. If |input| is a string, run these substeps:"
    // "1. Let |parsedURL| be the result of parsing |input| with entry settings
    // object's API base URL."
    KURL parsedURL = context->completeURL(input);
    // "2. If |parsedURL| is failure, throw a TypeError."
    if (!parsedURL.isValid()) {
        exceptionState.throwTypeError("Invalid URL");
        return nullptr;
    }
    // "3. Set |request|'s url to |parsedURL|."
    request->setURL(parsedURL);
    // "4. Set |fallbackMode| to CORS."
    // "5. Set |fallbackCredentials| to omit."
    return createRequestWithRequestData(request.release(), RequestInit(init), FetchRequestData::CORSMode, FetchRequestData::OmitCredentials, exceptionState);
}


PassRefPtrWillBeRawPtr<Request> Request::create(ExecutionContext* context, Request* input, ExceptionState& exceptionState)
{
    return create(context, input, Dictionary(), exceptionState);
}

PassRefPtrWillBeRawPtr<Request> Request::create(ExecutionContext* context, Request* input, const Dictionary& init, ExceptionState& exceptionState)
{
    // "1. Let |request| be |input|'s associated request, if |input| is a
    // Request object, and a new request otherwise."
    // "2. Set |request| to a restricted copy of itself."
    RefPtrWillBeRawPtr<FetchRequestData> request(input->request()->createRestrictedCopy(context, SecurityOrigin::create(context->url())));
    // "3. Let |fallbackMode| be null."
    // "4. Let |fallbackCredentials| be null."
    // Instead of using null as a special fallback value, just pass the current
    // mode and credentials; it has the same effect.
    const FetchRequestData::Mode currentMode = request->mode();
    const FetchRequestData::Credentials currentCredentials = request->credentials();
    return createRequestWithRequestData(request.release(), RequestInit(init), currentMode, currentCredentials, exceptionState);
}

PassRefPtrWillBeRawPtr<Request> Request::create(PassRefPtrWillBeRawPtr<FetchRequestData> request)
{
    return adoptRefWillBeNoop(new Request(request));
}

Request::Request(PassRefPtrWillBeRawPtr<FetchRequestData> request)
    : m_request(request)
    , m_headers(Headers::create(m_request->headerList()))
{
    m_headers->setGuard(Headers::RequestGuard);
    ScriptWrappable::init(this);
}

PassRefPtrWillBeRawPtr<Request> Request::create(const WebServiceWorkerRequest& webRequest)
{
    return adoptRefWillBeNoop(new Request(webRequest));
}

Request::Request(const WebServiceWorkerRequest& webRequest)
    : m_request(FetchRequestData::create(webRequest))
    , m_headers(Headers::create(m_request->headerList()))
{
    m_headers->setGuard(Headers::RequestGuard);
    ScriptWrappable::init(this);
}

String Request::method() const
{
    // "The method attribute's getter must return request's method."
    return m_request->method();
}

String Request::url() const
{
    // The url attribute's getter must return request's url, serialized with the exclude fragment flag set.
    if (!m_request->url().hasFragmentIdentifier())
        return m_request->url();
    KURL url(m_request->url());
    url.removeFragmentIdentifier();
    return url;
}

String Request::referrer() const
{
    // "The referrer attribute's getter must return the empty string if
    // request's referrer is none, and request's referrer, serialized,
    // otherwise."
    return m_request->referrer().referrer().referrer;
}

String Request::mode() const
{
    // "The mode attribute's getter must return the value corresponding to the
    // first matching statement, switching on request's mode:"
    switch (m_request->mode()) {
    case FetchRequestData::SameOriginMode:
        return "same-origin";
    case FetchRequestData::NoCORSMode:
        return "no-cors";
    case FetchRequestData::CORSMode:
    case FetchRequestData::CORSWithForcedPreflight:
        return "cors";
    }
    ASSERT_NOT_REACHED();
    return "";
}

String Request::credentials() const
{
    // "The credentials attribute's getter must return the value corresponding
    // to the first matching statement, switching on request's credentials
    // mode:"
    switch (m_request->credentials()) {
    case FetchRequestData::OmitCredentials:
        return "omit";
    case FetchRequestData::SameOriginCredentials:
        return "same-origin";
    case FetchRequestData::IncludeCredentials:
        return "include";
    }
    ASSERT_NOT_REACHED();
    return "";
}

void Request::trace(Visitor* visitor)
{
    visitor->trace(m_request);
    visitor->trace(m_headers);
}

} // namespace blink
