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) {
237 void HttpFsNode::SetMode(int mode) {
238 stat_.st_mode = mode;
241 Error HttpFsNode::GetStat_Locked(struct stat* stat) {
242 // Assume we need to 'HEAD' if we do not know the size, otherwise, assume
243 // that the information is constant. We can add a timeout if needed.
244 HttpFs* filesystem = static_cast<HttpFs*>(filesystem_);
245 if (!has_cached_size_ || !filesystem->cache_stat_) {
247 ScopedResource loader(filesystem_->ppapi());
248 ScopedResource request(filesystem_->ppapi());
249 ScopedResource response(filesystem_->ppapi());
251 StringMap_t response_headers;
252 const char* method = "HEAD";
254 if (filesystem->is_blob_url_) {
255 // Blob URLs do not support HEAD requests, but do give the content length
256 // in their response headers. We issue a single-byte GET request to
257 // retrieve the content length.
259 headers["Range"] = "bytes=0-0";
262 Error error = OpenUrl(method,
273 if (ParseContentRange(response_headers, NULL, NULL, &entity_length)) {
274 SetCachedSize(static_cast<off_t>(entity_length));
275 } else if (ParseContentLength(response_headers, &entity_length)) {
276 SetCachedSize(static_cast<off_t>(entity_length));
277 } else if (cache_content_) {
278 // The server didn't give a content length; download the data to memory
279 // via DownloadToCache, which will also set stat_.st_size;
280 error = DownloadToCache();
284 // The user doesn't want to cache content, but we didn't get a
285 // "Content-Length" header. Read the entire entity, and throw it away.
286 // Don't use DownloadToCache, as that will still allocate enough memory
287 // for the entire entity.
289 error = DownloadToTemp(&bytes_read);
293 SetCachedSize(bytes_read);
296 stat_.st_atime = 0; // TODO(binji): Use "Last-Modified".
300 stat_.st_mode |= S_IFREG;
303 // Fill the stat structure if provided
310 Error HttpFsNode::OpenUrl(const char* method,
311 StringMap_t* request_headers,
312 ScopedResource* out_loader,
313 ScopedResource* out_request,
314 ScopedResource* out_response,
315 int32_t* out_statuscode,
316 StringMap_t* out_response_headers) {
317 // Clear all out parameters.
319 out_response_headers->clear();
321 // Assume lock_ is already held.
322 PepperInterface* ppapi = filesystem_->ppapi();
324 HttpFs* mount_http = static_cast<HttpFs*>(filesystem_);
326 mount_http->MakeUrlRequestInfo(url_, method, request_headers));
327 if (!out_request->pp_resource())
330 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
331 URLResponseInfoInterface* response_interface =
332 ppapi->GetURLResponseInfoInterface();
333 VarInterface* var_interface = ppapi->GetVarInterface();
335 out_loader->Reset(loader_interface->Create(ppapi->GetInstance()));
336 if (!out_loader->pp_resource())
339 int32_t result = loader_interface->Open(out_loader->pp_resource(),
340 out_request->pp_resource(),
341 PP_BlockUntilComplete());
343 return PPErrorToErrno(result);
346 loader_interface->GetResponseInfo(out_loader->pp_resource()));
347 if (!out_response->pp_resource())
350 // Get response statuscode.
351 PP_Var statuscode = response_interface->GetProperty(
352 out_response->pp_resource(), PP_URLRESPONSEPROPERTY_STATUSCODE);
354 if (statuscode.type != PP_VARTYPE_INT32)
357 *out_statuscode = statuscode.value.as_int;
359 // Only accept OK or Partial Content.
360 Error error = HTTPStatusCodeToErrno(*out_statuscode);
364 // Get response headers.
365 PP_Var response_headers_var = response_interface->GetProperty(
366 out_response->pp_resource(), PP_URLRESPONSEPROPERTY_HEADERS);
368 uint32_t response_headers_length;
369 const char* response_headers_str =
370 var_interface->VarToUtf8(response_headers_var, &response_headers_length);
372 *out_response_headers =
373 ParseHeaders(response_headers_str, response_headers_length);
375 var_interface->Release(response_headers_var);
380 Error HttpFsNode::DownloadToCache() {
382 ScopedResource loader(filesystem_->ppapi());
383 ScopedResource request(filesystem_->ppapi());
384 ScopedResource response(filesystem_->ppapi());
386 StringMap_t response_headers;
387 Error error = OpenUrl("GET",
397 off_t content_length = 0;
398 if (ParseContentLength(response_headers, &content_length)) {
399 cached_data_.resize(content_length);
401 error = ReadResponseToBuffer(
402 loader, cached_data_.data(), content_length, &real_size);
406 SetCachedSize(real_size);
407 cached_data_.resize(real_size);
412 error = ReadEntireResponseToCache(loader, &bytes_read);
416 SetCachedSize(bytes_read);
420 Error HttpFsNode::ReadPartialFromCache(const HandleAttr& attr,
425 off_t size = cached_data_.size();
427 if (attr.offs + count > size)
428 count = size - attr.offs;
433 memcpy(buf, &cached_data_.data()[attr.offs], count);
438 Error HttpFsNode::DownloadPartial(const HandleAttr& attr,
447 // Range request is inclusive: 0-99 returns 100 bytes.
450 "bytes=%" PRIi64 "-%" PRIi64,
452 attr.offs + count - 1);
453 headers["Range"] = buffer;
455 ScopedResource loader(filesystem_->ppapi());
456 ScopedResource request(filesystem_->ppapi());
457 ScopedResource response(filesystem_->ppapi());
459 StringMap_t response_headers;
460 Error error = OpenUrl("GET",
468 if (statuscode == STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE) {
469 // We're likely trying to read past the end. Return 0 bytes.
477 off_t read_start = 0;
478 if (statuscode == STATUSCODE_OK) {
479 // No partial result, read everything starting from the part we care about.
480 off_t content_length;
481 if (ParseContentLength(response_headers, &content_length)) {
482 if (attr.offs >= content_length)
485 // Clamp count, if trying to read past the end of the file.
486 if (attr.offs + count > content_length) {
487 count = content_length - attr.offs;
490 } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) {
491 // Determine from the headers where we are reading.
494 if (ParseContentRange(
495 response_headers, &read_start, &read_end, &entity_length)) {
496 if (read_start > attr.offs || read_start > read_end) {
497 // If this error occurs, the server is returning bogus values.
501 // Clamp count, if trying to read past the end of the file.
502 count = std::min(read_end - read_start, count);
504 // Partial Content without Content-Range. Assume that the server gave us
505 // exactly what we asked for. This can happen even when the server
506 // returns 200 -- the cache may return 206 in this case, but not modify
508 read_start = attr.offs;
512 if (read_start < attr.offs) {
513 // We aren't yet at the location where we want to start reading. Read into
514 // our dummy buffer until then.
515 int bytes_to_read = attr.offs - read_start;
517 error = ReadResponseToTemp(loader, bytes_to_read, &bytes_read);
521 // Tried to read past the end of the entity.
522 if (bytes_read < bytes_to_read) {
528 return ReadResponseToBuffer(loader, buf, count, out_bytes);
531 Error HttpFsNode::DownloadToTemp(off_t* out_bytes) {
533 ScopedResource loader(filesystem_->ppapi());
534 ScopedResource request(filesystem_->ppapi());
535 ScopedResource response(filesystem_->ppapi());
537 StringMap_t response_headers;
538 Error error = OpenUrl("GET",
548 off_t content_length = 0;
549 if (ParseContentLength(response_headers, &content_length)) {
550 *out_bytes = content_length;
554 return ReadEntireResponseToTemp(loader, out_bytes);
557 Error HttpFsNode::ReadEntireResponseToTemp(const ScopedResource& loader,
561 const int kBytesToRead = MAX_READ_BUFFER_SIZE;
562 buffer_ = (char*)realloc(buffer_, kBytesToRead);
568 buffer_len_ = kBytesToRead;
573 ReadResponseToBuffer(loader, buffer_, kBytesToRead, &bytes_read);
577 *out_bytes += bytes_read;
579 if (bytes_read < kBytesToRead)
584 Error HttpFsNode::ReadEntireResponseToCache(const ScopedResource& loader,
587 const int kBytesToRead = MAX_READ_BUFFER_SIZE;
590 // Always recalculate the buf pointer because it may have moved when
591 // cached_data_ was resized.
592 cached_data_.resize(*out_bytes + kBytesToRead);
593 void* buf = cached_data_.data() + *out_bytes;
596 Error error = ReadResponseToBuffer(loader, buf, kBytesToRead, &bytes_read);
600 *out_bytes += bytes_read;
602 if (bytes_read < kBytesToRead) {
603 // Shrink the cached data buffer to the correct size.
604 cached_data_.resize(*out_bytes);
610 Error HttpFsNode::ReadResponseToTemp(const ScopedResource& loader,
615 if (buffer_len_ < count) {
616 int new_len = std::min(count, MAX_READ_BUFFER_SIZE);
617 buffer_ = (char*)realloc(buffer_, new_len);
623 buffer_len_ = new_len;
626 int bytes_left = count;
627 while (bytes_left > 0) {
628 int bytes_to_read = std::min(bytes_left, buffer_len_);
630 Error error = ReadResponseToBuffer(
631 loader, buffer_, bytes_to_read, &bytes_read);
638 bytes_left -= bytes_read;
639 *out_bytes += bytes_read;
645 Error HttpFsNode::ReadResponseToBuffer(const ScopedResource& loader,
651 PepperInterface* ppapi = filesystem_->ppapi();
652 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
654 char* out_buffer = static_cast<char*>(buf);
655 int bytes_to_read = count;
656 while (bytes_to_read > 0) {
658 loader_interface->ReadResponseBody(loader.pp_resource(),
661 PP_BlockUntilComplete());
663 if (bytes_read == 0) {
664 // This is not an error -- it may just be that we were trying to read
665 // more data than exists.
666 *out_bytes = count - bytes_to_read;
671 return PPErrorToErrno(bytes_read);
673 assert(bytes_read <= bytes_to_read);
674 bytes_to_read -= bytes_read;
675 out_buffer += bytes_read;
682 } // namespace nacl_io