blob: f8d3163178c7bd880d51fab0d2e038e001fb2ee6 [file] [log] [blame]
Artem Stryginaf1b43422017-09-12 18:48:241// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "pdf/url_loader_wrapper_impl.h"
6
Lei Zhang64aa552c2017-10-17 18:35:537#include <memory>
8
Artem Stryginaf1b43422017-09-12 18:48:249#include "base/logging.h"
Artem Stryginaf1b43422017-09-12 18:48:2410#include "base/strings/string_util.h"
11#include "base/strings/stringprintf.h"
12#include "net/http/http_util.h"
Artem Stryginaf1b43422017-09-12 18:48:2413#include "ppapi/c/pp_errors.h"
14#include "ppapi/cpp/logging.h"
15#include "ppapi/cpp/url_request_info.h"
16#include "ppapi/cpp/url_response_info.h"
17
18namespace chrome_pdf {
19
20namespace {
Lei Zhangf74659e2017-12-05 22:48:5321
Artem Stryginaf1b43422017-09-12 18:48:2422// We should read with delay to prevent block UI thread, and reduce CPU usage.
Lei Zhangf74659e2017-12-05 22:48:5323constexpr base::TimeDelta kReadDelayMs = base::TimeDelta::FromMilliseconds(2);
Artem Stryginaf1b43422017-09-12 18:48:2424
25pp::URLRequestInfo MakeRangeRequest(pp::Instance* plugin_instance,
26 const std::string& url,
27 const std::string& referrer_url,
28 uint32_t position,
29 uint32_t size) {
30 pp::URLRequestInfo request(plugin_instance);
31 request.SetURL(url);
32 request.SetMethod("GET");
33 request.SetFollowRedirects(false);
34 request.SetCustomReferrerURL(referrer_url);
35
36 // According to rfc2616, byte range specifies position of the first and last
37 // bytes in the requested range inclusively. Therefore we should subtract 1
38 // from the position + size, to get index of the last byte that needs to be
39 // downloaded.
40 std::string str_header =
41 base::StringPrintf("Range: bytes=%d-%d", position, position + size - 1);
42 pp::Var header(str_header.c_str());
43 request.SetHeaders(header);
44
45 return request;
46}
47
48bool GetByteRangeFromStr(const std::string& content_range_str,
49 int* start,
50 int* end) {
51 std::string range = content_range_str;
52 if (!base::StartsWith(range, "bytes", base::CompareCase::INSENSITIVE_ASCII))
53 return false;
54
55 range = range.substr(strlen("bytes"));
56 std::string::size_type pos = range.find('-');
57 std::string range_end;
58 if (pos != std::string::npos)
59 range_end = range.substr(pos + 1);
60 base::TrimWhitespaceASCII(range, base::TRIM_LEADING, &range);
61 base::TrimWhitespaceASCII(range_end, base::TRIM_LEADING, &range_end);
62 *start = atoi(range.c_str());
63 *end = atoi(range_end.c_str());
64 return true;
65}
66
67// If the headers have a byte-range response, writes the start and end
68// positions and returns true if at least the start position was parsed.
69// The end position will be set to 0 if it was not found or parsed from the
70// response.
71// Returns false if not even a start position could be parsed.
72bool GetByteRangeFromHeaders(const std::string& headers, int* start, int* end) {
73 net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n");
74 while (it.GetNext()) {
75 if (base::LowerCaseEqualsASCII(it.name(), "content-range")) {
76 if (GetByteRangeFromStr(it.values().c_str(), start, end))
77 return true;
78 }
79 }
80 return false;
81}
82
83bool IsDoubleEndLineAtEnd(const char* buffer, int size) {
84 if (size < 2)
85 return false;
86
87 if (buffer[size - 1] == '\n' && buffer[size - 2] == '\n')
88 return true;
89
90 if (size < 4)
91 return false;
92
93 return buffer[size - 1] == '\n' && buffer[size - 2] == '\r' &&
94 buffer[size - 3] == '\n' && buffer[size - 4] == '\r';
95}
96
97} // namespace
98
Artem Stryginaf1b43422017-09-12 18:48:2499URLLoaderWrapperImpl::URLLoaderWrapperImpl(pp::Instance* plugin_instance,
100 const pp::URLLoader& url_loader)
101 : plugin_instance_(plugin_instance),
102 url_loader_(url_loader),
103 callback_factory_(this) {
104 SetHeadersFromLoader();
105}
106
107URLLoaderWrapperImpl::~URLLoaderWrapperImpl() {
108 Close();
109 // We should call callbacks to prevent memory leaks.
110 // The callbacks don't do anything, because the objects that created the
111 // callbacks have been destroyed.
112 if (!did_open_callback_.IsOptional())
113 did_open_callback_.RunAndClear(-1);
114 if (!did_read_callback_.IsOptional())
115 did_read_callback_.RunAndClear(-1);
116}
117
118int URLLoaderWrapperImpl::GetContentLength() const {
119 return content_length_;
120}
121
122bool URLLoaderWrapperImpl::IsAcceptRangesBytes() const {
123 return accept_ranges_bytes_;
124}
125
126bool URLLoaderWrapperImpl::IsContentEncoded() const {
127 return content_encoded_;
128}
129
130std::string URLLoaderWrapperImpl::GetContentType() const {
131 return content_type_;
132}
133std::string URLLoaderWrapperImpl::GetContentDisposition() const {
134 return content_disposition_;
135}
136
137int URLLoaderWrapperImpl::GetStatusCode() const {
138 return url_loader_.GetResponseInfo().GetStatusCode();
139}
140
141bool URLLoaderWrapperImpl::IsMultipart() const {
142 return is_multipart_;
143}
144
Lei Zhang02a3a0f2017-12-11 20:25:48145bool URLLoaderWrapperImpl::GetByteRangeStart(int* start) const {
Artem Stryginaf1b43422017-09-12 18:48:24146 DCHECK(start);
Artem Stryginaf1b43422017-09-12 18:48:24147 *start = byte_range_.start();
Artem Stryginaf1b43422017-09-12 18:48:24148 return byte_range_.IsValid();
149}
150
151bool URLLoaderWrapperImpl::GetDownloadProgress(
152 int64_t* bytes_received,
153 int64_t* total_bytes_to_be_received) const {
154 return url_loader_.GetDownloadProgress(bytes_received,
155 total_bytes_to_be_received);
156}
157
158void URLLoaderWrapperImpl::Close() {
159 url_loader_.Close();
Artem Strygin54e088f2018-07-03 15:52:48160 read_starter_.Stop();
Artem Stryginaf1b43422017-09-12 18:48:24161}
162
163void URLLoaderWrapperImpl::OpenRange(const std::string& url,
164 const std::string& referrer_url,
165 uint32_t position,
166 uint32_t size,
167 const pp::CompletionCallback& cc) {
168 did_open_callback_ = cc;
169 pp::CompletionCallback callback =
170 callback_factory_.NewCallback(&URLLoaderWrapperImpl::DidOpen);
171 int rv = url_loader_.Open(
172 MakeRangeRequest(plugin_instance_, url, referrer_url, position, size),
173 callback);
174 if (rv != PP_OK_COMPLETIONPENDING)
175 callback.Run(rv);
176}
177
178void URLLoaderWrapperImpl::ReadResponseBody(char* buffer,
179 int buffer_size,
180 const pp::CompletionCallback& cc) {
181 did_read_callback_ = cc;
182 buffer_ = buffer;
183 buffer_size_ = buffer_size;
Artem Strygin54e088f2018-07-03 15:52:48184 read_starter_.Start(
185 FROM_HERE, kReadDelayMs,
186 base::BindRepeating(&URLLoaderWrapperImpl::ReadResponseBodyImpl,
187 base::Unretained(this)));
Artem Stryginaf1b43422017-09-12 18:48:24188}
189
190void URLLoaderWrapperImpl::ReadResponseBodyImpl() {
Artem Stryginaf1b43422017-09-12 18:48:24191 pp::CompletionCallback callback =
192 callback_factory_.NewCallback(&URLLoaderWrapperImpl::DidRead);
193 int rv = url_loader_.ReadResponseBody(buffer_, buffer_size_, callback);
194 if (rv != PP_OK_COMPLETIONPENDING) {
195 callback.Run(rv);
196 }
197}
198
199void URLLoaderWrapperImpl::SetResponseHeaders(
200 const std::string& response_headers) {
201 response_headers_ = response_headers;
202 ParseHeaders();
203}
204
205void URLLoaderWrapperImpl::ParseHeaders() {
206 content_length_ = -1;
207 accept_ranges_bytes_ = false;
208 content_encoded_ = false;
209 content_type_.clear();
210 content_disposition_.clear();
211 multipart_boundary_.clear();
212 byte_range_ = gfx::Range::InvalidRange();
213 is_multipart_ = false;
214
215 if (response_headers_.empty())
216 return;
217
218 net::HttpUtil::HeadersIterator it(response_headers_.begin(),
219 response_headers_.end(), "\n");
220 while (it.GetNext()) {
221 if (base::LowerCaseEqualsASCII(it.name(), "content-length")) {
222 content_length_ = atoi(it.values().c_str());
223 } else if (base::LowerCaseEqualsASCII(it.name(), "accept-ranges")) {
224 accept_ranges_bytes_ = base::LowerCaseEqualsASCII(it.values(), "bytes");
225 } else if (base::LowerCaseEqualsASCII(it.name(), "content-encoding")) {
226 content_encoded_ = true;
227 } else if (base::LowerCaseEqualsASCII(it.name(), "content-type")) {
228 content_type_ = it.values();
229 size_t semi_colon_pos = content_type_.find(';');
230 if (semi_colon_pos != std::string::npos) {
231 content_type_ = content_type_.substr(0, semi_colon_pos);
232 }
233 base::TrimWhitespaceASCII(content_type_, base::TRIM_ALL, &content_type_);
234 // multipart boundary.
235 std::string type = base::ToLowerASCII(it.values());
236 if (base::StartsWith(type, "multipart/", base::CompareCase::SENSITIVE)) {
237 const char* boundary = strstr(type.c_str(), "boundary=");
238 DCHECK(boundary);
239 if (boundary) {
240 multipart_boundary_ = std::string(boundary + 9);
241 is_multipart_ = !multipart_boundary_.empty();
242 }
243 }
244 } else if (base::LowerCaseEqualsASCII(it.name(), "content-disposition")) {
245 content_disposition_ = it.values();
246 } else if (base::LowerCaseEqualsASCII(it.name(), "content-range")) {
247 int start = 0;
248 int end = 0;
249 if (GetByteRangeFromStr(it.values().c_str(), &start, &end)) {
250 byte_range_ = gfx::Range(start, end);
251 }
252 }
253 }
254}
255
256void URLLoaderWrapperImpl::DidOpen(int32_t result) {
257 SetHeadersFromLoader();
258 did_open_callback_.RunAndClear(result);
259}
260
261void URLLoaderWrapperImpl::DidRead(int32_t result) {
262 if (multi_part_processed_) {
263 // Reset this flag so we look inside the buffer in calls of DidRead for this
264 // response only once. Note that this code DOES NOT handle multi part
265 // responses with more than one part (we don't issue them at the moment, so
266 // they shouldn't arrive).
267 is_multipart_ = false;
268 }
269 if (result <= 0 || !is_multipart_) {
270 did_read_callback_.RunAndClear(result);
271 return;
272 }
273 if (result <= 2) {
274 // TODO(art-snake): Accumulate data for parse headers.
275 did_read_callback_.RunAndClear(result);
276 return;
277 }
278
279 char* start = buffer_;
280 size_t length = result;
281 multi_part_processed_ = true;
282 for (int i = 2; i < result; ++i) {
283 if (IsDoubleEndLineAtEnd(buffer_, i)) {
284 int start_pos = 0;
285 int end_pos = 0;
286 if (GetByteRangeFromHeaders(std::string(buffer_, i), &start_pos,
287 &end_pos)) {
288 byte_range_ = gfx::Range(start_pos, end_pos);
289 start += i;
290 length -= i;
291 }
292 break;
293 }
294 }
295 result = length;
296 if (result == 0) {
297 // Continue receiving.
298 return ReadResponseBodyImpl();
299 }
Lei Zhang64aa552c2017-10-17 18:35:53300 DCHECK_GT(result, 0);
Artem Stryginaf1b43422017-09-12 18:48:24301 memmove(buffer_, start, result);
302
303 did_read_callback_.RunAndClear(result);
304}
305
306void URLLoaderWrapperImpl::SetHeadersFromLoader() {
307 pp::URLResponseInfo response = url_loader_.GetResponseInfo();
308 pp::Var headers_var = response.GetHeaders();
309
310 SetResponseHeaders(headers_var.is_string() ? headers_var.AsString() : "");
311}
312
313} // namespace chrome_pdf