1 // Copyright 2013 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.
5 #include "nacl_io/httpfs/http_fs_node.h"
12 #include <ppapi/c/pp_errors.h>
14 #include "nacl_io/httpfs/http_fs.h"
15 #include "nacl_io/kernel_handle.h"
16 #include "nacl_io/osinttypes.h"
19 #define snprintf _snprintf
26 // If we're attempting to read a partial request, but the server returns a full
27 // request, we need to read all of the data up to the start of our partial
28 // request into a dummy buffer. This is the maximum size of that buffer.
29 const int MAX_READ_BUFFER_SIZE
= 64 * 1024;
30 const int32_t STATUSCODE_OK
= 200;
31 const int32_t STATUSCODE_PARTIAL_CONTENT
= 206;
32 const int32_t STATUSCODE_FORBIDDEN
= 403;
33 const int32_t STATUSCODE_NOT_FOUND
= 404;
34 const int32_t STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE
= 416;
36 StringMap_t
ParseHeaders(const char* headers
, int32_t headers_length
) {
47 State state
= FINDING_KEY
;
48 const char* start
= headers
;
49 for (int i
= 0; i
< headers_length
; ++i
) {
52 if (headers
[i
] == ':') {
54 key
.assign(start
, &headers
[i
] - start
);
55 key
= NormalizeHeaderKey(key
);
56 state
= SKIPPING_WHITESPACE
;
60 case SKIPPING_WHITESPACE
:
61 if (headers
[i
] == ' ') {
62 // Found whitespace, keep going...
66 // Found a non-whitespace, mark this as the start of the value.
68 state
= FINDING_VALUE
;
69 // Fallthrough to start processing value without incrementing i.
72 if (headers
[i
] == '\n') {
74 value
.assign(start
, &headers
[i
] - start
);
76 start
= &headers
[i
+ 1];
86 bool ParseContentLength(const StringMap_t
& headers
, off_t
* content_length
) {
87 StringMap_t::const_iterator iter
= headers
.find("Content-Length");
88 if (iter
== headers
.end())
91 *content_length
= strtoull(iter
->second
.c_str(), NULL
, 10);
95 bool ParseContentRange(const StringMap_t
& headers
,
98 off_t
* entity_length
) {
99 StringMap_t::const_iterator iter
= headers
.find("Content-Range");
100 if (iter
== headers
.end())
103 // The key should look like "bytes ##-##/##" or "bytes ##-##/*". The last
104 // value is the entity length, which can potentially be * (i.e. unknown).
105 off_t read_start_int
;
107 off_t entity_length_int
;
108 int result
= sscanf(iter
->second
.c_str(),
109 "bytes %" SCNi64
"-%" SCNi64
"/%" SCNi64
,
114 // The Content-Range header specifies an inclusive range: e.g. the first ten
115 // bytes is "bytes 0-9/*". Convert it to a half-open range by incrementing
119 *read_start
= read_start_int
;
121 *read_end
= read_end_int
+ 1;
125 } else if (result
== 3) {
127 *read_start
= read_start_int
;
129 *read_end
= read_end_int
+ 1;
131 *entity_length
= entity_length_int
;
138 // Maps an HTTP |status_code| onto the appropriate errno code.
139 int HTTPStatusCodeToErrno(int status_code
) {
140 switch (status_code
) {
142 case STATUSCODE_PARTIAL_CONTENT
:
144 case STATUSCODE_FORBIDDEN
:
146 case STATUSCODE_NOT_FOUND
:
149 if (status_code
>= 400 && status_code
< 500)
156 void HttpFsNode::SetCachedSize(off_t size
) {
157 has_cached_size_
= true;
158 stat_
.st_size
= size
;
161 Error
HttpFsNode::FSync() {
165 Error
HttpFsNode::GetDents(size_t offs
,
173 Error
HttpFsNode::GetStat(struct stat
* stat
) {
174 AUTO_LOCK(node_lock_
);
175 return GetStat_Locked(stat
);
178 Error
HttpFsNode::Read(const HandleAttr
& attr
,
184 AUTO_LOCK(node_lock_
);
185 if (cache_content_
) {
186 if (cached_data_
.empty()) {
187 Error error
= DownloadToCache();
192 return ReadPartialFromCache(attr
, buf
, count
, out_bytes
);
195 return DownloadPartial(attr
, buf
, count
, out_bytes
);
198 Error
HttpFsNode::FTruncate(off_t size
) {
202 Error
HttpFsNode::Write(const HandleAttr
& attr
,
206 // TODO(binji): support POST?
211 Error
HttpFsNode::GetSize(off_t
* out_size
) {
214 // TODO(binji): This value should be cached properly; i.e. obey the caching
215 // headers returned by the server.
216 AUTO_LOCK(node_lock_
);
218 Error error
= GetStat_Locked(&statbuf
);
222 *out_size
= stat_
.st_size
;
226 HttpFsNode::HttpFsNode(Filesystem
* filesystem
,
227 const std::string
& url
,
233 cache_content_(cache_content
),
234 has_cached_size_(false) {
235 // http nodes are read-only by default
239 HttpFsNode::~HttpFsNode() {
243 Error
HttpFsNode::GetStat_Locked(struct stat
* stat
) {
244 // Assume we need to 'HEAD' if we do not know the size, otherwise, assume
245 // that the information is constant. We can add a timeout if needed.
246 HttpFs
* filesystem
= static_cast<HttpFs
*>(filesystem_
);
247 if (!has_cached_size_
|| !filesystem
->cache_stat_
) {
249 ScopedResource
loader(filesystem_
->ppapi());
250 ScopedResource
request(filesystem_
->ppapi());
251 ScopedResource
response(filesystem_
->ppapi());
253 StringMap_t response_headers
;
254 const char* method
= "HEAD";
256 if (filesystem
->is_blob_url_
) {
257 // Blob URLs do not support HEAD requests, but do give the content length
258 // in their response headers. We issue a single-byte GET request to
259 // retrieve the content length.
261 headers
["Range"] = "bytes=0-0";
264 Error error
= OpenUrl(method
,
275 if (ParseContentRange(response_headers
, NULL
, NULL
, &entity_length
)) {
276 SetCachedSize(static_cast<off_t
>(entity_length
));
277 } else if (ParseContentLength(response_headers
, &entity_length
)) {
278 SetCachedSize(static_cast<off_t
>(entity_length
));
279 } else if (cache_content_
) {
280 // The server didn't give a content length; download the data to memory
281 // via DownloadToCache, which will also set stat_.st_size;
282 error
= DownloadToCache();
286 // The user doesn't want to cache content, but we didn't get a
287 // "Content-Length" header. Read the entire entity, and throw it away.
288 // Don't use DownloadToCache, as that will still allocate enough memory
289 // for the entire entity.
291 error
= DownloadToTemp(&bytes_read
);
295 SetCachedSize(bytes_read
);
298 stat_
.st_atime
= 0; // TODO(binji): Use "Last-Modified".
305 // Fill the stat structure if provided
312 Error
HttpFsNode::OpenUrl(const char* method
,
313 StringMap_t
* request_headers
,
314 ScopedResource
* out_loader
,
315 ScopedResource
* out_request
,
316 ScopedResource
* out_response
,
317 int32_t* out_statuscode
,
318 StringMap_t
* out_response_headers
) {
319 // Clear all out parameters.
321 out_response_headers
->clear();
323 // Assume lock_ is already held.
324 PepperInterface
* ppapi
= filesystem_
->ppapi();
326 HttpFs
* mount_http
= static_cast<HttpFs
*>(filesystem_
);
328 mount_http
->MakeUrlRequestInfo(url_
, method
, request_headers
));
329 if (!out_request
->pp_resource())
332 URLLoaderInterface
* loader_interface
= ppapi
->GetURLLoaderInterface();
333 URLResponseInfoInterface
* response_interface
=
334 ppapi
->GetURLResponseInfoInterface();
335 VarInterface
* var_interface
= ppapi
->GetVarInterface();
337 out_loader
->Reset(loader_interface
->Create(ppapi
->GetInstance()));
338 if (!out_loader
->pp_resource())
341 int32_t result
= loader_interface
->Open(out_loader
->pp_resource(),
342 out_request
->pp_resource(),
343 PP_BlockUntilComplete());
345 return PPERROR_TO_ERRNO(result
);
348 loader_interface
->GetResponseInfo(out_loader
->pp_resource()));
349 if (!out_response
->pp_resource())
352 // Get response statuscode.
353 PP_Var statuscode
= response_interface
->GetProperty(
354 out_response
->pp_resource(), PP_URLRESPONSEPROPERTY_STATUSCODE
);
356 if (statuscode
.type
!= PP_VARTYPE_INT32
)
359 *out_statuscode
= statuscode
.value
.as_int
;
361 // Only accept OK or Partial Content.
362 Error error
= HTTPStatusCodeToErrno(*out_statuscode
);
366 // Get response headers.
367 PP_Var response_headers_var
= response_interface
->GetProperty(
368 out_response
->pp_resource(), PP_URLRESPONSEPROPERTY_HEADERS
);
370 uint32_t response_headers_length
;
371 const char* response_headers_str
=
372 var_interface
->VarToUtf8(response_headers_var
, &response_headers_length
);
374 *out_response_headers
=
375 ParseHeaders(response_headers_str
, response_headers_length
);
377 var_interface
->Release(response_headers_var
);
382 Error
HttpFsNode::DownloadToCache() {
384 ScopedResource
loader(filesystem_
->ppapi());
385 ScopedResource
request(filesystem_
->ppapi());
386 ScopedResource
response(filesystem_
->ppapi());
388 StringMap_t response_headers
;
389 Error error
= OpenUrl("GET",
399 off_t content_length
= 0;
400 if (ParseContentLength(response_headers
, &content_length
)) {
401 cached_data_
.resize(content_length
);
403 error
= ReadResponseToBuffer(
404 loader
, cached_data_
.data(), content_length
, &real_size
);
408 SetCachedSize(real_size
);
409 cached_data_
.resize(real_size
);
414 error
= ReadEntireResponseToCache(loader
, &bytes_read
);
418 SetCachedSize(bytes_read
);
422 Error
HttpFsNode::ReadPartialFromCache(const HandleAttr
& attr
,
427 off_t size
= cached_data_
.size();
429 if (attr
.offs
+ count
> size
)
430 count
= size
- attr
.offs
;
435 memcpy(buf
, &cached_data_
.data()[attr
.offs
], count
);
440 Error
HttpFsNode::DownloadPartial(const HandleAttr
& attr
,
449 // Range request is inclusive: 0-99 returns 100 bytes.
452 "bytes=%" PRIi64
"-%" PRIi64
,
454 attr
.offs
+ count
- 1);
455 headers
["Range"] = buffer
;
457 ScopedResource
loader(filesystem_
->ppapi());
458 ScopedResource
request(filesystem_
->ppapi());
459 ScopedResource
response(filesystem_
->ppapi());
461 StringMap_t response_headers
;
462 Error error
= OpenUrl("GET",
470 if (statuscode
== STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE
) {
471 // We're likely trying to read past the end. Return 0 bytes.
479 off_t read_start
= 0;
480 if (statuscode
== STATUSCODE_OK
) {
481 // No partial result, read everything starting from the part we care about.
482 off_t content_length
;
483 if (ParseContentLength(response_headers
, &content_length
)) {
484 if (attr
.offs
>= content_length
)
487 // Clamp count, if trying to read past the end of the file.
488 if (attr
.offs
+ count
> content_length
) {
489 count
= content_length
- attr
.offs
;
492 } else if (statuscode
== STATUSCODE_PARTIAL_CONTENT
) {
493 // Determine from the headers where we are reading.
496 if (ParseContentRange(
497 response_headers
, &read_start
, &read_end
, &entity_length
)) {
498 if (read_start
> attr
.offs
|| read_start
> read_end
) {
499 // If this error occurs, the server is returning bogus values.
503 // Clamp count, if trying to read past the end of the file.
504 count
= std::min(read_end
- read_start
, count
);
506 // Partial Content without Content-Range. Assume that the server gave us
507 // exactly what we asked for. This can happen even when the server
508 // returns 200 -- the cache may return 206 in this case, but not modify
510 read_start
= attr
.offs
;
514 if (read_start
< attr
.offs
) {
515 // We aren't yet at the location where we want to start reading. Read into
516 // our dummy buffer until then.
517 int bytes_to_read
= attr
.offs
- read_start
;
519 error
= ReadResponseToTemp(loader
, bytes_to_read
, &bytes_read
);
523 // Tried to read past the end of the entity.
524 if (bytes_read
< bytes_to_read
) {
530 return ReadResponseToBuffer(loader
, buf
, count
, out_bytes
);
533 Error
HttpFsNode::DownloadToTemp(off_t
* out_bytes
) {
535 ScopedResource
loader(filesystem_
->ppapi());
536 ScopedResource
request(filesystem_
->ppapi());
537 ScopedResource
response(filesystem_
->ppapi());
539 StringMap_t response_headers
;
540 Error error
= OpenUrl("GET",
550 off_t content_length
= 0;
551 if (ParseContentLength(response_headers
, &content_length
)) {
552 *out_bytes
= content_length
;
556 return ReadEntireResponseToTemp(loader
, out_bytes
);
559 Error
HttpFsNode::ReadEntireResponseToTemp(const ScopedResource
& loader
,
563 const int kBytesToRead
= MAX_READ_BUFFER_SIZE
;
564 buffer_
= (char*)realloc(buffer_
, kBytesToRead
);
570 buffer_len_
= kBytesToRead
;
575 ReadResponseToBuffer(loader
, buffer_
, kBytesToRead
, &bytes_read
);
579 *out_bytes
+= bytes_read
;
581 if (bytes_read
< kBytesToRead
)
586 Error
HttpFsNode::ReadEntireResponseToCache(const ScopedResource
& loader
,
589 const int kBytesToRead
= MAX_READ_BUFFER_SIZE
;
592 // Always recalculate the buf pointer because it may have moved when
593 // cached_data_ was resized.
594 cached_data_
.resize(*out_bytes
+ kBytesToRead
);
595 void* buf
= cached_data_
.data() + *out_bytes
;
598 Error error
= ReadResponseToBuffer(loader
, buf
, kBytesToRead
, &bytes_read
);
602 *out_bytes
+= bytes_read
;
604 if (bytes_read
< kBytesToRead
) {
605 // Shrink the cached data buffer to the correct size.
606 cached_data_
.resize(*out_bytes
);
612 Error
HttpFsNode::ReadResponseToTemp(const ScopedResource
& loader
,
617 if (buffer_len_
< count
) {
618 int new_len
= std::min(count
, MAX_READ_BUFFER_SIZE
);
619 buffer_
= (char*)realloc(buffer_
, new_len
);
625 buffer_len_
= new_len
;
628 int bytes_left
= count
;
629 while (bytes_left
> 0) {
630 int bytes_to_read
= std::min(bytes_left
, buffer_len_
);
632 Error error
= ReadResponseToBuffer(
633 loader
, buffer_
, bytes_to_read
, &bytes_read
);
640 bytes_left
-= bytes_read
;
641 *out_bytes
+= bytes_read
;
647 Error
HttpFsNode::ReadResponseToBuffer(const ScopedResource
& loader
,
653 PepperInterface
* ppapi
= filesystem_
->ppapi();
654 URLLoaderInterface
* loader_interface
= ppapi
->GetURLLoaderInterface();
656 char* out_buffer
= static_cast<char*>(buf
);
657 int bytes_to_read
= count
;
658 while (bytes_to_read
> 0) {
660 loader_interface
->ReadResponseBody(loader
.pp_resource(),
663 PP_BlockUntilComplete());
665 if (bytes_read
== 0) {
666 // This is not an error -- it may just be that we were trying to read
667 // more data than exists.
668 *out_bytes
= count
- bytes_to_read
;
673 return PPERROR_TO_ERRNO(bytes_read
);
675 assert(bytes_read
<= bytes_to_read
);
676 bytes_to_read
-= bytes_read
;
677 out_buffer
+= bytes_read
;
684 } // namespace nacl_io