| Index: third_party/WebKit/Source/modules/fetch/MultipartParser.cpp
|
| diff --git a/third_party/WebKit/Source/modules/fetch/MultipartParser.cpp b/third_party/WebKit/Source/modules/fetch/MultipartParser.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..46af956753aec05d69c6edfec86cd8b07ac38744
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/modules/fetch/MultipartParser.cpp
|
| @@ -0,0 +1,333 @@
|
| +// Copyright 2016 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 "modules/fetch/MultipartParser.h"
|
| +
|
| +#include "platform/HTTPNames.h"
|
| +#include "platform/network/HTTPParsers.h"
|
| +#include "public/platform/Platform.h"
|
| +
|
| +#include <algorithm>
|
| +#include <utility>
|
| +
|
| +namespace blink {
|
| +
|
| +namespace {
|
| +
|
| +constexpr char kCloseDelimiterSuffix[] = "--\r\n";
|
| +constexpr size_t kCloseDelimiterSuffixSize =
|
| + WTF_ARRAY_LENGTH(kCloseDelimiterSuffix) - 1u;
|
| +constexpr size_t kDashBoundaryOffset = 2u; // The length of "\r\n".
|
| +constexpr char kDelimiterSuffix[] = "\r\n";
|
| +constexpr size_t kDelimiterSuffixSize = WTF_ARRAY_LENGTH(kDelimiterSuffix) - 1u;
|
| +
|
| +} // namespace
|
| +
|
| +MultipartParser::Matcher::Matcher() = default;
|
| +
|
| +MultipartParser::Matcher::Matcher(const char* data,
|
| + size_t num_matched_bytes,
|
| + size_t size)
|
| + : data_(data), num_matched_bytes_(num_matched_bytes), size_(size) {}
|
| +
|
| +bool MultipartParser::Matcher::Match(const char* first, const char* last) {
|
| + while (first < last) {
|
| + if (!Match(*first++))
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +void MultipartParser::Matcher::SetNumMatchedBytes(size_t num_matched_bytes) {
|
| + DCHECK_LE(num_matched_bytes, size_);
|
| + num_matched_bytes_ = num_matched_bytes;
|
| +}
|
| +
|
| +MultipartParser::MultipartParser(Vector<char> boundary, Client* client)
|
| + : client_(client),
|
| + delimiter_(std::move(boundary)),
|
| + state_(State::kParsingPreamble) {
|
| + // The delimiter consists of "\r\n" and a dash boundary which consists of
|
| + // "--" and a boundary.
|
| + delimiter_.push_front("\r\n--", 4u);
|
| + matcher_ = DelimiterMatcher(kDashBoundaryOffset);
|
| +}
|
| +
|
| +bool MultipartParser::AppendData(const char* bytes, size_t size) {
|
| + DCHECK_NE(State::kFinished, state_);
|
| + DCHECK_NE(State::kCancelled, state_);
|
| +
|
| + const char* const bytes_end = bytes + size;
|
| +
|
| + while (bytes < bytes_end) {
|
| + switch (state_) {
|
| + case State::kParsingPreamble:
|
| + // Parse either a preamble and a delimiter or a dash boundary.
|
| + ParseDelimiter(&bytes, bytes_end);
|
| + if (!matcher_.IsMatchComplete() && bytes < bytes_end) {
|
| + // Parse a preamble data (by ignoring it) and then a delimiter.
|
| + matcher_.SetNumMatchedBytes(0u);
|
| + ParseDataAndDelimiter(&bytes, bytes_end);
|
| + }
|
| + if (matcher_.IsMatchComplete()) {
|
| + // Prepare for a delimiter suffix.
|
| + matcher_ = DelimiterSuffixMatcher();
|
| + state_ = State::kParsingDelimiterSuffix;
|
| + }
|
| + break;
|
| +
|
| + case State::kParsingDelimiterSuffix:
|
| + // Parse transport padding and "\r\n" after a delimiter.
|
| + // This state can be reached after either a preamble or part
|
| + // octets are parsed.
|
| + if (matcher_.NumMatchedBytes() == 0u)
|
| + ParseTransportPadding(&bytes, bytes_end);
|
| + while (bytes < bytes_end) {
|
| + if (!matcher_.Match(*bytes++))
|
| + return false;
|
| + if (matcher_.IsMatchComplete()) {
|
| + // Prepare for part header fields.
|
| + state_ = State::kParsingPartHeaderFields;
|
| + break;
|
| + }
|
| + }
|
| + break;
|
| +
|
| + case State::kParsingPartHeaderFields: {
|
| + // Parse part header fields (which ends with "\r\n") and an empty
|
| + // line (which also ends with "\r\n").
|
| + // This state can be reached after a delimiter and a delimiter
|
| + // suffix after either a preamble or part octets are parsed.
|
| + HTTPHeaderMap header_fields;
|
| + if (ParseHeaderFields(&bytes, bytes_end, &header_fields)) {
|
| + // Prepare for part octets.
|
| + matcher_ = DelimiterMatcher();
|
| + state_ = State::kParsingPartOctets;
|
| + client_->PartHeaderFieldsInMultipartReceived(header_fields);
|
| + }
|
| + break;
|
| + }
|
| +
|
| + case State::kParsingPartOctets: {
|
| + // Parse part octets and a delimiter.
|
| + // This state can be reached only after part header fields are
|
| + // parsed.
|
| + const size_t num_initially_matched_bytes = matcher_.NumMatchedBytes();
|
| + const char* octets_begin = bytes;
|
| + ParseDelimiter(&bytes, bytes_end);
|
| + if (!matcher_.IsMatchComplete() && bytes < bytes_end) {
|
| + if (matcher_.NumMatchedBytes() >= num_initially_matched_bytes &&
|
| + num_initially_matched_bytes > 0u) {
|
| + // Since the matched bytes did not form a complete
|
| + // delimiter, the matched bytes turned out to be octet
|
| + // bytes instead of being delimiter bytes. Additionally,
|
| + // some of the matched bytes are from the previous call and
|
| + // are therefore not in the range [octetsBegin, bytesEnd[.
|
| + client_->PartDataInMultipartReceived(matcher_.Data(),
|
| + matcher_.NumMatchedBytes());
|
| + if (state_ != State::kParsingPartOctets)
|
| + break;
|
| + octets_begin = bytes;
|
| + }
|
| + matcher_.SetNumMatchedBytes(0u);
|
| + ParseDataAndDelimiter(&bytes, bytes_end);
|
| + const char* const octets_end = bytes - matcher_.NumMatchedBytes();
|
| + if (octets_begin < octets_end) {
|
| + client_->PartDataInMultipartReceived(
|
| + octets_begin, static_cast<size_t>(octets_end - octets_begin));
|
| + if (state_ != State::kParsingPartOctets)
|
| + break;
|
| + }
|
| + }
|
| + if (matcher_.IsMatchComplete()) {
|
| + state_ = State::kParsingDelimiterOrCloseDelimiterSuffix;
|
| + client_->PartDataInMultipartFullyReceived();
|
| + }
|
| + break;
|
| + }
|
| +
|
| + case State::kParsingDelimiterOrCloseDelimiterSuffix:
|
| + // Determine whether this is a delimiter suffix or a close
|
| + // delimiter suffix.
|
| + // This state can be reached only after part octets are parsed.
|
| + if (*bytes == '-') {
|
| + // Prepare for a close delimiter suffix.
|
| + matcher_ = CloseDelimiterSuffixMatcher();
|
| + state_ = State::kParsingCloseDelimiterSuffix;
|
| + } else {
|
| + // Prepare for a delimiter suffix.
|
| + matcher_ = DelimiterSuffixMatcher();
|
| + state_ = State::kParsingDelimiterSuffix;
|
| + }
|
| + break;
|
| +
|
| + case State::kParsingCloseDelimiterSuffix:
|
| + // Parse "--", transport padding and "\r\n" after a delimiter
|
| + // (a delimiter and "--" constitute a close delimiter).
|
| + // This state can be reached only after part octets are parsed.
|
| + for (;;) {
|
| + if (matcher_.NumMatchedBytes() == 2u)
|
| + ParseTransportPadding(&bytes, bytes_end);
|
| + if (bytes >= bytes_end)
|
| + break;
|
| + if (!matcher_.Match(*bytes++))
|
| + return false;
|
| + if (matcher_.IsMatchComplete()) {
|
| + // Prepare for an epilogue.
|
| + state_ = State::kParsingEpilogue;
|
| + break;
|
| + }
|
| + }
|
| + break;
|
| +
|
| + case State::kParsingEpilogue:
|
| + // Parse an epilogue (by ignoring it).
|
| + // This state can be reached only after a delimiter and a close
|
| + // delimiter suffix after part octets are parsed.
|
| + return true;
|
| +
|
| + case State::kCancelled:
|
| + case State::kFinished:
|
| + // The client changed the state.
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + DCHECK_EQ(bytes_end, bytes);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void MultipartParser::Cancel() {
|
| + state_ = State::kCancelled;
|
| +}
|
| +
|
| +bool MultipartParser::Finish() {
|
| + DCHECK_NE(State::kCancelled, state_);
|
| + DCHECK_NE(State::kFinished, state_);
|
| +
|
| + const State initial_state = state_;
|
| + state_ = State::kFinished;
|
| +
|
| + switch (initial_state) {
|
| + case State::kParsingPartOctets:
|
| + if (matcher_.NumMatchedBytes() > 0u) {
|
| + // Since the matched bytes did not form a complete delimiter,
|
| + // the matched bytes turned out to be octet bytes instead of being
|
| + // delimiter bytes.
|
| + client_->PartDataInMultipartReceived(matcher_.Data(),
|
| + matcher_.NumMatchedBytes());
|
| + }
|
| + return false;
|
| + case State::kParsingCloseDelimiterSuffix:
|
| + // Require a full close delimiter consisting of a delimiter and "--"
|
| + // but ignore missing or partial "\r\n" after that.
|
| + return matcher_.NumMatchedBytes() >= 2u;
|
| + case State::kParsingEpilogue:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +MultipartParser::Matcher MultipartParser::CloseDelimiterSuffixMatcher() const {
|
| + return Matcher(kCloseDelimiterSuffix, 0u, kCloseDelimiterSuffixSize);
|
| +}
|
| +
|
| +MultipartParser::Matcher MultipartParser::DelimiterMatcher(
|
| + size_t num_already_matched_bytes) const {
|
| + return Matcher(delimiter_.data(), num_already_matched_bytes,
|
| + delimiter_.size());
|
| +}
|
| +
|
| +MultipartParser::Matcher MultipartParser::DelimiterSuffixMatcher() const {
|
| + return Matcher(kDelimiterSuffix, 0u, kDelimiterSuffixSize);
|
| +}
|
| +
|
| +void MultipartParser::ParseDataAndDelimiter(const char** bytes_pointer,
|
| + const char* bytes_end) {
|
| + DCHECK_EQ(0u, matcher_.NumMatchedBytes());
|
| +
|
| + // Search for a complete delimiter within the bytes.
|
| + const char* delimiter_begin = std::search(
|
| + *bytes_pointer, bytes_end, delimiter_.begin(), delimiter_.end());
|
| + if (delimiter_begin != bytes_end) {
|
| + // A complete delimiter was found. The bytes before that are octet
|
| + // bytes.
|
| + const char* const delimiter_end = delimiter_begin + delimiter_.size();
|
| + const bool matched = matcher_.Match(delimiter_begin, delimiter_end);
|
| + DCHECK(matched);
|
| + DCHECK(matcher_.IsMatchComplete());
|
| + *bytes_pointer = delimiter_end;
|
| + } else {
|
| + // Search for a partial delimiter in the end of the bytes.
|
| + const size_t size = static_cast<size_t>(bytes_end - *bytes_pointer);
|
| + for (delimiter_begin = bytes_end - std::min(delimiter_.size() - 1u, size);
|
| + delimiter_begin < bytes_end; ++delimiter_begin) {
|
| + if (matcher_.Match(delimiter_begin, bytes_end))
|
| + break;
|
| + matcher_.SetNumMatchedBytes(0u);
|
| + }
|
| + // If a partial delimiter was found in the end of bytes, the bytes
|
| + // before the partial delimiter are definitely octets bytes and
|
| + // the partial delimiter bytes are buffered for now.
|
| + // If a partial delimiter was not found in the end of bytes, all bytes
|
| + // are definitely octets bytes.
|
| + // In all cases, all bytes are parsed now.
|
| + *bytes_pointer = bytes_end;
|
| + }
|
| +
|
| + DCHECK(matcher_.IsMatchComplete() || *bytes_pointer == bytes_end);
|
| +}
|
| +
|
| +void MultipartParser::ParseDelimiter(const char** bytes_pointer,
|
| + const char* bytes_end) {
|
| + DCHECK(!matcher_.IsMatchComplete());
|
| + while (*bytes_pointer < bytes_end && matcher_.Match(*(*bytes_pointer))) {
|
| + ++(*bytes_pointer);
|
| + if (matcher_.IsMatchComplete())
|
| + break;
|
| + }
|
| +}
|
| +
|
| +bool MultipartParser::ParseHeaderFields(const char** bytes_pointer,
|
| + const char* bytes_end,
|
| + HTTPHeaderMap* header_fields) {
|
| + // Combine the current bytes with buffered header bytes if needed.
|
| + const char* header_bytes = *bytes_pointer;
|
| + size_t header_size = static_cast<size_t>(bytes_end - *bytes_pointer);
|
| + if (!buffered_header_bytes_.IsEmpty()) {
|
| + buffered_header_bytes_.Append(header_bytes, header_size);
|
| + header_bytes = buffered_header_bytes_.data();
|
| + header_size = buffered_header_bytes_.size();
|
| + }
|
| +
|
| + size_t end = 0u;
|
| + if (!ParseMultipartFormHeadersFromBody(header_bytes, header_size,
|
| + header_fields, &end)) {
|
| + // Store the current header bytes for the next call unless that has
|
| + // already been done.
|
| + if (buffered_header_bytes_.IsEmpty())
|
| + buffered_header_bytes_.Append(header_bytes, header_size);
|
| + *bytes_pointer = bytes_end;
|
| + return false;
|
| + }
|
| + buffered_header_bytes_.clear();
|
| + *bytes_pointer = bytes_end - (header_size - end);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void MultipartParser::ParseTransportPadding(const char** bytes_pointer,
|
| + const char* bytes_end) const {
|
| + while (*bytes_pointer < bytes_end &&
|
| + (*(*bytes_pointer) == '\t' || *(*bytes_pointer) == ' '))
|
| + ++(*bytes_pointer);
|
| +}
|
| +
|
| +DEFINE_TRACE(MultipartParser) {
|
| + visitor->Trace(client_);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|