blob: ded01690aab7c7c2e60f26eedb7d8b7da9b360e3 [file] [log] [blame]
// 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 "third_party/blink/renderer/core/fetch/body.h"
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "third_party/blink/public/platform/web_data_consumer_handle.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fetch/body_stream_buffer.h"
#include "third_party/blink/renderer/core/fetch/fetch_data_loader.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/core/url/url_search_params.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
#include "third_party/blink/renderer/platform/network/parsed_content_type.h"
namespace blink {
namespace {
class BodyConsumerBase : public GarbageCollectedFinalized<BodyConsumerBase>,
public FetchDataLoader::Client {
USING_GARBAGE_COLLECTED_MIXIN(BodyConsumerBase);
public:
explicit BodyConsumerBase(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
ScriptPromiseResolver* Resolver() { return resolver_; }
void DidFetchDataLoadFailed() override {
ScriptState::Scope scope(Resolver()->GetScriptState());
resolver_->Reject(V8ThrowException::CreateTypeError(
Resolver()->GetScriptState()->GetIsolate(), "Failed to fetch"));
}
void Abort() override {
resolver_->Reject(DOMException::Create(DOMExceptionCode::kAbortError));
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(resolver_);
FetchDataLoader::Client::Trace(visitor);
}
private:
Member<ScriptPromiseResolver> resolver_;
DISALLOW_COPY_AND_ASSIGN(BodyConsumerBase);
};
class BodyBlobConsumer final : public BodyConsumerBase {
public:
explicit BodyBlobConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void DidFetchDataLoadedBlobHandle(
scoped_refptr<BlobDataHandle> blob_data_handle) override {
Resolver()->Resolve(Blob::Create(std::move(blob_data_handle)));
}
DISALLOW_COPY_AND_ASSIGN(BodyBlobConsumer);
};
class BodyArrayBufferConsumer final : public BodyConsumerBase {
public:
explicit BodyArrayBufferConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void DidFetchDataLoadedArrayBuffer(DOMArrayBuffer* array_buffer) override {
Resolver()->Resolve(array_buffer);
}
DISALLOW_COPY_AND_ASSIGN(BodyArrayBufferConsumer);
};
class BodyFormDataConsumer final : public BodyConsumerBase {
public:
explicit BodyFormDataConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void DidFetchDataLoadedFormData(FormData* formData) override {
Resolver()->Resolve(formData);
}
void DidFetchDataLoadedString(const String& string) override {
FormData* formData = FormData::Create();
for (const auto& pair : URLSearchParams::Create(string)->Params())
formData->append(pair.first, pair.second);
DidFetchDataLoadedFormData(formData);
}
DISALLOW_COPY_AND_ASSIGN(BodyFormDataConsumer);
};
class BodyTextConsumer final : public BodyConsumerBase {
public:
explicit BodyTextConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void DidFetchDataLoadedString(const String& string) override {
Resolver()->Resolve(string);
}
DISALLOW_COPY_AND_ASSIGN(BodyTextConsumer);
};
class BodyJsonConsumer final : public BodyConsumerBase {
public:
explicit BodyJsonConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void DidFetchDataLoadedString(const String& string) override {
if (!Resolver()->GetExecutionContext() ||
Resolver()->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState::Scope scope(Resolver()->GetScriptState());
v8::Isolate* isolate = Resolver()->GetScriptState()->GetIsolate();
v8::Local<v8::String> input_string = V8String(isolate, string);
v8::TryCatch trycatch(isolate);
v8::Local<v8::Value> parsed;
if (v8::JSON::Parse(Resolver()->GetScriptState()->GetContext(),
input_string)
.ToLocal(&parsed))
Resolver()->Resolve(parsed);
else
Resolver()->Reject(trycatch.Exception());
}
DISALLOW_COPY_AND_ASSIGN(BodyJsonConsumer);
};
} // namespace
ScriptPromise Body::arrayBuffer(ScriptState* script_state,
ExceptionState& exception_state) {
RejectInvalidConsumption(script_state, exception_state);
if (exception_state.HadException())
return ScriptPromise();
// When the main thread sends a V8::TerminateExecution() signal to a worker
// thread, any V8 API on the worker thread starts returning an empty
// handle. This can happen in this function. To avoid the situation, we
// first check the ExecutionContext and return immediately if it's already
// gone (which means that the V8::TerminateExecution() signal has been sent
// to this worker thread).
if (!ExecutionContext::From(script_state))
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (BodyBuffer()) {
BodyBuffer()->StartLoading(FetchDataLoader::CreateLoaderAsArrayBuffer(),
new BodyArrayBufferConsumer(resolver),
exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
} else {
resolver->Resolve(DOMArrayBuffer::Create(0u, 1));
}
return promise;
}
ScriptPromise Body::blob(ScriptState* script_state,
ExceptionState& exception_state) {
RejectInvalidConsumption(script_state, exception_state);
if (exception_state.HadException())
return ScriptPromise();
// See above comment.
if (!ExecutionContext::From(script_state))
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (BodyBuffer()) {
BodyBuffer()->StartLoading(
FetchDataLoader::CreateLoaderAsBlobHandle(MimeType()),
new BodyBlobConsumer(resolver), exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
} else {
std::unique_ptr<BlobData> blob_data = BlobData::Create();
blob_data->SetContentType(MimeType());
resolver->Resolve(
Blob::Create(BlobDataHandle::Create(std::move(blob_data), 0)));
}
return promise;
}
ScriptPromise Body::formData(ScriptState* script_state,
ExceptionState& exception_state) {
RejectInvalidConsumption(script_state, exception_state);
if (exception_state.HadException())
return ScriptPromise();
// See above comment.
if (!ExecutionContext::From(script_state))
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ParsedContentType parsedTypeWithParameters(ContentType());
const String parsedType = parsedTypeWithParameters.MimeType().LowerASCII();
ScriptPromise promise = resolver->Promise();
if (parsedType == "multipart/form-data") {
const String boundary =
parsedTypeWithParameters.ParameterValueForName("boundary");
auto* body_buffer = BodyBuffer();
if (body_buffer && !boundary.IsEmpty()) {
body_buffer->StartLoading(
FetchDataLoader::CreateLoaderAsFormData(boundary),
new BodyFormDataConsumer(resolver), exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
return promise;
}
} else if (parsedType == "application/x-www-form-urlencoded") {
if (BodyBuffer()) {
BodyBuffer()->StartLoading(FetchDataLoader::CreateLoaderAsString(),
new BodyFormDataConsumer(resolver),
exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
} else {
resolver->Resolve(FormData::Create());
}
return promise;
} else {
if (BodyBuffer()) {
BodyBuffer()->StartLoading(FetchDataLoader::CreateLoaderAsFailure(),
new BodyFormDataConsumer(resolver),
exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
return promise;
}
}
resolver->Reject(V8ThrowException::CreateTypeError(script_state->GetIsolate(),
"Invalid MIME type"));
return promise;
}
ScriptPromise Body::json(ScriptState* script_state,
ExceptionState& exception_state) {
RejectInvalidConsumption(script_state, exception_state);
if (exception_state.HadException())
return ScriptPromise();
// See above comment.
if (!ExecutionContext::From(script_state))
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (BodyBuffer()) {
BodyBuffer()->StartLoading(FetchDataLoader::CreateLoaderAsString(),
new BodyJsonConsumer(resolver), exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
} else {
resolver->Reject(V8ThrowException::CreateSyntaxError(
script_state->GetIsolate(), "Unexpected end of input"));
}
return promise;
}
ScriptPromise Body::text(ScriptState* script_state,
ExceptionState& exception_state) {
RejectInvalidConsumption(script_state, exception_state);
if (exception_state.HadException())
return ScriptPromise();
// See above comment.
if (!ExecutionContext::From(script_state))
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (BodyBuffer()) {
BodyBuffer()->StartLoading(FetchDataLoader::CreateLoaderAsString(),
new BodyTextConsumer(resolver), exception_state);
if (exception_state.HadException()) {
// Need to resolve the ScriptPromiseResolver to avoid a DCHECK().
resolver->Resolve();
return ScriptPromise();
}
} else {
resolver->Resolve(String());
}
return promise;
}
ReadableStream* Body::body() {
if (!BodyBuffer())
return nullptr;
return BodyBuffer()->Stream();
}
Body::BodyUsed Body::IsBodyUsed(ExceptionState& exception_state) {
auto* body_buffer = BodyBuffer();
if (!body_buffer)
return BodyUsed::kUnused;
base::Optional<bool> stream_disturbed =
body_buffer->IsStreamDisturbed(exception_state);
if (exception_state.HadException())
return BodyUsed::kBroken;
return stream_disturbed.value() ? BodyUsed::kUsed : BodyUsed::kUnused;
}
Body::BodyLocked Body::IsBodyLocked(ExceptionState& exception_state) {
auto* body_buffer = BodyBuffer();
if (!body_buffer)
return BodyLocked::kUnlocked;
base::Optional<bool> is_locked = body_buffer->IsStreamLocked(exception_state);
if (exception_state.HadException())
return BodyLocked::kBroken;
return is_locked.value() ? BodyLocked::kLocked : BodyLocked::kUnlocked;
}
bool Body::HasPendingActivity() const {
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed())
return false;
auto* body_buffer = BodyBuffer();
if (!body_buffer)
return false;
return body_buffer->HasPendingActivity();
}
bool Body::IsBodyUsedForDCheck(ExceptionState& exception_state) {
return BodyBuffer() &&
BodyBuffer()->IsStreamDisturbedForDCheck(exception_state);
}
Body::Body(ExecutionContext* context) : ContextClient(context) {}
void Body::RejectInvalidConsumption(ScriptState* script_state,
ExceptionState& exception_state) {
const auto used = IsBodyUsed(exception_state);
if (exception_state.HadException()) {
DCHECK_EQ(used, BodyUsed::kBroken);
return;
}
DCHECK_NE(used, BodyUsed::kBroken);
if (IsBodyLocked(exception_state) == BodyLocked::kLocked) {
DCHECK(!exception_state.HadException());
exception_state.ThrowTypeError("body stream is locked");
}
if (exception_state.HadException())
return;
if (used == BodyUsed::kUsed)
exception_state.ThrowTypeError("body stream already read");
}
} // namespace blink