blob: 02e31350f26e0c9b18524cd4249dbb8672427697 [file] [log] [blame]
/*
* Copyright (C) 2016 Canon Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted, provided that the following conditions
* are required to be met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of Canon Inc. nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "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 CANON INC. AND ITS 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 "FetchResponse.h"
#if ENABLE(FETCH_API)
#include "ExceptionCode.h"
#include "FetchRequest.h"
#include "HTTPParsers.h"
#include "JSBlob.h"
#include "JSFetchResponse.h"
#include "ScriptExecutionContext.h"
namespace WebCore {
static inline bool isRedirectStatus(int status)
{
return status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
}
Ref<FetchResponse> FetchResponse::error(ScriptExecutionContext& context)
{
auto response = adoptRef(*new FetchResponse(context, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
response->m_response.setType(Type::Error);
return response;
}
ExceptionOr<Ref<FetchResponse>> FetchResponse::redirect(ScriptExecutionContext& context, const String& url, int status)
{
// FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
URL requestURL = context.completeURL(url);
if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty())
return Exception { TypeError };
if (!isRedirectStatus(status))
return Exception { RangeError };
auto redirectResponse = adoptRef(*new FetchResponse(context, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
redirectResponse->m_response.setHTTPStatusCode(status);
redirectResponse->m_headers->fastSet(HTTPHeaderName::Location, requestURL.string());
return WTFMove(redirectResponse);
}
ExceptionOr<void> FetchResponse::setStatus(int status, const String& statusText)
{
if (!isValidReasonPhrase(statusText))
return Exception { TypeError };
m_response.setHTTPStatusCode(status);
m_response.setHTTPStatusText(statusText);
return { };
}
void FetchResponse::initializeWith(JSC::ExecState& execState, JSC::JSValue body)
{
ASSERT(scriptExecutionContext());
extractBody(*scriptExecutionContext(), execState, body);
updateContentType();
}
FetchResponse::FetchResponse(ScriptExecutionContext& context, Optional<FetchBody>&& body, Ref<FetchHeaders>&& headers, ResourceResponse&& response)
: FetchBodyOwner(context, WTFMove(body), WTFMove(headers))
, m_response(WTFMove(response))
{
}
Ref<FetchResponse> FetchResponse::cloneForJS()
{
ASSERT(scriptExecutionContext());
ASSERT(!isDisturbedOrLocked());
auto clone = adoptRef(*new FetchResponse(*scriptExecutionContext(), Nullopt, FetchHeaders::create(headers()), ResourceResponse(m_response)));
clone->cloneBody(*this);
return clone;
}
void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& request, FetchPromise&& promise)
{
auto response = adoptRef(*new FetchResponse(context, FetchBody::loadingBody(), FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
// Setting pending activity until BodyLoader didFail or didSucceed callback is called.
response->setPendingActivity(response.ptr());
response->m_bodyLoader = BodyLoader(response.get(), WTFMove(promise));
if (!response->m_bodyLoader->start(context, request))
response->m_bodyLoader = Nullopt;
}
const String& FetchResponse::url() const
{
if (m_responseURL.isNull())
m_responseURL = m_response.url().serialize(true);
return m_responseURL;
}
void FetchResponse::BodyLoader::didSucceed()
{
ASSERT(m_response.hasPendingActivity());
m_response.m_body->loadingSucceeded();
#if ENABLE(READABLE_STREAM_API)
if (m_response.m_readableStreamSource && !m_response.body().consumer().hasData())
m_response.closeStream();
#endif
if (m_loader->isStarted())
m_response.m_bodyLoader = Nullopt;
m_response.unsetPendingActivity(&m_response);
}
void FetchResponse::BodyLoader::didFail()
{
ASSERT(m_response.hasPendingActivity());
if (m_promise)
std::exchange(m_promise, Nullopt)->reject(TypeError);
#if ENABLE(READABLE_STREAM_API)
if (m_response.m_readableStreamSource) {
if (!m_response.m_readableStreamSource->isCancelling())
m_response.m_readableStreamSource->error(ASCIILiteral("Loading failed"));
m_response.m_readableStreamSource = nullptr;
}
#endif
// Check whether didFail is called as part of FetchLoader::start.
if (m_loader->isStarted())
m_response.m_bodyLoader = Nullopt;
m_response.unsetPendingActivity(&m_response);
}
FetchResponse::BodyLoader::BodyLoader(FetchResponse& response, FetchPromise&& promise)
: m_response(response)
, m_promise(WTFMove(promise))
{
}
void FetchResponse::BodyLoader::didReceiveResponse(const ResourceResponse& resourceResponse)
{
ASSERT(m_promise);
m_response.m_response = resourceResponse;
m_response.m_headers->filterAndFill(resourceResponse.httpHeaderFields(), FetchHeaders::Guard::Response);
std::exchange(m_promise, Nullopt)->resolve(m_response);
}
void FetchResponse::BodyLoader::didReceiveData(const char* data, size_t size)
{
#if ENABLE(READABLE_STREAM_API)
ASSERT(m_response.m_readableStreamSource);
auto& source = *m_response.m_readableStreamSource;
if (!source.isPulling()) {
m_response.body().consumer().append(data, size);
return;
}
if (m_response.body().consumer().hasData() && !source.enqueue(m_response.body().consumer().takeAsArrayBuffer())) {
stop();
return;
}
if (!source.enqueue(ArrayBuffer::tryCreate(data, size))) {
stop();
return;
}
source.resolvePullPromise();
#else
UNUSED_PARAM(data);
UNUSED_PARAM(size);
#endif
}
bool FetchResponse::BodyLoader::start(ScriptExecutionContext& context, const FetchRequest& request)
{
m_loader = std::make_unique<FetchLoader>(*this, &m_response.m_body->consumer());
m_loader->start(context, request);
return m_loader->isStarted();
}
void FetchResponse::BodyLoader::stop()
{
m_promise = Nullopt;
if (m_loader)
m_loader->stop();
}
void FetchResponse::consume(unsigned type, Ref<DeferredPromise>&& wrapper)
{
ASSERT(type <= static_cast<unsigned>(FetchBodyConsumer::Type::Text));
auto consumerType = static_cast<FetchBodyConsumer::Type>(type);
if (isLoading()) {
consumeOnceLoadingFinished(consumerType, WTFMove(wrapper));
return;
}
switch (consumerType) {
case FetchBodyConsumer::Type::ArrayBuffer:
arrayBuffer(WTFMove(wrapper));
return;
case FetchBodyConsumer::Type::Blob:
blob(WTFMove(wrapper));
return;
case FetchBodyConsumer::Type::JSON:
json(WTFMove(wrapper));
return;
case FetchBodyConsumer::Type::Text:
text(WTFMove(wrapper));
return;
case FetchBodyConsumer::Type::None:
ASSERT_NOT_REACHED();
return;
}
}
#if ENABLE(READABLE_STREAM_API)
void FetchResponse::startConsumingStream(unsigned type)
{
m_isDisturbed = true;
m_consumer.setType(static_cast<FetchBodyConsumer::Type>(type));
}
void FetchResponse::consumeChunk(Ref<JSC::Uint8Array>&& chunk)
{
m_consumer.append(chunk->data(), chunk->byteLength());
}
void FetchResponse::finishConsumingStream(Ref<DeferredPromise>&& promise)
{
m_consumer.resolve(WTFMove(promise));
}
void FetchResponse::consumeBodyAsStream()
{
ASSERT(m_readableStreamSource);
m_isDisturbed = true;
if (!isLoading()) {
body().consumeAsStream(*this, *m_readableStreamSource);
if (!m_readableStreamSource->isPulling())
m_readableStreamSource = nullptr;
return;
}
ASSERT(m_bodyLoader);
RefPtr<SharedBuffer> data = m_bodyLoader->startStreaming();
if (data) {
if (!m_readableStreamSource->enqueue(data->createArrayBuffer())) {
stop();
return;
}
m_readableStreamSource->resolvePullPromise();
}
}
void FetchResponse::closeStream()
{
ASSERT(m_readableStreamSource);
m_readableStreamSource->close();
m_readableStreamSource = nullptr;
}
void FetchResponse::feedStream()
{
ASSERT(m_readableStreamSource);
bool shouldCloseStream = !m_bodyLoader;
if (body().consumer().hasData()) {
if (!m_readableStreamSource->enqueue(body().consumer().takeAsArrayBuffer())) {
stop();
return;
}
if (!shouldCloseStream) {
m_readableStreamSource->resolvePullPromise();
return;
}
} else if (!shouldCloseStream)
return;
closeStream();
}
ReadableStreamSource* FetchResponse::createReadableStreamSource()
{
ASSERT(!m_readableStreamSource);
ASSERT(!m_isDisturbed);
if (isBodyNull())
return nullptr;
m_readableStreamSource = adoptRef(*new FetchResponseSource(*this));
return m_readableStreamSource.get();
}
RefPtr<SharedBuffer> FetchResponse::BodyLoader::startStreaming()
{
ASSERT(m_loader);
return m_loader->startStreaming();
}
void FetchResponse::cancel()
{
m_isDisturbed = true;
stop();
}
#endif
void FetchResponse::stop()
{
RefPtr<FetchResponse> protectedThis(this);
FetchBodyOwner::stop();
if (m_bodyLoader) {
m_bodyLoader->stop();
ASSERT(!m_bodyLoader);
}
}
const char* FetchResponse::activeDOMObjectName() const
{
return "Response";
}
bool FetchResponse::canSuspendForDocumentSuspension() const
{
// FIXME: We can probably do the same strategy as XHR.
return !isActive();
}
} // namespace WebCore
#endif // ENABLE(FETCH_API)