CurlMultipartHandle.cpp [plain text]
#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 std::make_unique<CurlMultipartHandle>(client, *boundary);
}
std::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 std::nullopt;
}
std::optional<String> CurlMultipartHandle::extractBoundaryFromContentType(const String& contentType)
{
static const size_t length = strlen("boundary=");
auto boundaryStart = contentType.findIgnoringASCIICase("boundary=");
if (boundaryStart == notFound)
return std::nullopt;
boundaryStart += length;
size_t boundaryEnd = 0;
if (contentType[boundaryStart] == '"') {
++boundaryStart;
boundaryEnd = contentType.find('"', boundaryStart);
if (boundaryEnd == notFound)
return std::nullopt;
} else if (contentType[boundaryStart] == '\'') {
++boundaryStart;
boundaryEnd = contentType.find('\'', boundaryStart);
if (boundaryEnd == notFound)
return std::nullopt;
} else {
boundaryEnd = contentType.find(';', boundaryStart);
if (boundaryEnd == notFound)
boundaryEnd = contentType.length();
}
if (boundaryEnd <= boundaryStart)
return std::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;
m_buffer.append(buffer.data(), buffer.size());
while (processContent()) { }
}
void CurlMultipartHandle::didComplete()
{
while (processContent()) { }
if (m_state != State::End) {
m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), m_buffer.size()));
m_state = State::End;
}
m_buffer.clear();
}
bool CurlMultipartHandle::processContent()
{
switch (m_state) {
case State::CheckBoundary: {
if (m_buffer.size() < m_boundary.length()) {
return false;
}
size_t boundaryStart;
size_t lastPartialMatch;
if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) {
m_buffer.remove(0, lastPartialMatch);
return false;
}
m_buffer.remove(0, boundaryStart + m_boundary.length());
m_state = State::InBoundary;
}
case State::InBoundary: {
if (m_buffer.size() < 2)
return false;
const char* content = m_buffer.data();
size_t removeCount = 2;
if (content[0] != '\r' || content[1] != '\n') {
if (content[0] != '\n') {
m_state = State::End;
return false;
}
removeCount = 1;
}
m_buffer.remove(0, removeCount);
m_headers.clear();
m_state = State::InHeader;
}
case State::InHeader: {
if (!parseHeadersIfPossible()) {
return false;
}
m_client.didReceiveHeaderFromMultipart(m_headers);
m_state = State::InContent;
}
case State::InContent: {
if (m_buffer.isEmpty())
return false;
size_t boundaryStart;
size_t lastPartialMatch;
if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) {
m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), lastPartialMatch));
m_buffer.remove(0, lastPartialMatch);
return false;
}
m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), boundaryStart));
m_buffer.remove(0, boundaryStart + m_boundary.length());
m_state = State::EndBoundary;
}
case State::EndBoundary: {
if (m_buffer.size() < 2)
return false;
const char* content = m_buffer.data();
if (content[0] == '-' && content[1] == '-') {
m_state = State::End;
return false;
}
m_state = State::InBoundary;
break;
}
case State::End:
return false;
default:
ASSERT_NOT_REACHED();
return false;
}
return true; }
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();
if (!strnstr(content, "\r\n\r\n", contentLength)) {
if (!strnstr(content, "\n\n", contentLength)) {
return false;
}
}
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;
p += consumedLength;
totalConsumedLength += consumedLength;
if (name.isEmpty())
break;
m_headers.append(name.toString() + ": " + value + "\r\n");
}
m_buffer.remove(0, totalConsumedLength + 1);
return true;
}
}
#endif