Home | History | Annotate | Download | only in nacl_io
      1 // Copyright (c) 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.
      4 
      5 #include "nacl_io/mount_node_http.h"
      6 
      7 #include <assert.h>
      8 #include <errno.h>
      9 #include <stdio.h>
     10 #include <string.h>
     11 
     12 #include <ppapi/c/pp_errors.h>
     13 
     14 #include "nacl_io/mount_http.h"
     15 #include "nacl_io/osinttypes.h"
     16 
     17 #if defined(WIN32)
     18 #define snprintf _snprintf
     19 #endif
     20 
     21 namespace nacl_io {
     22 
     23 namespace {
     24 
     25 // If we're attempting to read a partial request, but the server returns a full
     26 // request, we need to read all of the data up to the start of our partial
     27 // request into a dummy buffer. This is the maximum size of that buffer.
     28 const size_t MAX_READ_BUFFER_SIZE = 64 * 1024;
     29 const int32_t STATUSCODE_OK = 200;
     30 const int32_t STATUSCODE_PARTIAL_CONTENT = 206;
     31 const int32_t STATUSCODE_FORBIDDEN = 403;
     32 const int32_t STATUSCODE_NOT_FOUND = 404;
     33 
     34 StringMap_t ParseHeaders(const char* headers, int32_t headers_length) {
     35   enum State {
     36     FINDING_KEY,
     37     SKIPPING_WHITESPACE,
     38     FINDING_VALUE,
     39   };
     40 
     41   StringMap_t result;
     42   std::string key;
     43   std::string value;
     44 
     45   State state = FINDING_KEY;
     46   const char* start = headers;
     47   for (int i = 0; i < headers_length; ++i) {
     48     switch (state) {
     49       case FINDING_KEY:
     50         if (headers[i] == ':') {
     51           // Found key.
     52           key.assign(start, &headers[i] - start);
     53           key = NormalizeHeaderKey(key);
     54           state = SKIPPING_WHITESPACE;
     55         }
     56         break;
     57 
     58       case SKIPPING_WHITESPACE:
     59         if (headers[i] == ' ') {
     60           // Found whitespace, keep going...
     61           break;
     62         }
     63 
     64         // Found a non-whitespace, mark this as the start of the value.
     65         start = &headers[i];
     66         state = FINDING_VALUE;
     67       // Fallthrough to start processing value without incrementing i.
     68 
     69       case FINDING_VALUE:
     70         if (headers[i] == '\n') {
     71           // Found value.
     72           value.assign(start, &headers[i] - start);
     73           result[key] = value;
     74           start = &headers[i + 1];
     75           state = FINDING_KEY;
     76         }
     77         break;
     78     }
     79   }
     80 
     81   return result;
     82 }
     83 
     84 bool ParseContentLength(const StringMap_t& headers, size_t* content_length) {
     85   StringMap_t::const_iterator iter = headers.find("Content-Length");
     86   if (iter == headers.end())
     87     return false;
     88 
     89   *content_length = strtoul(iter->second.c_str(), NULL, 10);
     90   return true;
     91 }
     92 
     93 bool ParseContentRange(const StringMap_t& headers,
     94                        size_t* read_start,
     95                        size_t* read_end,
     96                        size_t* entity_length) {
     97   StringMap_t::const_iterator iter = headers.find("Content-Range");
     98   if (iter == headers.end())
     99     return false;
    100 
    101   // The key should look like "bytes ##-##/##" or "bytes ##-##/*". The last
    102   // value is the entity length, which can potentially be * (i.e. unknown).
    103   int read_start_int;
    104   int read_end_int;
    105   int entity_length_int;
    106   int result = sscanf(iter->second.c_str(),
    107                       "bytes %" SCNuS "-%" SCNuS "/%" SCNuS,
    108                       &read_start_int,
    109                       &read_end_int,
    110                       &entity_length_int);
    111 
    112   // The Content-Range header specifies an inclusive range: e.g. the first ten
    113   // bytes is "bytes 0-9/*". Convert it to a half-open range by incrementing
    114   // read_end.
    115   if (result == 2) {
    116     *read_start = read_start_int;
    117     *read_end = read_end_int + 1;
    118     *entity_length = 0;
    119     return true;
    120   } else if (result == 3) {
    121     *read_start = read_start_int;
    122     *read_end = read_end_int + 1;
    123     *entity_length = entity_length_int;
    124     return true;
    125   }
    126 
    127   return false;
    128 }
    129 
    130 // Maps an HTTP |status_code| onto the appropriate errno code.
    131 int HTTPStatusCodeToErrno(int status_code) {
    132   switch (status_code) {
    133     case STATUSCODE_OK:
    134     case STATUSCODE_PARTIAL_CONTENT:
    135       return 0;
    136     case STATUSCODE_FORBIDDEN:
    137       return EACCES;
    138     case STATUSCODE_NOT_FOUND:
    139       return ENOENT;
    140   }
    141   if (status_code >= 400 && status_code < 500)
    142     return EINVAL;
    143   return EIO;
    144 }
    145 
    146 }  // namespace
    147 
    148 void MountNodeHttp::SetCachedSize(off_t size) {
    149   has_cached_size_ = true;
    150   stat_.st_size = size;
    151 }
    152 
    153 Error MountNodeHttp::FSync() { return ENOSYS; }
    154 
    155 Error MountNodeHttp::GetDents(size_t offs,
    156                               struct dirent* pdir,
    157                               size_t count,
    158                               int* out_bytes) {
    159   *out_bytes = 0;
    160   return ENOSYS;
    161 }
    162 
    163 Error MountNodeHttp::GetStat(struct stat* stat) {
    164   AUTO_LOCK(node_lock_);
    165 
    166   // Assume we need to 'HEAD' if we do not know the size, otherwise, assume
    167   // that the information is constant.  We can add a timeout if needed.
    168   MountHttp* mount = static_cast<MountHttp*>(mount_);
    169   if (stat_.st_size == 0 || !mount->cache_stat_) {
    170     StringMap_t headers;
    171     PP_Resource loader;
    172     PP_Resource request;
    173     PP_Resource response;
    174     int32_t statuscode;
    175     StringMap_t response_headers;
    176     Error error = OpenUrl("HEAD",
    177                           &headers,
    178                           &loader,
    179                           &request,
    180                           &response,
    181                           &statuscode,
    182                           &response_headers);
    183     if (error)
    184       return error;
    185 
    186     ScopedResource scoped_loader(mount_->ppapi(), loader);
    187     ScopedResource scoped_request(mount_->ppapi(), request);
    188     ScopedResource scoped_response(mount_->ppapi(), response);
    189 
    190     size_t entity_length;
    191     if (ParseContentLength(response_headers, &entity_length)) {
    192       SetCachedSize(static_cast<off_t>(entity_length));
    193     } else if (cache_content_ && !has_cached_size_) {
    194       error = DownloadToCache();
    195       // TODO(binji): this error should not be dropped, but it requires a bit
    196       // of a refactor of the tests. See crbug.com/245431
    197       // if (error)
    198       //   return error;
    199     } else {
    200       // Don't use SetCachedSize here -- it is actually unknown.
    201       stat_.st_size = 0;
    202     }
    203 
    204     stat_.st_atime = 0;  // TODO(binji): Use "Last-Modified".
    205     stat_.st_mtime = 0;
    206     stat_.st_ctime = 0;
    207   }
    208 
    209   // Fill the stat structure if provided
    210   if (stat)
    211     memcpy(stat, &stat_, sizeof(stat_));
    212 
    213   return 0;
    214 }
    215 
    216 Error MountNodeHttp::Read(size_t offs,
    217                           void* buf,
    218                           size_t count,
    219                           int* out_bytes) {
    220   *out_bytes = 0;
    221 
    222   AUTO_LOCK(node_lock_);
    223   if (cache_content_) {
    224     if (cached_data_.empty()) {
    225       Error error = DownloadToCache();
    226       if (error)
    227         return error;
    228     }
    229 
    230     return ReadPartialFromCache(offs, buf, count, out_bytes);
    231   }
    232 
    233   return DownloadPartial(offs, buf, count, out_bytes);
    234 }
    235 
    236 Error MountNodeHttp::FTruncate(off_t size) { return ENOSYS; }
    237 
    238 Error MountNodeHttp::Write(size_t offs,
    239                            const void* buf,
    240                            size_t count,
    241                            int* out_bytes) {
    242   // TODO(binji): support POST?
    243   *out_bytes = 0;
    244   return ENOSYS;
    245 }
    246 
    247 Error MountNodeHttp::GetSize(size_t* out_size) {
    248   *out_size = 0;
    249 
    250   // TODO(binji): This value should be cached properly; i.e. obey the caching
    251   // headers returned by the server.
    252   AUTO_LOCK(node_lock_);
    253   if (!has_cached_size_) {
    254     // Even if DownloadToCache fails, the best result we can return is what
    255     // was written to stat_.st_size.
    256     if (cache_content_) {
    257       Error error = DownloadToCache();
    258       if (error)
    259         return error;
    260     }
    261   }
    262 
    263   *out_size = stat_.st_size;
    264   return 0;
    265 }
    266 
    267 MountNodeHttp::MountNodeHttp(Mount* mount,
    268                              const std::string& url,
    269                              bool cache_content)
    270     : MountNode(mount),
    271       url_(url),
    272       cache_content_(cache_content),
    273       has_cached_size_(false) {}
    274 
    275 Error MountNodeHttp::OpenUrl(const char* method,
    276                              StringMap_t* request_headers,
    277                              PP_Resource* out_loader,
    278                              PP_Resource* out_request,
    279                              PP_Resource* out_response,
    280                              int32_t* out_statuscode,
    281                              StringMap_t* out_response_headers) {
    282   // Assume lock_ is already held.
    283   PepperInterface* ppapi = mount_->ppapi();
    284 
    285   MountHttp* mount_http = static_cast<MountHttp*>(mount_);
    286   ScopedResource request(
    287       ppapi, mount_http->MakeUrlRequestInfo(url_, method, request_headers));
    288   if (!request.pp_resource())
    289     return EINVAL;
    290 
    291   URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
    292   URLResponseInfoInterface* response_interface =
    293       ppapi->GetURLResponseInfoInterface();
    294   VarInterface* var_interface = ppapi->GetVarInterface();
    295 
    296   ScopedResource loader(ppapi, loader_interface->Create(ppapi->GetInstance()));
    297   if (!loader.pp_resource())
    298     return EINVAL;
    299 
    300   int32_t result = loader_interface->Open(
    301       loader.pp_resource(), request.pp_resource(), PP_BlockUntilComplete());
    302   if (result != PP_OK)
    303     return PPErrorToErrno(result);
    304 
    305   ScopedResource response(
    306       ppapi, loader_interface->GetResponseInfo(loader.pp_resource()));
    307   if (!response.pp_resource())
    308     return EINVAL;
    309 
    310   // Get response statuscode.
    311   PP_Var statuscode = response_interface->GetProperty(
    312       response.pp_resource(), PP_URLRESPONSEPROPERTY_STATUSCODE);
    313 
    314   if (statuscode.type != PP_VARTYPE_INT32)
    315     return EINVAL;
    316 
    317   *out_statuscode = statuscode.value.as_int;
    318 
    319   // Only accept OK or Partial Content.
    320   Error error = HTTPStatusCodeToErrno(*out_statuscode);
    321   if (error)
    322     return error;
    323 
    324   // Get response headers.
    325   PP_Var response_headers_var = response_interface->GetProperty(
    326       response.pp_resource(), PP_URLRESPONSEPROPERTY_HEADERS);
    327 
    328   uint32_t response_headers_length;
    329   const char* response_headers_str =
    330       var_interface->VarToUtf8(response_headers_var, &response_headers_length);
    331 
    332   *out_loader = loader.Release();
    333   *out_request = request.Release();
    334   *out_response = response.Release();
    335   *out_response_headers =
    336       ParseHeaders(response_headers_str, response_headers_length);
    337 
    338   return 0;
    339 }
    340 
    341 Error MountNodeHttp::DownloadToCache() {
    342   StringMap_t headers;
    343   PP_Resource loader;
    344   PP_Resource request;
    345   PP_Resource response;
    346   int32_t statuscode;
    347   StringMap_t response_headers;
    348   Error error = OpenUrl("GET",
    349                         &headers,
    350                         &loader,
    351                         &request,
    352                         &response,
    353                         &statuscode,
    354                         &response_headers);
    355   if (error)
    356     return error;
    357 
    358   PepperInterface* ppapi = mount_->ppapi();
    359   ScopedResource scoped_loader(ppapi, loader);
    360   ScopedResource scoped_request(ppapi, request);
    361   ScopedResource scoped_response(ppapi, response);
    362 
    363   size_t content_length = 0;
    364   if (ParseContentLength(response_headers, &content_length)) {
    365     cached_data_.resize(content_length);
    366     int real_size;
    367     error = DownloadToBuffer(
    368         loader, cached_data_.data(), content_length, &real_size);
    369     if (error)
    370       return error;
    371 
    372     SetCachedSize(real_size);
    373     cached_data_.resize(real_size);
    374     return 0;
    375   }
    376 
    377   // We don't know how big the file is. Read in chunks.
    378   cached_data_.resize(MAX_READ_BUFFER_SIZE);
    379   int total_bytes_read = 0;
    380   int bytes_to_read = MAX_READ_BUFFER_SIZE;
    381   while (true) {
    382     char* buf = cached_data_.data() + total_bytes_read;
    383     int bytes_read;
    384     error = DownloadToBuffer(loader, buf, bytes_to_read, &bytes_read);
    385     if (error)
    386       return error;
    387 
    388     total_bytes_read += bytes_read;
    389 
    390     if (bytes_read < bytes_to_read) {
    391       SetCachedSize(total_bytes_read);
    392       cached_data_.resize(total_bytes_read);
    393       return 0;
    394     }
    395 
    396     cached_data_.resize(total_bytes_read + bytes_to_read);
    397   }
    398 }
    399 
    400 Error MountNodeHttp::ReadPartialFromCache(size_t offs,
    401                                           void* buf,
    402                                           int count,
    403                                           int* out_bytes) {
    404   *out_bytes = 0;
    405 
    406   if (offs > cached_data_.size())
    407     return EINVAL;
    408 
    409   count = std::min(count, static_cast<int>(cached_data_.size() - offs));
    410   memcpy(buf, &cached_data_.data()[offs], count);
    411 
    412   *out_bytes = count;
    413   return 0;
    414 }
    415 
    416 Error MountNodeHttp::DownloadPartial(size_t offs,
    417                                      void* buf,
    418                                      size_t count,
    419                                      int* out_bytes) {
    420   *out_bytes = 0;
    421 
    422   StringMap_t headers;
    423 
    424   char buffer[100];
    425   // Range request is inclusive: 0-99 returns 100 bytes.
    426   snprintf(&buffer[0],
    427            sizeof(buffer),
    428            "bytes=%" PRIuS "-%" PRIuS,
    429            offs,
    430            offs + count - 1);
    431   headers["Range"] = buffer;
    432 
    433   PP_Resource loader;
    434   PP_Resource request;
    435   PP_Resource response;
    436   int32_t statuscode;
    437   StringMap_t response_headers;
    438   Error error = OpenUrl("GET",
    439                         &headers,
    440                         &loader,
    441                         &request,
    442                         &response,
    443                         &statuscode,
    444                         &response_headers);
    445   if (error)
    446     return error;
    447 
    448   PepperInterface* ppapi = mount_->ppapi();
    449   ScopedResource scoped_loader(ppapi, loader);
    450   ScopedResource scoped_request(ppapi, request);
    451   ScopedResource scoped_response(ppapi, response);
    452 
    453   size_t read_start = 0;
    454   if (statuscode == STATUSCODE_OK) {
    455     // No partial result, read everything starting from the part we care about.
    456     size_t content_length;
    457     if (ParseContentLength(response_headers, &content_length)) {
    458       if (offs >= content_length)
    459         return EINVAL;
    460 
    461       // Clamp count, if trying to read past the end of the file.
    462       if (offs + count > content_length) {
    463         count = content_length - offs;
    464       }
    465     }
    466   } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) {
    467     // Determine from the headers where we are reading.
    468     size_t read_end;
    469     size_t entity_length;
    470     if (ParseContentRange(
    471             response_headers, &read_start, &read_end, &entity_length)) {
    472       if (read_start > offs || read_start > read_end) {
    473         // If this error occurs, the server is returning bogus values.
    474         return EINVAL;
    475       }
    476 
    477       // Clamp count, if trying to read past the end of the file.
    478       count = std::min(read_end - read_start, count);
    479     } else {
    480       // Partial Content without Content-Range. Assume that the server gave us
    481       // exactly what we asked for. This can happen even when the server
    482       // returns 200 -- the cache may return 206 in this case, but not modify
    483       // the headers.
    484       read_start = offs;
    485     }
    486   }
    487 
    488   if (read_start < offs) {
    489     // We aren't yet at the location where we want to start reading. Read into
    490     // our dummy buffer until then.
    491     size_t bytes_to_read = offs - read_start;
    492     if (buffer_.size() < bytes_to_read)
    493       buffer_.resize(std::min(bytes_to_read, MAX_READ_BUFFER_SIZE));
    494 
    495     while (bytes_to_read > 0) {
    496       int32_t bytes_read;
    497       Error error =
    498           DownloadToBuffer(loader, buffer_.data(), buffer_.size(), &bytes_read);
    499       if (error)
    500         return error;
    501 
    502       bytes_to_read -= bytes_read;
    503     }
    504   }
    505 
    506   return DownloadToBuffer(loader, buf, count, out_bytes);
    507 }
    508 
    509 Error MountNodeHttp::DownloadToBuffer(PP_Resource loader,
    510                                       void* buf,
    511                                       int count,
    512                                       int* out_bytes) {
    513   *out_bytes = 0;
    514 
    515   PepperInterface* ppapi = mount_->ppapi();
    516   URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
    517 
    518   char* out_buffer = static_cast<char*>(buf);
    519   int bytes_to_read = count;
    520   while (bytes_to_read > 0) {
    521     int bytes_read = loader_interface->ReadResponseBody(
    522         loader, out_buffer, bytes_to_read, PP_BlockUntilComplete());
    523 
    524     if (bytes_read == 0) {
    525       // This is not an error -- it may just be that we were trying to read
    526       // more data than exists.
    527       *out_bytes = count - bytes_to_read;
    528       return 0;
    529     }
    530 
    531     if (bytes_read < 0)
    532       return PPErrorToErrno(bytes_read);
    533 
    534     assert(bytes_read <= bytes_to_read);
    535     bytes_to_read -= bytes_read;
    536     out_buffer += bytes_read;
    537   }
    538 
    539   *out_bytes = count;
    540   return 0;
    541 }
    542 
    543 }  // namespace nacl_io
    544 
    545