blob: b4cc463f97ec4280691dde0e084521663ec17ebf [file] [log] [blame]
// 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 "third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h"
#include "third_party/blink/renderer/platform/network/http_parsers.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
#include <algorithm>
namespace blink {
MultipartImageResourceParser::MultipartImageResourceParser(
const ResourceResponse& response,
const Vector<char>& boundary,
Client* client)
: original_response_(response), boundary_(boundary), client_(client) {
// Some servers report a boundary prefixed with "--". See
// https://crbug.com/5786.
if (boundary_.size() < 2 || boundary_[0] != '-' || boundary_[1] != '-')
boundary_.push_front("--", 2);
}
void MultipartImageResourceParser::AppendData(const char* bytes,
wtf_size_t size) {
DCHECK(!IsCancelled());
// m_sawLastBoundary means that we've already received the final boundary
// token. The server should stop sending us data at this point, but if it
// does, we just throw it away.
if (saw_last_boundary_)
return;
data_.Append(bytes, size);
if (is_parsing_top_) {
// Eat leading \r\n
wtf_size_t pos = SkippableLength(data_, 0);
// +2 for "--"
if (data_.size() < boundary_.size() + 2 + pos) {
// We don't have enough data yet to make a boundary token. Just wait
// until the next chunk of data arrives.
return;
}
if (pos)
data_.EraseAt(0, pos);
// Some servers don't send a boundary token before the first chunk of
// data. We handle this case anyway (Gecko does too).
if (0 != memcmp(data_.data(), boundary_.data(), boundary_.size())) {
data_.push_front("\n", 1);
data_.PrependVector(boundary_);
}
is_parsing_top_ = false;
}
// Headers
if (is_parsing_headers_) {
if (!ParseHeaders()) {
// Get more data before trying again.
return;
}
// Successfully parsed headers.
is_parsing_headers_ = false;
if (IsCancelled())
return;
}
wtf_size_t boundary_position;
while ((boundary_position = FindBoundary(data_, &boundary_)) != kNotFound) {
// Strip out trailing \r\n characters in the buffer preceding the boundary
// on the same lines as does Firefox.
wtf_size_t data_size = boundary_position;
if (boundary_position > 0 && data_[boundary_position - 1] == '\n') {
data_size--;
if (boundary_position > 1 && data_[boundary_position - 2] == '\r') {
data_size--;
}
}
if (data_size) {
client_->MultipartDataReceived(data_.data(), data_size);
if (IsCancelled())
return;
}
wtf_size_t boundary_end_position = boundary_position + boundary_.size();
if (boundary_end_position < data_.size() &&
'-' == data_[boundary_end_position]) {
// This was the last boundary so we can stop processing.
saw_last_boundary_ = true;
data_.clear();
return;
}
// We can now throw out data up through the boundary
data_.EraseAt(0, boundary_end_position);
// Ok, back to parsing headers
if (!ParseHeaders()) {
is_parsing_headers_ = true;
break;
}
if (IsCancelled())
return;
}
// At this point, we should send over any data we have, but keep enough data
// buffered to handle a boundary that may have been truncated. "+2" for CRLF,
// as we may ignore the last CRLF.
if (!is_parsing_headers_ && data_.size() > boundary_.size() + 2) {
wtf_size_t send_length = data_.size() - boundary_.size() - 2;
client_->MultipartDataReceived(data_.data(), send_length);
data_.EraseAt(0, send_length);
}
}
void MultipartImageResourceParser::Finish() {
DCHECK(!IsCancelled());
if (saw_last_boundary_)
return;
// If we have any pending data and we're not in a header, go ahead and send
// it to the client.
if (!is_parsing_headers_ && !data_.IsEmpty())
client_->MultipartDataReceived(data_.data(), data_.size());
data_.clear();
saw_last_boundary_ = true;
}
wtf_size_t MultipartImageResourceParser::SkippableLength(
const Vector<char>& data,
wtf_size_t pos) {
if (data.size() >= pos + 2 && data[pos] == '\r' && data[pos + 1] == '\n')
return 2;
if (data.size() >= pos + 1 && data[pos] == '\n')
return 1;
return 0;
}
bool MultipartImageResourceParser::ParseHeaders() {
// Eat leading \r\n
wtf_size_t pos = SkippableLength(data_, 0);
// Create a ResourceResponse based on the original set of headers + the
// replacement headers. We only replace the same few headers that gecko does.
// See netwerk/streamconv/converters/nsMultiMixedConv.cpp.
ResourceResponse response(original_response_.CurrentRequestUrl());
response.SetWasFetchedViaServiceWorker(
original_response_.WasFetchedViaServiceWorker());
response.SetType(original_response_.GetType());
for (const auto& header : original_response_.HttpHeaderFields())
response.AddHttpHeaderField(header.key, header.value);
wtf_size_t end = 0;
if (!ParseMultipartHeadersFromBody(data_.data() + pos, data_.size() - pos,
&response, &end))
return false;
data_.EraseAt(0, end + pos);
// Send the response!
client_->OnePartInMultipartReceived(response);
return true;
}
// Boundaries are supposed to be preceeded with --, but it looks like gecko
// doesn't require the dashes to exist. See nsMultiMixedConv::FindToken.
wtf_size_t MultipartImageResourceParser::FindBoundary(const Vector<char>& data,
Vector<char>* boundary) {
auto* it = std::search(data.data(), data.data() + data.size(),
boundary->data(), boundary->data() + boundary->size());
if (it == data.data() + data.size())
return kNotFound;
wtf_size_t boundary_position = static_cast<wtf_size_t>(it - data.data());
// Back up over -- for backwards compat
// TODO(tc): Don't we only want to do this once? Gecko code doesn't seem to
// care.
if (boundary_position >= 2) {
if (data[boundary_position - 1] == '-' &&
data[boundary_position - 2] == '-') {
boundary_position -= 2;
Vector<char> v(2, '-');
v.AppendVector(*boundary);
*boundary = v;
}
}
return boundary_position;
}
void MultipartImageResourceParser::Trace(Visitor* visitor) const {
visitor->Trace(client_);
}
} // namespace blink