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..9d054987999fc063ce161a417089174590aa59a9 |
--- /dev/null |
+++ b/third_party/WebKit/Source/modules/fetch/MultipartParser.cpp |
@@ -0,0 +1,263 @@ |
+// 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 "public/platform/Platform.h" |
+ |
+#include <algorithm> |
+#include <utility> |
+ |
+namespace blink { |
+ |
+namespace { |
+ const char kCloseDelimiterSuffix[] = "--\r\n"; |
yhirano
2016/09/07 06:00:16
constexpr
ditto below.
e_hakkinen
2016/09/08 00:06:29
Done.
|
+ const char kDelimiterSuffix[] = "\r\n"; |
+ const size_t kDelimiterOffsetForEmptyPreamble = 2u; // For no "\r\n" prefix. |
+ const size_t kDelimiterOffsetForEmptyBody = kDelimiterOffsetForEmptyPreamble; |
+ const size_t kDelimiterOffsetForErroneousBoundaryPrefix = 4u; // For no "\r\n--" prefix. |
+} |
+ |
+MultipartParser::MultipartParser(Vector<char> boundary, Client* client) |
+ : m_delimiter(std::move(boundary)) |
+ , m_client(client) |
+ , m_seenDelimiterLength(0u) |
+ , m_seenDelimiterOffset(kDelimiterOffsetForEmptyPreamble) |
+{ |
+ // The delimiter consists of "\r\n" and a dash delimiter which consists of |
+ // "--" and a boundary. |
+ m_delimiter.prepend("\r\n--", 4u); |
+} |
+ |
+bool MultipartParser::appendData(const char* bytes, size_t size) |
+{ |
+ DCHECK_NE(Finished, m_state); |
+ DCHECK_NE(Cancelled, m_state); |
+ |
+ while (size > 0u) { |
+ size_t index = 0u; |
+ |
+ switch (m_state) { |
+ case ParsingPreamble: |
+ // Parse either a preamble and a delimiter or a dash delimiter. |
+ if (parseDelimiter(bytes, size, &index)) { |
+ if (m_seenDelimiterOffset == kDelimiterOffsetForErroneousBoundaryPrefix) { |
+ // Remove the erroneous boundary prefix (see parseDelimiter |
+ // for details). |
+ m_delimiter.remove(kDelimiterOffsetForErroneousBoundaryPrefix, 2u); |
+ m_seenDelimiterLength -= 2u; |
+ } |
+ m_state = ParsingDelimiterSuffix; |
+ } |
+ break; |
+ |
+ case ParsingDelimiterSuffix: |
+ // Parse transport padding and "\r\n" after a delimiter. |
+ if (parseDelimiterSuffix(bytes, size, &index, kDelimiterSuffix)) |
+ m_state = ParsingPartHeaderFields; |
+ break; |
+ |
+ case ParsingPartHeaderFields: { |
+ // Parse part header fields (which ends with "\r\n") and an empty |
+ // line (which also ends with "\r\n"). |
+ WebURLResponse response; |
+ |
+ // Combine the current bytes with previously seen header bytes if |
+ // needed. |
+ const char* headerBytes = bytes + index; |
+ size_t headerSize = size - index; |
+ if (!m_seenHeaderBytes.isEmpty()) { |
+ m_seenHeaderBytes.append(headerBytes, headerSize); |
+ headerBytes = m_seenHeaderBytes.data(); |
+ headerSize = m_seenHeaderBytes.size(); |
+ } |
+ |
+ size_t end = 0; |
+ if (!Platform::current()->parseMultipartHeadersFromBody(headerBytes, headerSize, &response, &end)) { |
+ // Store the current bytes for the next call. |
+ if (headerBytes != m_seenHeaderBytes.data()) |
+ m_seenHeaderBytes.append(headerBytes, headerSize); |
+ return true; |
+ } |
+ |
+ m_seenDelimiterLength = 0u; |
+ m_seenDelimiterOffset = kDelimiterOffsetForEmptyBody; |
+ m_seenHeaderBytes.shrink(0); |
+ m_state = ParsingPartOctets; |
+ index = size - (headerSize - end); |
+ m_client->partHeaderFieldsInMultipartReceived( |
+ response.toResourceResponse()); |
+ break; |
+ } |
+ |
+ case ParsingPartOctets: { |
+ // Parse either a non-empty part octets and a delimiter or an empty |
+ // part octets and a dash delimiter. |
+ size_t initialSeenDelimiterLength = m_seenDelimiterLength; |
+ size_t initialSeenDelimiterOffset = m_seenDelimiterOffset; |
+ if (parseDelimiter(bytes, size, &index)) |
+ m_state = ParsingDelimiterOrCloseDelimiterSuffix; |
+ if (index >= m_seenDelimiterLength && initialSeenDelimiterLength > 0u) |
+ m_client->partDataInMultipartReceived(m_delimiter.data() + initialSeenDelimiterOffset, initialSeenDelimiterLength); |
+ if (index > m_seenDelimiterLength) |
+ m_client->partDataInMultipartReceived(bytes, index - m_seenDelimiterLength); |
+ if (m_state == ParsingDelimiterOrCloseDelimiterSuffix) |
+ m_client->partDataInMultipartFullyReceived(); |
+ break; |
+ } |
+ |
+ case ParsingDelimiterOrCloseDelimiterSuffix: |
+ m_state = bytes[index] != '-' ? ParsingDelimiterSuffix : ParsingCloseDelimiterSuffix; |
+ break; |
+ |
+ case ParsingCloseDelimiterSuffix: |
+ // Parse "--", transport padding and "\r\n" after a delimiter |
+ // (a delimiter and "--" constitute a close delimiter). |
+ if (parseDelimiterSuffix(bytes, size, &index, kCloseDelimiterSuffix)) |
+ m_state = ParsingEpilogue; |
+ break; |
+ |
+ case ParsingEpilogue: |
+ // Data in an epilogue should be ignored. |
+ return true; |
+ |
+ case Cancelled: |
+ case Finished: |
+ // The client changed the state. |
+ return true; |
+ |
+ case Failed: |
+ // Keep failing. |
+ return false; |
+ } |
+ |
+ bytes += index; |
+ size -= index; |
+ } |
+ |
+ return true; |
+} |
+ |
+void MultipartParser::cancel() |
+{ |
+ m_state = Cancelled; |
+} |
+ |
+bool MultipartParser::finish() |
+{ |
+ DCHECK_NE(Cancelled, m_state); |
+ |
+ State initialState = m_state; |
+ |
+ if (m_state == ParsingPartOctets && m_seenDelimiterLength > 0u && m_seenDelimiterOffset + m_seenDelimiterLength < m_delimiter.size()) { |
+ // The end of append bytes looked like a delimiter but was not a full |
+ // one, after all. Treat the those bytes as part of part octets. |
+ m_client->partDataInMultipartReceived( |
+ m_delimiter.data() + m_seenDelimiterOffset, m_seenDelimiterLength); |
+ } |
+ m_state = Finished; |
+ |
+ switch (initialState) { |
+ case ParsingCloseDelimiterSuffix: |
+ // Require a full close delimiter consisting of a delimiter and "--" |
+ // but ignore missing or partial "\r\n" after that. |
+ return seenDelimiterSuffixLength() >= 2u; |
+ case ParsingEpilogue: |
+ case Finished: |
+ return true; |
+ default: |
+ return false; |
+ } |
+} |
+ |
+size_t MultipartParser::countNonDelimiterBytes(const char* bytes, size_t size) const |
+{ |
+ const char* p = static_cast<const char*>(memchr(bytes, '\r', size)); |
+ if (p) |
+ return static_cast<size_t>(p - bytes); |
+ return size; |
+} |
+ |
+size_t MultipartParser::countPossibleDelimiterBytes(const char* bytes, size_t size) const |
+{ |
+ size_t index = 0u; |
+ while (index < size && m_seenDelimiterOffset + m_seenDelimiterLength + index < m_delimiter.size() && bytes[index] == m_delimiter[m_seenDelimiterOffset + m_seenDelimiterLength + index]) |
+ ++index; |
+ return index; |
+} |
+ |
+size_t MultipartParser::countTransportPaddingBytes(const char* bytes, size_t size) const |
+{ |
+ size_t index = 0u; |
+ while (index < size && (bytes[index] == '\t' || bytes[index] == ' ')) |
+ ++index; |
+ return index; |
+} |
+ |
+bool MultipartParser::parseDelimiter(const char* bytes, size_t size, size_t* index) |
+{ |
+ for (;;) { |
+ // Try to continue reading a delimiter. |
+ size_t possibleDelimiterBytes = countPossibleDelimiterBytes(bytes + *index, size - *index); |
+ if (possibleDelimiterBytes > 0u) { |
+ m_seenDelimiterLength += possibleDelimiterBytes; |
+ *index += possibleDelimiterBytes; |
+ } |
+ size_t seenDelimiterEnd = m_seenDelimiterOffset + m_seenDelimiterLength; |
+ if (seenDelimiterEnd == m_delimiter.size()) |
+ return true; |
+ if (*index >= size) |
+ break; |
+ if (m_state == ParsingPreamble |
+ && m_seenDelimiterOffset < kDelimiterOffsetForErroneousBoundaryPrefix |
+ && kDelimiterOffsetForErroneousBoundaryPrefix <= seenDelimiterEnd |
+ && seenDelimiterEnd <= m_delimiter.size() - 2u |
+ && std::equal( |
+ m_delimiter.data() + kDelimiterOffsetForErroneousBoundaryPrefix - 2u, |
+ m_delimiter.data() + seenDelimiterEnd, |
+ m_delimiter.data() + kDelimiterOffsetForErroneousBoundaryPrefix)) { |
+ // Some servers erroneously prefix the boundary with "--" (see |
+ // https://crbug.com/5786). Repeat the delimiter search with |
yhirano
2016/09/07 06:00:16
This is a problem for multipart/x-mixed-replace. A
e_hakkinen
2016/09/08 00:06:29
OK, let's do that initially.
Although this is a n
|
+ // an offset which ignores that erroneous prefix. Gecko does |
+ // the same. |
+ m_seenDelimiterLength -= kDelimiterOffsetForErroneousBoundaryPrefix - 2u - m_seenDelimiterOffset; |
+ m_seenDelimiterOffset = kDelimiterOffsetForErroneousBoundaryPrefix; |
+ continue; |
+ } |
+ |
+ // Jump to the next possible delimiter or to the end of bytes. |
+ m_seenDelimiterLength = 0u; |
+ m_seenDelimiterOffset = 0u; |
+ *index += countNonDelimiterBytes(bytes + *index, size - *index); |
+ } |
+ return false; |
+} |
+ |
+bool MultipartParser::parseDelimiterSuffix(const char* bytes, size_t size, size_t* index, const char* suffix) |
+{ |
+ while (char expected = suffix[seenDelimiterSuffixLength()]) { |
+ if (expected == '\r') |
+ *index += countTransportPaddingBytes(bytes + *index, size - *index); |
+ if (*index >= size) |
+ return false; |
+ if (bytes[(*index)++] != expected) { |
+ m_state = Failed; |
+ return false; |
+ } |
+ ++m_seenDelimiterLength; |
+ } |
+ return true; |
+} |
+ |
+size_t MultipartParser::seenDelimiterSuffixLength() const |
+{ |
+ return m_seenDelimiterOffset + m_seenDelimiterLength - m_delimiter.size(); |
+} |
+ |
+DEFINE_TRACE(MultipartParser) |
+{ |
+ visitor->trace(m_client); |
+} |
+ |
+} // namespace blink |