diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-22 18:52:15 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-22 18:52:15 +0000 |
commit | cd7541a7fbe2c0f343ca307ff6d0e3ac98885daa (patch) | |
tree | 7c4cc5f8485a093a938a27e321028ecb501420ed /native_client_sdk | |
parent | a93e6564a2ceb51148422e158edfbd7e91891b62 (diff) | |
download | chromium_src-cd7541a7fbe2c0f343ca307ff6d0e3ac98885daa.zip chromium_src-cd7541a7fbe2c0f343ca307ff6d0e3ac98885daa.tar.gz chromium_src-cd7541a7fbe2c0f343ca307ff6d0e3ac98885daa.tar.bz2 |
[NaCl SDK] nacl_io: Fix some httpfs bugs.
* Fix GetStat returning size=0, when there is no "Content-Length" header, and
cache_stat is off.
* Fix infinite loop when reading uncached past the end of the entity.
* Fix error when doing partial read past the end of the entity.
* Fix Fake URLLoader to handle partial content correctly.
* Add parameterized HTTP tests (on each caching type, cache_stat/cache_content).
BUG=320557
R=sbc@chromium.org
Review URL: https://codereview.chromium.org/81483002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236799 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
6 files changed, 453 insertions, 331 deletions
diff --git a/native_client_sdk/src/libraries/nacl_io/mount_node_http.cc b/native_client_sdk/src/libraries/nacl_io/mount_node_http.cc index 25135e4..66b63c7 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_node_http.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_node_http.cc @@ -26,11 +26,12 @@ namespace { // If we're attempting to read a partial request, but the server returns a full // request, we need to read all of the data up to the start of our partial // request into a dummy buffer. This is the maximum size of that buffer. -const size_t MAX_READ_BUFFER_SIZE = 64 * 1024; +const int MAX_READ_BUFFER_SIZE = 64 * 1024; const int32_t STATUSCODE_OK = 200; const int32_t STATUSCODE_PARTIAL_CONTENT = 206; const int32_t STATUSCODE_FORBIDDEN = 403; const int32_t STATUSCODE_NOT_FOUND = 404; +const int32_t STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE = 416; StringMap_t ParseHeaders(const char* headers, int32_t headers_length) { enum State { @@ -163,55 +164,7 @@ Error MountNodeHttp::GetDents(size_t offs, Error MountNodeHttp::GetStat(struct stat* stat) { AUTO_LOCK(node_lock_); - - // Assume we need to 'HEAD' if we do not know the size, otherwise, assume - // that the information is constant. We can add a timeout if needed. - MountHttp* mount = static_cast<MountHttp*>(mount_); - if (stat_.st_size == 0 || !mount->cache_stat_) { - StringMap_t headers; - PP_Resource loader; - PP_Resource request; - PP_Resource response; - int32_t statuscode; - StringMap_t response_headers; - Error error = OpenUrl("HEAD", - &headers, - &loader, - &request, - &response, - &statuscode, - &response_headers); - if (error) - return error; - - ScopedResource scoped_loader(mount_->ppapi(), loader); - ScopedResource scoped_request(mount_->ppapi(), request); - ScopedResource scoped_response(mount_->ppapi(), response); - - size_t entity_length; - if (ParseContentLength(response_headers, &entity_length)) { - SetCachedSize(static_cast<off_t>(entity_length)); - } else if (cache_content_ && !has_cached_size_) { - error = DownloadToCache(); - if (error) - return error; - } else { - // Don't use SetCachedSize here -- it is actually unknown. - stat_.st_size = 0; - } - - stat_.st_atime = 0; // TODO(binji): Use "Last-Modified". - stat_.st_mtime = 0; - stat_.st_ctime = 0; - - stat_.st_mode |= S_IFREG; - } - - // Fill the stat structure if provided - if (stat) - *stat = stat_; - - return 0; + return GetStat_Locked(stat); } Error MountNodeHttp::Read(const HandleAttr& attr, @@ -251,15 +204,10 @@ Error MountNodeHttp::GetSize(size_t* out_size) { // TODO(binji): This value should be cached properly; i.e. obey the caching // headers returned by the server. AUTO_LOCK(node_lock_); - if (!has_cached_size_) { - // Even if DownloadToCache fails, the best result we can return is what - // was written to stat_.st_size. - if (cache_content_) { - Error error = DownloadToCache(); - if (error) - return error; - } - } + struct stat statbuf; + Error error = GetStat_Locked(&statbuf); + if (error) + return error; *out_size = stat_.st_size; return 0; @@ -273,24 +221,83 @@ MountNodeHttp::MountNodeHttp(Mount* mount, cache_content_(cache_content), has_cached_size_(false) {} -void MountNodeHttp::SetMode(int mode) { - stat_.st_mode = mode; +void MountNodeHttp::SetMode(int mode) { stat_.st_mode = mode; } + +Error MountNodeHttp::GetStat_Locked(struct stat* stat) { + // Assume we need to 'HEAD' if we do not know the size, otherwise, assume + // that the information is constant. We can add a timeout if needed. + MountHttp* mount = static_cast<MountHttp*>(mount_); + if (!has_cached_size_ || !mount->cache_stat_) { + StringMap_t headers; + ScopedResource loader(mount_->ppapi()); + ScopedResource request(mount_->ppapi()); + ScopedResource response(mount_->ppapi()); + int32_t statuscode; + StringMap_t response_headers; + Error error = OpenUrl("HEAD", + &headers, + &loader, + &request, + &response, + &statuscode, + &response_headers); + if (error) + return error; + + size_t entity_length; + if (ParseContentLength(response_headers, &entity_length)) { + SetCachedSize(static_cast<off_t>(entity_length)); + } else if (cache_content_) { + // The server didn't give a content length; download the data to memory + // via DownloadToCache, which will also set stat_.st_size; + error = DownloadToCache(); + if (error) + return error; + } else { + // The user doesn't want to cache content, but we didn't get a + // "Content-Length" header. Read the entire entity, and throw it away. + // Don't use DownloadToCache, as that will still allocate enough memory + // for the entire entity. + int bytes_read; + error = DownloadToTemp(&bytes_read); + if (error) + return error; + + SetCachedSize(bytes_read); + } + + stat_.st_atime = 0; // TODO(binji): Use "Last-Modified". + stat_.st_mtime = 0; + stat_.st_ctime = 0; + + stat_.st_mode |= S_IFREG; + } + + // Fill the stat structure if provided + if (stat) + *stat = stat_; + + return 0; } Error MountNodeHttp::OpenUrl(const char* method, StringMap_t* request_headers, - PP_Resource* out_loader, - PP_Resource* out_request, - PP_Resource* out_response, + ScopedResource* out_loader, + ScopedResource* out_request, + ScopedResource* out_response, int32_t* out_statuscode, StringMap_t* out_response_headers) { + // Clear all out parameters. + *out_statuscode = 0; + out_response_headers->clear(); + // Assume lock_ is already held. PepperInterface* ppapi = mount_->ppapi(); MountHttp* mount_http = static_cast<MountHttp*>(mount_); - ScopedResource request( - ppapi, mount_http->MakeUrlRequestInfo(url_, method, request_headers)); - if (!request.pp_resource()) + out_request->Reset( + mount_http->MakeUrlRequestInfo(url_, method, request_headers)); + if (!out_request->pp_resource()) return EINVAL; URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface(); @@ -298,23 +305,24 @@ Error MountNodeHttp::OpenUrl(const char* method, ppapi->GetURLResponseInfoInterface(); VarInterface* var_interface = ppapi->GetVarInterface(); - ScopedResource loader(ppapi, loader_interface->Create(ppapi->GetInstance())); - if (!loader.pp_resource()) + out_loader->Reset(loader_interface->Create(ppapi->GetInstance())); + if (!out_loader->pp_resource()) return EINVAL; - int32_t result = loader_interface->Open( - loader.pp_resource(), request.pp_resource(), PP_BlockUntilComplete()); + int32_t result = loader_interface->Open(out_loader->pp_resource(), + out_request->pp_resource(), + PP_BlockUntilComplete()); if (result != PP_OK) return PPErrorToErrno(result); - ScopedResource response( - ppapi, loader_interface->GetResponseInfo(loader.pp_resource())); - if (!response.pp_resource()) + out_response->Reset( + loader_interface->GetResponseInfo(out_loader->pp_resource())); + if (!out_response->pp_resource()) return EINVAL; // Get response statuscode. PP_Var statuscode = response_interface->GetProperty( - response.pp_resource(), PP_URLRESPONSEPROPERTY_STATUSCODE); + out_response->pp_resource(), PP_URLRESPONSEPROPERTY_STATUSCODE); if (statuscode.type != PP_VARTYPE_INT32) return EINVAL; @@ -328,15 +336,12 @@ Error MountNodeHttp::OpenUrl(const char* method, // Get response headers. PP_Var response_headers_var = response_interface->GetProperty( - response.pp_resource(), PP_URLRESPONSEPROPERTY_HEADERS); + out_response->pp_resource(), PP_URLRESPONSEPROPERTY_HEADERS); uint32_t response_headers_length; const char* response_headers_str = var_interface->VarToUtf8(response_headers_var, &response_headers_length); - *out_loader = loader.Release(); - *out_request = request.Release(); - *out_response = response.Release(); *out_response_headers = ParseHeaders(response_headers_str, response_headers_length); @@ -347,9 +352,9 @@ Error MountNodeHttp::OpenUrl(const char* method, Error MountNodeHttp::DownloadToCache() { StringMap_t headers; - PP_Resource loader; - PP_Resource request; - PP_Resource response; + ScopedResource loader(mount_->ppapi()); + ScopedResource request(mount_->ppapi()); + ScopedResource response(mount_->ppapi()); int32_t statuscode; StringMap_t response_headers; Error error = OpenUrl("GET", @@ -362,16 +367,11 @@ Error MountNodeHttp::DownloadToCache() { if (error) return error; - PepperInterface* ppapi = mount_->ppapi(); - ScopedResource scoped_loader(ppapi, loader); - ScopedResource scoped_request(ppapi, request); - ScopedResource scoped_response(ppapi, response); - size_t content_length = 0; if (ParseContentLength(response_headers, &content_length)) { cached_data_.resize(content_length); int real_size; - error = DownloadToBuffer( + error = ReadResponseToBuffer( loader, cached_data_.data(), content_length, &real_size); if (error) return error; @@ -381,27 +381,13 @@ Error MountNodeHttp::DownloadToCache() { return 0; } - // We don't know how big the file is. Read in chunks. - cached_data_.resize(MAX_READ_BUFFER_SIZE); - int total_bytes_read = 0; - int bytes_to_read = MAX_READ_BUFFER_SIZE; - while (true) { - char* buf = cached_data_.data() + total_bytes_read; - int bytes_read; - error = DownloadToBuffer(loader, buf, bytes_to_read, &bytes_read); - if (error) - return error; - - total_bytes_read += bytes_read; - - if (bytes_read < bytes_to_read) { - SetCachedSize(total_bytes_read); - cached_data_.resize(total_bytes_read); - return 0; - } + int bytes_read; + error = ReadEntireResponseToCache(loader, &bytes_read); + if (error) + return error; - cached_data_.resize(total_bytes_read + bytes_to_read); - } + SetCachedSize(bytes_read); + return 0; } Error MountNodeHttp::ReadPartialFromCache(const HandleAttr& attr, @@ -439,9 +425,9 @@ Error MountNodeHttp::DownloadPartial(const HandleAttr& attr, attr.offs + count - 1); headers["Range"] = buffer; - PP_Resource loader; - PP_Resource request; - PP_Resource response; + ScopedResource loader(mount_->ppapi()); + ScopedResource request(mount_->ppapi()); + ScopedResource response(mount_->ppapi()); int32_t statuscode; StringMap_t response_headers; Error error = OpenUrl("GET", @@ -451,13 +437,15 @@ Error MountNodeHttp::DownloadPartial(const HandleAttr& attr, &response, &statuscode, &response_headers); - if (error) - return error; + if (error) { + if (statuscode == STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE) { + // We're likely trying to read past the end. Return 0 bytes. + *out_bytes = 0; + return 0; + } - PepperInterface* ppapi = mount_->ppapi(); - ScopedResource scoped_loader(ppapi, loader); - ScopedResource scoped_request(ppapi, request); - ScopedResource scoped_response(ppapi, response); + return error; + } size_t read_start = 0; if (statuscode == STATUSCODE_OK) { @@ -497,28 +485,127 @@ Error MountNodeHttp::DownloadPartial(const HandleAttr& attr, if (read_start < attr.offs) { // We aren't yet at the location where we want to start reading. Read into // our dummy buffer until then. - size_t bytes_to_read = attr.offs - read_start; - if (buffer_.size() < bytes_to_read) - buffer_.resize(std::min(bytes_to_read, MAX_READ_BUFFER_SIZE)); - - while (bytes_to_read > 0) { - int32_t bytes_read; - Error error = - DownloadToBuffer(loader, buffer_.data(), buffer_.size(), &bytes_read); - if (error) - return error; + int bytes_to_read = attr.offs - read_start; + int bytes_read; + error = ReadResponseToTemp(loader, bytes_to_read, &bytes_read); + if (error) + return error; - bytes_to_read -= bytes_read; + // Tried to read past the end of the entity. + if (bytes_read < bytes_to_read) { + *out_bytes = 0; + return 0; } } - return DownloadToBuffer(loader, buf, count, out_bytes); + return ReadResponseToBuffer(loader, buf, count, out_bytes); +} + +Error MountNodeHttp::DownloadToTemp(int* out_bytes) { + StringMap_t headers; + ScopedResource loader(mount_->ppapi()); + ScopedResource request(mount_->ppapi()); + ScopedResource response(mount_->ppapi()); + int32_t statuscode; + StringMap_t response_headers; + Error error = OpenUrl("GET", + &headers, + &loader, + &request, + &response, + &statuscode, + &response_headers); + if (error) + return error; + + size_t content_length = 0; + if (ParseContentLength(response_headers, &content_length)) { + *out_bytes = content_length; + return 0; + } + + return ReadEntireResponseToTemp(loader, out_bytes); } -Error MountNodeHttp::DownloadToBuffer(PP_Resource loader, - void* buf, - int count, - int* out_bytes) { +Error MountNodeHttp::ReadEntireResponseToTemp(const ScopedResource& loader, + int* out_bytes) { + *out_bytes = 0; + + const int kBytesToRead = MAX_READ_BUFFER_SIZE; + buffer_.resize(kBytesToRead); + + while (true) { + int bytes_read; + Error error = + ReadResponseToBuffer(loader, buffer_.data(), kBytesToRead, &bytes_read); + if (error) + return error; + + *out_bytes += bytes_read; + + if (bytes_read < kBytesToRead) + return 0; + } +} + +Error MountNodeHttp::ReadEntireResponseToCache(const ScopedResource& loader, + int* out_bytes) { + *out_bytes = 0; + const int kBytesToRead = MAX_READ_BUFFER_SIZE; + + while (true) { + // Always recalculate the buf pointer because it may have moved when + // cached_data_ was resized. + cached_data_.resize(*out_bytes + kBytesToRead); + void* buf = cached_data_.data() + *out_bytes; + + int bytes_read; + Error error = ReadResponseToBuffer(loader, buf, kBytesToRead, &bytes_read); + if (error) + return error; + + *out_bytes += bytes_read; + + if (bytes_read < kBytesToRead) { + // Shrink the cached data buffer to the correct size. + cached_data_.resize(*out_bytes); + return 0; + } + } +} + +Error MountNodeHttp::ReadResponseToTemp(const ScopedResource& loader, + int count, + int* out_bytes) { + *out_bytes = 0; + + if (buffer_.size() < static_cast<size_t>(count)) + buffer_.resize(std::min(count, MAX_READ_BUFFER_SIZE)); + + int bytes_left = count; + while (bytes_left > 0) { + int bytes_to_read = + std::min(static_cast<size_t>(bytes_left), buffer_.size()); + int bytes_read; + Error error = ReadResponseToBuffer( + loader, buffer_.data(), bytes_to_read, &bytes_read); + if (error) + return error; + + if (bytes_read == 0) + return 0; + + bytes_left -= bytes_read; + *out_bytes += bytes_read; + } + + return 0; +} + +Error MountNodeHttp::ReadResponseToBuffer(const ScopedResource& loader, + void* buf, + int count, + int* out_bytes) { *out_bytes = 0; PepperInterface* ppapi = mount_->ppapi(); @@ -527,8 +614,11 @@ Error MountNodeHttp::DownloadToBuffer(PP_Resource loader, char* out_buffer = static_cast<char*>(buf); int bytes_to_read = count; while (bytes_to_read > 0) { - int bytes_read = loader_interface->ReadResponseBody( - loader, out_buffer, bytes_to_read, PP_BlockUntilComplete()); + int bytes_read = + loader_interface->ReadResponseBody(loader.pp_resource(), + out_buffer, + bytes_to_read, + PP_BlockUntilComplete()); if (bytes_read == 0) { // This is not an error -- it may just be that we were trying to read @@ -550,4 +640,3 @@ Error MountNodeHttp::DownloadToBuffer(PP_Resource loader, } } // namespace nacl_io - diff --git a/native_client_sdk/src/libraries/nacl_io/mount_node_http.h b/native_client_sdk/src/libraries/nacl_io/mount_node_http.h index 9ea5f14..9ad2ab2 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_node_http.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_node_http.h @@ -43,11 +43,12 @@ class MountNodeHttp : public MountNode { MountNodeHttp(Mount* mount, const std::string& url, bool cache_content); private: + Error GetStat_Locked(struct stat* stat); Error OpenUrl(const char* method, StringMap_t* request_headers, - PP_Resource* out_loader, - PP_Resource* out_request, - PP_Resource* out_response, + ScopedResource* out_loader, + ScopedResource* out_request, + ScopedResource* out_response, int32_t* out_statuscode, StringMap_t* out_response_headers); @@ -60,10 +61,26 @@ class MountNodeHttp : public MountNode { void* buf, size_t count, int* out_bytes); - Error DownloadToBuffer(PP_Resource loader, - void* buf, - int count, - int* out_bytes); + + Error DownloadToTemp(int* out_bytes); + + // Read as much as possible from |loader|, using |buffer_| as a scratch area. + Error ReadEntireResponseToTemp(const ScopedResource& loader, int* out_bytes); + // Read as much as possible from |loader|, storing the result in + // |cached_data_|. + Error ReadEntireResponseToCache(const ScopedResource& loader, int* out_bytes); + + // Read up to |count| bytes from |loader|, using |buffer_| as a scratch area. + Error ReadResponseToTemp(const ScopedResource& loader, + int count, + int* out_bytes); + + // Read up to |count| bytes from |loader|, and store the result in |buf|, + // which is assumed to have |count| bytes free. + Error ReadResponseToBuffer(const ScopedResource& loader, + void* buf, + int count, + int* out_bytes); std::string url_; std::vector<char> buffer_; diff --git a/native_client_sdk/src/libraries/nacl_io/pepper_interface.cc b/native_client_sdk/src/libraries/nacl_io/pepper_interface.cc index 84e8305..46591a5 100644 --- a/native_client_sdk/src/libraries/nacl_io/pepper_interface.cc +++ b/native_client_sdk/src/libraries/nacl_io/pepper_interface.cc @@ -16,16 +16,24 @@ void PepperInterface::ReleaseResource(PP_Resource resource) { GetCoreInterface()->ReleaseResource(resource); } +ScopedResource::ScopedResource(PepperInterface* ppapi) + : ppapi_(ppapi), resource_(0) {} + ScopedResource::ScopedResource(PepperInterface* ppapi, PP_Resource resource) - : ppapi_(ppapi), - resource_(resource) { -} + : ppapi_(ppapi), resource_(resource) {} ScopedResource::~ScopedResource() { if (resource_) ppapi_->ReleaseResource(resource_); } +void ScopedResource::Reset(PP_Resource resource) { + if (resource_) + ppapi_->ReleaseResource(resource_); + + resource_ = resource; +} + PP_Resource ScopedResource::Release() { PP_Resource result = resource_; resource_ = 0; diff --git a/native_client_sdk/src/libraries/nacl_io/pepper_interface.h b/native_client_sdk/src/libraries/nacl_io/pepper_interface.h index ac8f6ba..e311229 100644 --- a/native_client_sdk/src/libraries/nacl_io/pepper_interface.h +++ b/native_client_sdk/src/libraries/nacl_io/pepper_interface.h @@ -130,11 +130,16 @@ class PepperInterface { class ScopedResource { public: - // Does not AddRef by default. + // Does not AddRef. + explicit ScopedResource(PepperInterface* ppapi); ScopedResource(PepperInterface* ppapi, PP_Resource resource); ~ScopedResource(); - PP_Resource pp_resource() { return resource_; } + PP_Resource pp_resource() const { return resource_; } + + // Set a new resource, releasing the old one. Does not AddRef the new + // resource. + void Reset(PP_Resource resource); // Return the resource without decrementing its refcount. PP_Resource Release(); diff --git a/native_client_sdk/src/tests/nacl_io_test/fake_pepper_interface_url_loader.cc b/native_client_sdk/src/tests/nacl_io_test/fake_pepper_interface_url_loader.cc index 1b6b92c..f0dc472 100644 --- a/native_client_sdk/src/tests/nacl_io_test/fake_pepper_interface_url_loader.cc +++ b/native_client_sdk/src/tests/nacl_io_test/fake_pepper_interface_url_loader.cc @@ -116,6 +116,70 @@ int32_t RunCompletionCallback(PP_CompletionCallback* callback, int32_t result) { return result; } +void HandleContentLength(FakeURLLoaderResource* loader, + FakeURLResponseInfoResource* response, + FakeURLLoaderEntity* entity) { + size_t content_length = entity->body().size(); + if (!loader->server->send_content_length()) + return; + + std::ostringstream ss; + ss << "Content-Length: " << content_length << "\n"; + response->headers += ss.str(); +} + +void HandlePartial(FakeURLLoaderResource* loader, + FakeURLRequestInfoResource* request, + FakeURLResponseInfoResource* response, + FakeURLLoaderEntity* entity) { + if (!loader->server->allow_partial()) + return; + + // Read the RFC on byte ranges for more info: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 + std::string range; + if (!GetHeaderValue(request->headers, "Range", &range)) + return; + + // We don't support all range requests, just bytes=<num>-<num> + unsigned lo; + unsigned hi; + if (sscanf(range.c_str(), "bytes=%u-%u", &lo, &hi) != 2) { + // Couldn't parse the range value. + return; + } + + size_t content_length = entity->body().size(); + if (lo > content_length) { + // Trying to start reading past the end of the entity is + // unsatisfiable. + response->status_code = 416; // Request range not satisfiable. + return; + } + + // Clamp the hi value to the content length. + if (hi >= content_length) + hi = content_length - 1; + + if (lo > hi) { + // Bad range, ignore it and return the full result. + return; + } + + // The range is a closed interval; e.g. 0-10 is 11 bytes. We'll + // store it as a half-open interval instead--it's more natural + // in C that way. + loader->read_offset = lo; + loader->read_end = hi + 1; + + // Also add a "Content-Range" response header. + std::ostringstream ss; + ss << "Content-Range: " << lo << "-" << hi << "/" << content_length << "\n"; + response->headers += ss.str(); + + response->status_code = 206; // Partial content +} + } // namespace FakeURLLoaderEntity::FakeURLLoaderEntity(const std::string& body) @@ -193,95 +257,64 @@ PP_Resource FakeURLLoaderInterface::Create(PP_Instance instance) { } int32_t FakeURLLoaderInterface::Open(PP_Resource loader, - PP_Resource request_info, + PP_Resource request, PP_CompletionCallback callback) { FakeURLLoaderResource* loader_resource = core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader); if (loader_resource == NULL) return PP_ERROR_BADRESOURCE; - FakeURLRequestInfoResource* request_info_resource = + FakeURLRequestInfoResource* request_resource = core_interface_->resource_manager()->Get<FakeURLRequestInfoResource>( - request_info); - if (request_info_resource == NULL) + request); + if (request_resource == NULL) return PP_ERROR_BADRESOURCE; // Create a response resource. - FakeURLResponseInfoResource* response = new FakeURLResponseInfoResource; + FakeURLResponseInfoResource* response_resource = + new FakeURLResponseInfoResource; loader_resource->response = CREATE_RESOURCE(core_interface_->resource_manager(), FakeURLResponseInfoResource, - response); + response_resource); loader_resource->entity = NULL; loader_resource->read_offset = 0; loader_resource->read_end = 0; // Get the URL from the request info. - std::string url = request_info_resource->url; - std::string method = request_info_resource->method; + std::string url = request_resource->url; + std::string method = request_resource->method; - response->url = url; + response_resource->url = url; // TODO(binji): allow this to be set? - response->headers.clear(); + response_resource->headers.clear(); // Check the error map first, to see if this URL should produce an error. EXPECT_TRUE(NULL != loader_resource->server); int http_status_code = loader_resource->server->GetError(url); if (http_status_code != 0) { // Got an error, return that in the response. - response->status_code = http_status_code; - } else { - // Look up the URL in the loader resource entity map. - FakeURLLoaderEntity* entity = loader_resource->server->GetEntity(url); - response->status_code = entity ? 200 : 404; - - if (method == "GET") { - loader_resource->entity = entity; - } else if (method == "HEAD") { - // Do nothing, we only set the status code. - } else { - response->status_code = 405; // Method not allowed. - } + response_resource->status_code = http_status_code; + return RunCompletionCallback(&callback, PP_OK); + } - if (entity != NULL) { - size_t content_length = entity->body().size(); - loader_resource->read_end = content_length; - - if (loader_resource->server->send_content_length()) { - std::ostringstream ss; - ss << "Content-Length: " << content_length << "\n"; - response->headers += ss.str(); - } - - if (loader_resource->server->allow_partial()) { - std::string headers = request_info_resource->headers; - std::string range; - if (GetHeaderValue(headers, "Range", &range)) { - // We don't support all range requests, just bytes=<num>-<num> - int lo; - int hi; - if (sscanf(range.c_str(), "bytes=%d-%d", &lo, &hi) == 2) { - // The range is a closed interval; e.g. 0-10 is 11 bytes. We'll - // store it as a half-open interval instead--it's more natural in - // C that way. - loader_resource->read_offset = lo; - loader_resource->read_end = hi + 1; - - // Also add a "Content-Range" response header. - std::ostringstream ss; - ss << "Content-Range: " - << lo << "-" << hi << "/" << content_length << "\n"; - response->headers += ss.str(); - - response->status_code = 206; // Partial content - } else { - // Couldn't parse the range. - response->status_code = 416; // Request range not satisfiable. - } - } - } - } + // Look up the URL in the loader resource entity map. + FakeURLLoaderEntity* entity = loader_resource->server->GetEntity(url); + response_resource->status_code = entity ? 200 : 404; + + if (method == "GET") { + loader_resource->entity = entity; + } else if (method != "HEAD") { + response_resource->status_code = 405; // Method not allowed. + return RunCompletionCallback(&callback, PP_OK); + } + + if (entity != NULL) { + size_t content_length = entity->body().size(); + loader_resource->read_end = content_length; + HandleContentLength(loader_resource, response_resource, entity); + HandlePartial(loader_resource, request_resource, response_resource, entity); } // Call the callback. diff --git a/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc b/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc index 79a2fcb..bc7655a 100644 --- a/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc +++ b/native_client_sdk/src/tests/nacl_io_test/mount_http_test.cc @@ -34,39 +34,46 @@ class MountHttpForTesting : public MountHttp { using MountHttp::FindOrCreateDir; }; -class MountHttpTest : public ::testing::Test { - public: - MountHttpTest(); - - protected: - FakePepperInterfaceURLLoader ppapi_; - MountHttpForTesting mnt_; +enum { + kStringMapParamCacheNone = 0, + kStringMapParamCacheContent = 1, + kStringMapParamCacheStat = 2, + kStringMapParamCacheContentStat = + kStringMapParamCacheContent | kStringMapParamCacheStat, }; +typedef uint32_t StringMapParam; -MountHttpTest::MountHttpTest() : mnt_(StringMap_t(), &ppapi_) {} - -StringMap_t StringMap_NoCache() { +StringMap_t MakeStringMap(StringMapParam param) { StringMap_t smap; - smap["cache_content"] = "false"; + if (param & kStringMapParamCacheContent) + smap["cache_content"] = "true"; + else + smap["cache_content"] = "false"; + + if (param & kStringMapParamCacheStat) + smap["cache_stat"] = "true"; + else + smap["cache_stat"] = "false"; return smap; } -class MountHttpNoCacheTest : public ::testing::Test { +class MountHttpTest : public ::testing::TestWithParam<StringMapParam> { public: - MountHttpNoCacheTest(); + MountHttpTest(); protected: FakePepperInterfaceURLLoader ppapi_; MountHttpForTesting mnt_; }; -MountHttpNoCacheTest::MountHttpNoCacheTest() : - mnt_(StringMap_NoCache(), &ppapi_) {} +MountHttpTest::MountHttpTest() : + mnt_(MakeStringMap(GetParam()), &ppapi_) { +} } // namespace -TEST_F(MountHttpTest, Access) { +TEST_P(MountHttpTest, Access) { ASSERT_TRUE(ppapi_.server_template()->AddEntity("foo", "", NULL)); ASSERT_EQ(0, mnt_.Access(Path("/foo"), R_OK)); @@ -75,34 +82,74 @@ TEST_F(MountHttpTest, Access) { ASSERT_EQ(ENOENT, mnt_.Access(Path("/bar"), F_OK)); } -TEST_F(MountHttpTest, Read) { - const char contents[] = "contents"; +TEST_P(MountHttpTest, OpenAndCloseServerError) { + EXPECT_TRUE(ppapi_.server_template()->AddError("file", 500)); + + ScopedMountNode node; + ASSERT_EQ(ENOENT, mnt_.Open(Path("/file"), O_RDONLY, &node)); +} + +TEST_P(MountHttpTest, ReadPartial) { + const char contents[] = "0123456789abcdefg"; ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); + ppapi_.server_template()->set_allow_partial(true); + + int result_bytes = 0; + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); ScopedMountNode node; ASSERT_EQ(0, mnt_.Open(Path("/file"), O_RDONLY, &node)); + HandleAttr attr; + EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); + EXPECT_EQ(sizeof(buf) - 1, result_bytes); + EXPECT_STREQ("012345678", &buf[0]); + + // Read is clamped when reading past the end of the file. + attr.offs = 10; + ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); + ASSERT_EQ(strlen("abcdefg"), result_bytes); + buf[result_bytes] = 0; + EXPECT_STREQ("abcdefg", &buf[0]); + + // Read nothing when starting past the end of the file. + attr.offs = 100; + EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes)); + EXPECT_EQ(0, result_bytes); +} + +TEST_P(MountHttpTest, ReadPartialNoServerSupport) { + const char contents[] = "0123456789abcdefg"; + ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); + ppapi_.server_template()->set_allow_partial(false); - char buffer[10] = {0}; - int bytes_read = 0; + int result_bytes = 0; + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ScopedMountNode node; + ASSERT_EQ(0, mnt_.Open(Path("/file"), O_RDONLY, &node)); HandleAttr attr; - EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); - EXPECT_EQ(strlen(contents), bytes_read); - EXPECT_STREQ(contents, buffer); + EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); + EXPECT_EQ(sizeof(buf) - 1, result_bytes); + EXPECT_STREQ("012345678", &buf[0]); - // Read nothing past the end of the file. + // Read is clamped when reading past the end of the file. + attr.offs = 10; + ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); + ASSERT_EQ(strlen("abcdefg"), result_bytes); + buf[result_bytes] = 0; + EXPECT_STREQ("abcdefg", &buf[0]); + + // Read nothing when starting past the end of the file. attr.offs = 100; - EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); - EXPECT_EQ(0, bytes_read); - - // Read part of the data. - attr.offs = 4; - EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); - ASSERT_EQ(strlen(contents) - 4, bytes_read); - buffer[bytes_read] = 0; - EXPECT_STREQ("ents", buffer); + EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes)); + EXPECT_EQ(0, result_bytes); } -TEST_F(MountHttpTest, Write) { +TEST_P(MountHttpTest, Write) { const char contents[] = "contents"; ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); @@ -117,7 +164,7 @@ TEST_F(MountHttpTest, Write) { EXPECT_EQ(0, bytes_written); } -TEST_F(MountHttpTest, GetStat) { +TEST_P(MountHttpTest, GetStat) { const char contents[] = "contents"; ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); @@ -135,7 +182,7 @@ TEST_F(MountHttpTest, GetStat) { EXPECT_EQ(0, statbuf.st_mtime); } -TEST_F(MountHttpTest, FTruncate) { +TEST_P(MountHttpTest, FTruncate) { const char contents[] = "contents"; ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); @@ -144,6 +191,15 @@ TEST_F(MountHttpTest, FTruncate) { EXPECT_EQ(EACCES, node->FTruncate(4)); } +// Instantiate the above tests for all caching types. +INSTANTIATE_TEST_CASE_P( + Default, + MountHttpTest, + ::testing::Values((uint32_t)kStringMapParamCacheNone, + (uint32_t)kStringMapParamCacheContent, + (uint32_t)kStringMapParamCacheStat, + (uint32_t)kStringMapParamCacheContentStat)); + TEST(MountHttpDirTest, Mkdir) { StringMap_t args; MountHttpForTesting mnt(args, NULL); @@ -241,89 +297,3 @@ TEST(MountHttpDirTest, ParseManifest) { EXPECT_EQ(234, sbar.st_size); EXPECT_EQ(S_IFREG | S_IRALL | S_IWALL, sbar.st_mode); } - -TEST_F(MountHttpNoCacheTest, OpenAndClose) { - const char contents[] = "contents"; - ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); - - ScopedMountNode node; - ASSERT_EQ(0, mnt_.Open(Path("/file"), O_RDONLY, &node)); -} - -TEST_F(MountHttpNoCacheTest, OpenAndCloseNotFound) { - ScopedMountNode node; - ASSERT_EQ(ENOENT, mnt_.Open(Path("/file"), O_RDONLY, &node)); -} - -TEST_F(MountHttpNoCacheTest, OpenAndCloseServerError) { - ASSERT_TRUE(ppapi_.server_template()->AddError("file", 500)); - - ScopedMountNode node; - ASSERT_EQ(ENOENT, mnt_.Open(Path("/file"), O_RDONLY, &node)); -} - -TEST_F(MountHttpNoCacheTest, GetSize) { - const char contents[] = "contents"; - ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); - ppapi_.server_template()->set_send_content_length(true); - - ScopedMountNode node; - struct stat statbuf; - ASSERT_EQ(0, mnt_.Open(Path("/file"), O_RDONLY, &node)); - EXPECT_EQ(0, node->GetStat(&statbuf)); - EXPECT_EQ(strlen(contents), statbuf.st_size); -} - -TEST_F(MountHttpNoCacheTest, Access) { - ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", "", NULL)); - - ASSERT_EQ(0, mnt_.Access(Path("/file"), R_OK)); - ASSERT_EQ(EACCES, mnt_.Access(Path("/file"), W_OK)); - ASSERT_EQ(ENOENT, mnt_.Access(Path("/bar"), R_OK)); -} - -TEST_F(MountHttpNoCacheTest, ReadPartial) { - const char contents[] = "0123456789abcdefghi"; - ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); - ppapi_.server_template()->set_allow_partial(true); - - int result_bytes = 0; - - char buf[10]; - memset(&buf[0], 0, sizeof(buf)); - - ScopedMountNode node; - ASSERT_EQ(0, mnt_.Open(Path("/file"), O_RDONLY, &node)); - HandleAttr attr; - EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); - EXPECT_EQ(sizeof(buf) - 1, result_bytes); - EXPECT_STREQ("012345678", &buf[0]); - - attr.offs = 10; - EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); - EXPECT_EQ(sizeof(buf) - 1, result_bytes); - EXPECT_STREQ("abcdefghi", &buf[0]); -} - -TEST_F(MountHttpNoCacheTest, ReadPartialNoServerSupport) { - const char contents[] = "0123456789abcdefghi"; - ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); - ppapi_.server_template()->set_allow_partial(false); - - int result_bytes = 0; - - char buf[10]; - memset(&buf[0], 0, sizeof(buf)); - - ScopedMountNode node; - ASSERT_EQ(0, mnt_.Open(Path("/file"), O_RDONLY, &node)); - HandleAttr attr; - EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); - EXPECT_EQ(sizeof(buf) - 1, result_bytes); - EXPECT_STREQ("012345678", &buf[0]); - - attr.offs = 10; - EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); - EXPECT_EQ(sizeof(buf) - 1, result_bytes); - EXPECT_STREQ("abcdefghi", &buf[0]); -} |