910e62b5创建于 1月15日历史提交
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "pdf/loader/url_loader_wrapper_impl.h"

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "net/http/http_util.h"
#include "pdf/loader/result_codes.h"
#include "pdf/loader/url_loader.h"
#include "ui/gfx/range/range.h"

namespace chrome_pdf {

namespace {

// We should read with delay to prevent block UI thread, and reduce CPU usage.
constexpr base::TimeDelta kReadDelayMs = base::Milliseconds(2);

constexpr std::string_view kBytes = "bytes";

UrlRequest MakeRangeRequest(const std::string& url,
                            const std::string& referrer_url,
                            uint32_t position,
                            uint32_t size) {
  UrlRequest request;
  request.url = url;
  request.method = "GET";
  request.ignore_redirects = true;
  request.custom_referrer_url = referrer_url;

  // According to rfc2616, byte range specifies position of the first and last
  // bytes in the requested range inclusively. Therefore we should subtract 1
  // from the position + size, to get index of the last byte that needs to be
  // downloaded.
  request.headers =
      base::StringPrintf("Range: bytes=%d-%d", position, position + size - 1);

  return request;
}

std::optional<gfx::Range> GetByteRangeFromStr(
    std::string_view content_range_str) {
  if (!base::StartsWith(content_range_str, kBytes,
                        base::CompareCase::INSENSITIVE_ASCII)) {
    return std::nullopt;
  }

  std::string range(content_range_str.substr(kBytes.size()));
  std::string::size_type pos = range.find('-');
  std::string range_end;
  if (pos != std::string::npos) {
    range_end = range.substr(pos + 1);
  }
  base::TrimWhitespaceASCII(range, base::TRIM_LEADING, &range);
  base::TrimWhitespaceASCII(range_end, base::TRIM_LEADING, &range_end);
  return gfx::Range(atoi(range.c_str()), atoi(range_end.c_str()));
}

// If the headers have a byte-range response, and at least the start position
// was parsed, returns a range with the start and end positions.
// The end position will be set to 0 if it was not found or parsed from the
// response.
// Returns std::nullopt if not even a start position could be parsed.
std::optional<gfx::Range> GetByteRangeFromHeaders(std::string_view headers) {
  net::HttpUtil::HeadersIterator it(headers, "\n");
  while (it.GetNext()) {
    if (base::EqualsCaseInsensitiveASCII(it.name_piece(), "content-range")) {
      std::optional<gfx::Range> range = GetByteRangeFromStr(it.values());
      if (range.has_value()) {
        return range;
      }
    }
  }
  return std::nullopt;
}

bool IsDoubleEndLineAtEnd(std::string_view buffer) {
  return buffer.ends_with("\n\n") || buffer.ends_with("\r\n\r\n");
}

}  // namespace

URLLoaderWrapperImpl::URLLoaderWrapperImpl(
    std::unique_ptr<UrlLoader> url_loader)
    : url_loader_(std::move(url_loader)) {
  SetHeadersFromLoader();
}

URLLoaderWrapperImpl::~URLLoaderWrapperImpl() {
  Close();
}

int URLLoaderWrapperImpl::GetContentLength() const {
  return content_length_;
}

bool URLLoaderWrapperImpl::IsAcceptRangesBytes() const {
  return accept_ranges_bytes_;
}

bool URLLoaderWrapperImpl::IsContentEncoded() const {
  return content_encoded_;
}

std::string URLLoaderWrapperImpl::GetContentType() const {
  return content_type_;
}
std::string URLLoaderWrapperImpl::GetContentDisposition() const {
  return content_disposition_;
}

int URLLoaderWrapperImpl::GetStatusCode() const {
  return url_loader_->response().status_code;
}

bool URLLoaderWrapperImpl::IsMultipart() const {
  return is_multipart_;
}

bool URLLoaderWrapperImpl::GetByteRangeStart(int* start) const {
  DCHECK(start);
  *start = byte_range_.start();
  return byte_range_.IsValid();
}

void URLLoaderWrapperImpl::Close() {
  url_loader_->Close();
  read_starter_.Stop();
}

void URLLoaderWrapperImpl::OpenRange(const std::string& url,
                                     const std::string& referrer_url,
                                     uint32_t position,
                                     uint32_t size,
                                     base::OnceCallback<void(bool)> callback) {
  url_loader_->Open(
      MakeRangeRequest(url, referrer_url, position, size),
      base::BindOnce(&URLLoaderWrapperImpl::DidOpen, weak_factory_.GetWeakPtr(),
                     std::move(callback)));
}

void URLLoaderWrapperImpl::ReadResponseBody(
    base::span<uint8_t> buffer,
    base::OnceCallback<void(int)> callback) {
  buffer_ = buffer;
  read_starter_.Start(
      FROM_HERE, kReadDelayMs,
      base::BindOnce(&URLLoaderWrapperImpl::ReadResponseBodyImpl,
                     base::Unretained(this), std::move(callback)));
}

void URLLoaderWrapperImpl::ReadResponseBodyImpl(
    base::OnceCallback<void(int)> callback) {
  url_loader_->ReadResponseBody(
      buffer_, base::BindOnce(&URLLoaderWrapperImpl::DidRead,
                              weak_factory_.GetWeakPtr(), std::move(callback)));
}

void URLLoaderWrapperImpl::ParseHeaders(const std::string& response_headers) {
  content_length_ = -1;
  accept_ranges_bytes_ = false;
  content_encoded_ = false;
  content_type_.clear();
  content_disposition_.clear();
  multipart_boundary_.clear();
  byte_range_ = gfx::Range::InvalidRange();
  is_multipart_ = false;

  if (response_headers.empty())
    return;

  net::HttpUtil::HeadersIterator it(response_headers, "\n");
  while (it.GetNext()) {
    std::string_view name = it.name_piece();
    if (base::EqualsCaseInsensitiveASCII(name, "content-length")) {
      content_length_ = atoi(it.values().c_str());
    } else if (base::EqualsCaseInsensitiveASCII(name, "accept-ranges")) {
      accept_ranges_bytes_ =
          base::EqualsCaseInsensitiveASCII(it.values(), kBytes);
    } else if (base::EqualsCaseInsensitiveASCII(name, "content-encoding")) {
      content_encoded_ = true;
    } else if (base::EqualsCaseInsensitiveASCII(name, "content-type")) {
      content_type_ = it.values();
      size_t semi_colon_pos = content_type_.find(';');
      if (semi_colon_pos != std::string::npos) {
        content_type_ = content_type_.substr(0, semi_colon_pos);
      }
      base::TrimWhitespaceASCII(content_type_, base::TRIM_ALL, &content_type_);
      // multipart boundary.
      std::string type = base::ToLowerASCII(it.values_piece());
      if (base::StartsWith(type, "multipart/", base::CompareCase::SENSITIVE)) {
        static constexpr std::string_view kBoundary = "boundary=";
        size_t boundary = type.find(kBoundary);
        if (boundary != std::string::npos) {
          multipart_boundary_ = type.substr(boundary + kBoundary.size());
          is_multipart_ = !multipart_boundary_.empty();
        }
      }
    } else if (base::EqualsCaseInsensitiveASCII(name, "content-disposition")) {
      content_disposition_ = it.values();
    } else if (base::EqualsCaseInsensitiveASCII(name, "content-range")) {
      std::optional<gfx::Range> range = GetByteRangeFromStr(it.values());
      if (range.has_value()) {
        byte_range_ = range.value();
      }
    }
  }
}

void URLLoaderWrapperImpl::DidOpen(base::OnceCallback<void(bool)> callback,
                                   Result result) {
  SetHeadersFromLoader();
  std::move(callback).Run(result == Result::kSuccess);
}

void URLLoaderWrapperImpl::DidRead(base::OnceCallback<void(int)> callback,
                                   int32_t result) {
  if (multi_part_processed_) {
    // Reset this flag so we look inside the buffer in calls of DidRead for this
    // response only once.  Note that this code DOES NOT handle multi part
    // responses with more than one part (we don't issue them at the moment, so
    // they shouldn't arrive).
    is_multipart_ = false;
  }
  if (result <= 0 || !is_multipart_) {
    std::move(callback).Run(result);
    return;
  }
  if (result <= 2) {
    // TODO(art-snake): Accumulate data for parse headers.
    std::move(callback).Run(result);
    return;
  }

  base::span<uint8_t> start = buffer_.first(static_cast<size_t>(result));
  multi_part_processed_ = true;
  for (size_t i = 2; i < static_cast<size_t>(result); ++i) {
    auto buffer = base::as_string_view(buffer_.first(i));
    if (!IsDoubleEndLineAtEnd(buffer)) {
      continue;
    }

    std::optional<gfx::Range> range = GetByteRangeFromHeaders(buffer);
    if (range.has_value()) {
      byte_range_ = range.value();
      start = start.subspan(i);
    }
    break;
  }

  if (start.empty()) {
    // Continue receiving.
    return ReadResponseBodyImpl(std::move(callback));
  }

  base::span(buffer_).copy_prefix_from(start);
  std::move(callback).Run(base::checked_cast<int32_t>(start.size()));
}

void URLLoaderWrapperImpl::SetHeadersFromLoader() {
  ParseHeaders(url_loader_->response().headers);
}

}  // namespace chrome_pdf