text: // 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/not_found.h"
  #include "third_party/blink/renderer/platform/wtf/text/wtf_string.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, 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
      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;
    }
  
    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.
      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;
      }
      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) {
      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;
  }
  
  size_t MultipartImageResourceParser::SkippableLength(const Vector<char>& data,
                                                       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
    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_.Url());
    response.SetWasFetchedViaServiceWorker(
        original_response_.WasFetchedViaServiceWorker());
    response.SetResponseTypeViaServiceWorker(
        original_response_.ResponseTypeViaServiceWorker());
    for (const auto& header : original_response_.HttpHeaderFields())
      response.AddHTTPHeaderField(header.key, header.value);
  
    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.
  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;
  
    size_t boundary_position = 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(blink::Visitor* visitor) {
    visitor->Trace(client_);
  }
  
  }  // namespace blink
  