| /* |
| * Copyright (C) 2013 University of Szeged |
| * Copyright (C) 2018 Sony Interactive Entertainment Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``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 UNIVERSITY OF SZEGED OR |
| * 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 "CurlMultipartHandle.h" |
| |
| #if USE(CURL) |
| |
| #include "CurlMultipartHandleClient.h" |
| #include "CurlResponse.h" |
| #include "HTTPHeaderNames.h" |
| #include "HTTPParsers.h" |
| #include "ResourceResponse.h" |
| #include "SharedBuffer.h" |
| #include <wtf/StringExtras.h> |
| |
| namespace WebCore { |
| |
| std::unique_ptr<CurlMultipartHandle> CurlMultipartHandle::createIfNeeded(CurlMultipartHandleClient& client, const CurlResponse& response) |
| { |
| auto boundary = extractBoundary(response); |
| if (!boundary) |
| return nullptr; |
| |
| return makeUnique<CurlMultipartHandle>(client, *boundary); |
| } |
| |
| Optional<String> CurlMultipartHandle::extractBoundary(const CurlResponse& response) |
| { |
| for (auto header : response.headers) { |
| auto splitPosistion = header.find(":"); |
| if (splitPosistion == notFound) |
| continue; |
| |
| auto key = header.left(splitPosistion).stripWhiteSpace(); |
| if (!equalIgnoringASCIICase(key, "Content-Type")) |
| continue; |
| |
| auto contentType = header.substring(splitPosistion + 1).stripWhiteSpace(); |
| auto mimeType = extractMIMETypeFromMediaType(contentType); |
| if (!equalIgnoringASCIICase(mimeType, "multipart/x-mixed-replace")) |
| continue; |
| |
| auto boundary = extractBoundaryFromContentType(contentType); |
| if (!boundary) |
| continue; |
| |
| return String("--" + *boundary); |
| } |
| |
| return WTF::nullopt; |
| } |
| |
| Optional<String> CurlMultipartHandle::extractBoundaryFromContentType(const String& contentType) |
| { |
| static const size_t length = strlen("boundary="); |
| |
| auto boundaryStart = contentType.findIgnoringASCIICase("boundary="); |
| if (boundaryStart == notFound) |
| return WTF::nullopt; |
| |
| boundaryStart += length; |
| size_t boundaryEnd = 0; |
| |
| if (contentType[boundaryStart] == '"') { |
| // Boundary value starts with a " quote. Search for the closing one. |
| ++boundaryStart; |
| boundaryEnd = contentType.find('"', boundaryStart); |
| if (boundaryEnd == notFound) |
| return WTF::nullopt; |
| } else if (contentType[boundaryStart] == '\'') { |
| // Boundary value starts with a ' quote. Search for the closing one. |
| ++boundaryStart; |
| boundaryEnd = contentType.find('\'', boundaryStart); |
| if (boundaryEnd == notFound) |
| return WTF::nullopt; |
| } else { |
| // Check for the end of the boundary. That can be a semicolon or a newline. |
| boundaryEnd = contentType.find(';', boundaryStart); |
| if (boundaryEnd == notFound) |
| boundaryEnd = contentType.length(); |
| } |
| |
| // The boundary end should not be before the start |
| if (boundaryEnd <= boundaryStart) |
| return WTF::nullopt; |
| |
| return contentType.substring(boundaryStart, boundaryEnd - boundaryStart); |
| } |
| |
| CurlMultipartHandle::CurlMultipartHandle(CurlMultipartHandleClient& client, const String& boundary) |
| : m_client(client) |
| , m_boundary(boundary) |
| { |
| |
| } |
| |
| void CurlMultipartHandle::didReceiveData(const SharedBuffer& buffer) |
| { |
| if (m_state == State::End) |
| return; // The handler is closed down so ignore everything. |
| |
| m_buffer.append(buffer.data(), buffer.size()); |
| |
| while (processContent()) { } |
| } |
| |
| void CurlMultipartHandle::didComplete() |
| { |
| // Process the leftover data. |
| while (processContent()) { } |
| |
| if (m_state != State::End) { |
| // It seems we are still not at the end of the processing. |
| // Push out the remaining data. |
| m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), m_buffer.size())); |
| m_state = State::End; |
| } |
| |
| m_buffer.clear(); |
| } |
| |
| bool CurlMultipartHandle::processContent() |
| { |
| /* |
| The allowed transitions between the states: |
| Check Boundary |
| | |
| /-- In Boundary <----\ |
| | | | |
| | In Header | |
| | | | |
| | In Content | |
| | | | |
| | End Boundary ----/ |
| | | |
| \-----> End |
| |
| */ |
| switch (m_state) { |
| case State::CheckBoundary: { |
| if (m_buffer.size() < m_boundary.length()) { |
| // We don't have enough data, so just skip. |
| return false; |
| } |
| |
| // Check for the boundary string. |
| size_t boundaryStart; |
| size_t lastPartialMatch; |
| |
| if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) { |
| // Did not find the boundary start in this chunk. |
| // Skip ahead to the last valid looking boundary character and start again. |
| m_buffer.remove(0, lastPartialMatch); |
| return false; |
| } |
| |
| // Found the boundary start. |
| // Consume everything before that and also the boundary |
| m_buffer.remove(0, boundaryStart + m_boundary.length()); |
| m_state = State::InBoundary; |
| } |
| // Fallthrough. |
| case State::InBoundary: { |
| // Now the first two characters should be: \r\n |
| if (m_buffer.size() < 2) |
| return false; |
| |
| const char* content = m_buffer.data(); |
| // By default we'll remove 2 characters at the end. |
| // The \r and \n as stated in the multipart RFC. |
| size_t removeCount = 2; |
| |
| if (content[0] != '\r' || content[1] != '\n') { |
| // There should be a \r and a \n but it seems that's not the case. |
| // So we'll check for a simple \n. Not really RFC compatible but servers do tricky things. |
| if (content[0] != '\n') { |
| // Also no \n so just go to the end. |
| m_state = State::End; |
| return false; |
| } |
| |
| // Found a simple \n so remove just that. |
| removeCount = 1; |
| } |
| |
| // Consume the characters. |
| m_buffer.remove(0, removeCount); |
| m_headers.clear(); |
| m_state = State::InHeader; |
| } |
| // Fallthrough. |
| case State::InHeader: { |
| // Process the headers. |
| if (!parseHeadersIfPossible()) { |
| // Parsing of headers failed, try again later. |
| return false; |
| } |
| |
| m_client.didReceiveHeaderFromMultipart(m_headers); |
| m_state = State::InContent; |
| } |
| // Fallthrough. |
| case State::InContent: { |
| if (m_buffer.isEmpty()) |
| return false; |
| |
| size_t boundaryStart; |
| size_t lastPartialMatch; |
| |
| if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) { |
| // Did not find the boundary start, all data up to the lastPartialMatch is ok. |
| m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), lastPartialMatch)); |
| m_buffer.remove(0, lastPartialMatch); |
| return false; |
| } |
| |
| // There was a boundary start (or end we'll check that later), push out part of the data. |
| m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), boundaryStart)); |
| m_buffer.remove(0, boundaryStart + m_boundary.length()); |
| m_state = State::EndBoundary; |
| } |
| // Fallthrough. |
| case State::EndBoundary: { |
| if (m_buffer.size() < 2) |
| return false; // Not enough data to check. Return later when there is more data. |
| |
| // We'll decide if this is a closing boundary or an opening one. |
| const char* content = m_buffer.data(); |
| |
| if (content[0] == '-' && content[1] == '-') { |
| // This is a closing boundary. Close down the handler. |
| m_state = State::End; |
| return false; |
| } |
| |
| // This was a simple content separator not a closing one. |
| // Go to before the content processing. |
| m_state = State::InBoundary; |
| break; |
| } |
| case State::End: |
| // We are done. Nothing to do anymore. |
| return false; |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| return true; // There are still things to process, so go for it. |
| } |
| |
| bool CurlMultipartHandle::checkForBoundary(size_t& boundaryStartPosition, size_t& lastPartialMatchPosition) |
| { |
| auto contentLength = m_buffer.size(); |
| |
| boundaryStartPosition = notFound; |
| lastPartialMatchPosition = contentLength; |
| |
| if (contentLength < m_boundary.length()) |
| return false; |
| |
| const auto content = m_buffer.data(); |
| |
| for (size_t i = 0; i < contentLength - m_boundary.length(); ++i) { |
| auto length = matchedLength(content + i); |
| if (length == m_boundary.length()) { |
| boundaryStartPosition = i; |
| return true; |
| } |
| |
| if (length) |
| lastPartialMatchPosition = i; |
| } |
| |
| return false; |
| } |
| |
| size_t CurlMultipartHandle::matchedLength(const char* data) |
| { |
| auto length = m_boundary.length(); |
| for (size_t i = 0; i < length; ++i) { |
| if (data[i] != m_boundary[i]) |
| return i; |
| } |
| |
| return length; |
| } |
| |
| bool CurlMultipartHandle::parseHeadersIfPossible() |
| { |
| auto contentLength = m_buffer.size(); |
| |
| if (!contentLength) |
| return false; |
| |
| const auto content = m_buffer.data(); |
| |
| // Check if we have the header closing strings. |
| if (!strnstr(content, "\r\n\r\n", contentLength)) { |
| // Some servers closes the headers with only \n-s. |
| if (!strnstr(content, "\n\n", contentLength)) { |
| // Don't have the header closing string. Wait for more data. |
| return false; |
| } |
| } |
| |
| // Parse the HTTP headers. |
| String value; |
| StringView name; |
| auto p = content; |
| const auto end = content + contentLength; |
| size_t totalConsumedLength = 0; |
| for (; p < end; ++p) { |
| String failureReason; |
| size_t consumedLength = parseHTTPHeader(p, end - p, failureReason, name, value, false); |
| if (!consumedLength) |
| break; // No more header to parse. |
| |
| p += consumedLength; |
| totalConsumedLength += consumedLength; |
| |
| // The name should not be empty, but the value could be empty. |
| if (name.isEmpty()) |
| break; |
| |
| m_headers.append(name.toString() + ": " + value + "\r\n"); |
| } |
| |
| m_buffer.remove(0, totalConsumedLength + 1); |
| return true; |
| } |
| |
| } // namespace WebCore |
| |
| #endif |