blob: ccdbb28239f22c3636cac14b0785d6f55ab2f5bc [file] [log] [blame]
/*
* 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