Index: third_party/WebKit/Source/modules/fetch/FetchDataLoader.cpp |
diff --git a/third_party/WebKit/Source/modules/fetch/FetchDataLoader.cpp b/third_party/WebKit/Source/modules/fetch/FetchDataLoader.cpp |
index ac601a3b59e6087428c7204e09ad12a236334bb9..170249470fcb917733e830a064430bedd4e02233 100644 |
--- a/third_party/WebKit/Source/modules/fetch/FetchDataLoader.cpp |
+++ b/third_party/WebKit/Source/modules/fetch/FetchDataLoader.cpp |
@@ -5,9 +5,14 @@ |
#include "modules/fetch/FetchDataLoader.h" |
#include <memory> |
+#include "core/fileapi/File.h" |
+#include "core/html/FormData.h" |
#include "core/html/parser/TextResourceDecoder.h" |
#include "modules/fetch/BytesConsumer.h" |
+#include "modules/fetch/MultipartParser.h" |
#include "mojo/public/cpp/system/simple_watcher.h" |
+#include "platform/HTTPNames.h" |
+#include "platform/network/ParsedContentType.h" |
#include "platform/wtf/Functional.h" |
#include "platform/wtf/PtrUtil.h" |
#include "platform/wtf/text/StringBuilder.h" |
@@ -170,6 +175,236 @@ class FetchDataLoaderAsArrayBuffer final : public FetchDataLoader, |
std::unique_ptr<ArrayBufferBuilder> raw_data_; |
}; |
+class FetchDataLoaderAsFailure final : public FetchDataLoader, |
+ public BytesConsumer::Client { |
+ USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsFailure); |
+ |
+ public: |
+ void Start(BytesConsumer* consumer, |
+ FetchDataLoader::Client* client) override { |
+ DCHECK(!client_); |
+ DCHECK(!consumer_); |
+ client_ = client; |
+ consumer_ = consumer; |
+ consumer_->SetClient(this); |
+ OnStateChange(); |
+ } |
+ |
+ void OnStateChange() override { |
+ while (true) { |
+ const char* buffer; |
+ size_t available; |
+ auto result = consumer_->BeginRead(&buffer, &available); |
+ if (result == BytesConsumer::Result::kShouldWait) |
+ return; |
+ if (result == BytesConsumer::Result::kOk) |
+ result = consumer_->EndRead(available); |
+ switch (result) { |
+ case BytesConsumer::Result::kOk: |
+ break; |
+ case BytesConsumer::Result::kShouldWait: |
+ NOTREACHED(); |
+ return; |
+ case BytesConsumer::Result::kDone: |
+ case BytesConsumer::Result::kError: |
+ client_->DidFetchDataLoadFailed(); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ void Cancel() override { consumer_->Cancel(); } |
+ |
+ DEFINE_INLINE_TRACE() { |
+ visitor->Trace(consumer_); |
+ visitor->Trace(client_); |
+ FetchDataLoader::Trace(visitor); |
+ BytesConsumer::Client::Trace(visitor); |
+ } |
+ |
+ private: |
+ Member<BytesConsumer> consumer_; |
+ Member<FetchDataLoader::Client> client_; |
+}; |
+ |
+class FetchDataLoaderAsFormData final : public FetchDataLoader, |
+ public BytesConsumer::Client, |
+ public MultipartParser::Client { |
+ USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsFormData); |
+ |
+ public: |
+ explicit FetchDataLoaderAsFormData(const String& multipart_boundary) |
+ : multipart_boundary_(multipart_boundary) {} |
+ |
+ void Start(BytesConsumer* consumer, |
+ FetchDataLoader::Client* client) override { |
+ DCHECK(!client_); |
+ DCHECK(!consumer_); |
+ DCHECK(!form_data_); |
+ DCHECK(!multipart_parser_); |
+ |
+ const CString multipart_boundary_utf8 = multipart_boundary_.Utf8(); |
+ Vector<char> multipart_boundary_vector; |
+ multipart_boundary_vector.Append(multipart_boundary_utf8.data(), |
+ multipart_boundary_utf8.length()); |
+ |
+ client_ = client; |
+ form_data_ = FormData::Create(); |
+ multipart_parser_ = |
+ new MultipartParser(std::move(multipart_boundary_vector), this); |
+ consumer_ = consumer; |
+ consumer_->SetClient(this); |
+ OnStateChange(); |
+ } |
+ |
+ void OnStateChange() override { |
+ while (true) { |
+ const char* buffer; |
+ size_t available; |
+ auto result = consumer_->BeginRead(&buffer, &available); |
+ if (result == BytesConsumer::Result::kShouldWait) |
+ return; |
+ if (result == BytesConsumer::Result::kOk) { |
+ const bool buffer_appended = |
+ multipart_parser_->AppendData(buffer, available); |
+ const bool multipart_receive_failed = multipart_parser_->IsCancelled(); |
+ result = consumer_->EndRead(available); |
+ if (!buffer_appended || multipart_receive_failed) |
+ result = BytesConsumer::Result::kError; |
+ } |
+ switch (result) { |
+ case BytesConsumer::Result::kOk: |
+ break; |
+ case BytesConsumer::Result::kShouldWait: |
+ NOTREACHED(); |
+ return; |
+ case BytesConsumer::Result::kDone: |
+ if (multipart_parser_->Finish()) { |
+ DCHECK(!multipart_parser_->IsCancelled()); |
+ client_->DidFetchDataLoadedFormData(form_data_); |
+ } else { |
+ client_->DidFetchDataLoadFailed(); |
+ } |
+ return; |
+ case BytesConsumer::Result::kError: |
+ client_->DidFetchDataLoadFailed(); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ void Cancel() override { |
+ consumer_->Cancel(); |
+ multipart_parser_->Cancel(); |
+ } |
+ |
+ DEFINE_INLINE_TRACE() { |
+ visitor->Trace(consumer_); |
+ visitor->Trace(client_); |
+ visitor->Trace(form_data_); |
+ visitor->Trace(multipart_parser_); |
+ FetchDataLoader::Trace(visitor); |
+ BytesConsumer::Client::Trace(visitor); |
+ MultipartParser::Client::Trace(visitor); |
+ } |
+ |
+ private: |
+ void PartHeaderFieldsInMultipartReceived( |
+ const HTTPHeaderMap& header_fields) override { |
+ if (!current_entry_.Initialize(header_fields)) |
+ multipart_parser_->Cancel(); |
+ } |
+ |
+ void PartDataInMultipartReceived(const char* bytes, size_t size) override { |
+ if (!current_entry_.AppendBytes(bytes, size)) |
+ multipart_parser_->Cancel(); |
+ } |
+ |
+ void PartDataInMultipartFullyReceived() override { |
+ if (!current_entry_.Finish(form_data_)) |
+ multipart_parser_->Cancel(); |
+ } |
+ |
+ class Entry { |
+ public: |
+ bool Initialize(const HTTPHeaderMap& header_fields) { |
+ // TODO(e_hakkinen): Use a proper Content-Disposition parser instead of |
+ // converting content disposition to a faked content type and parsing |
+ // that. |
+ const String fakePrefix = "x-fake/"; |
+ const ParsedContentType disposition( |
+ fakePrefix + header_fields.Get(HTTPNames::Content_Disposition)); |
+ // The faked MIME type without the faked prefix is a proper disposition |
+ // type. |
+ const String disposition_type = |
+ disposition.MimeType().Substring(fakePrefix.length()); |
+ filename_ = disposition.ParameterValueForName("filename"); |
+ name_ = disposition.ParameterValueForName("name"); |
+ blob_data_.reset(); |
+ string_builder_.reset(); |
+ if (disposition_type != "form-data" || name_.IsNull()) |
+ return false; |
+ if (!filename_.IsNull()) { |
+ blob_data_ = BlobData::Create(); |
+ const AtomicString& content_type = |
+ header_fields.Get(HTTPNames::Content_Type); |
+ blob_data_->SetContentType(content_type.IsNull() ? "text/plain" |
+ : content_type); |
+ } else { |
+ if (!string_decoder_) |
+ string_decoder_ = TextResourceDecoder::CreateAlwaysUseUTF8ForText(); |
+ string_builder_.reset(new StringBuilder); |
+ } |
+ return true; |
+ } |
+ |
+ bool AppendBytes(const char* bytes, size_t size) { |
+ if (blob_data_) |
+ blob_data_->AppendBytes(bytes, size); |
+ if (string_builder_) { |
+ string_builder_->Append(string_decoder_->Decode(bytes, size)); |
+ if (string_decoder_->SawError()) |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ bool Finish(FormData* form_data) { |
+ if (blob_data_) { |
+ DCHECK(!string_builder_); |
+ const auto size = blob_data_->length(); |
+ File* file = |
+ File::Create(filename_, InvalidFileTime(), |
+ BlobDataHandle::Create(std::move(blob_data_), size)); |
+ form_data->append(name_, file, filename_); |
+ return true; |
+ } |
+ DCHECK(!blob_data_); |
+ DCHECK(string_builder_); |
+ string_builder_->Append(string_decoder_->Flush()); |
+ if (string_decoder_->SawError()) |
+ return false; |
+ form_data->append(name_, string_builder_->ToString()); |
+ return true; |
+ } |
+ |
+ private: |
+ std::unique_ptr<BlobData> blob_data_; |
+ String filename_; |
+ String name_; |
+ std::unique_ptr<StringBuilder> string_builder_; |
+ std::unique_ptr<TextResourceDecoder> string_decoder_; |
+ }; |
+ |
+ Member<BytesConsumer> consumer_; |
+ Member<FetchDataLoader::Client> client_; |
+ Member<FormData> form_data_; |
+ Member<MultipartParser> multipart_parser_; |
+ |
+ Entry current_entry_; |
+ String multipart_boundary_; |
+}; |
+ |
class FetchDataLoaderAsString final : public FetchDataLoader, |
public BytesConsumer::Client { |
USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsString); |
@@ -341,6 +576,15 @@ FetchDataLoader* FetchDataLoader::CreateLoaderAsArrayBuffer() { |
return new FetchDataLoaderAsArrayBuffer(); |
} |
+FetchDataLoader* FetchDataLoader::CreateLoaderAsFailure() { |
+ return new FetchDataLoaderAsFailure(); |
+} |
+ |
+FetchDataLoader* FetchDataLoader::CreateLoaderAsFormData( |
+ const String& multipartBoundary) { |
+ return new FetchDataLoaderAsFormData(multipartBoundary); |
+} |
+ |
FetchDataLoader* FetchDataLoader::CreateLoaderAsString() { |
return new FetchDataLoaderAsString(); |
} |