#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 {
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;
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()));
}
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");
}
}
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_);
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_) {
is_multipart_ = false;
}
if (result <= 0 || !is_multipart_) {
std::move(callback).Run(result);
return;
}
if (result <= 2) {
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()) {
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);
}
}