diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-01 18:38:26 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-01 18:38:26 +0000 |
commit | 60c97c7102f43ac23277db385e0cccd9d25aa1fa (patch) | |
tree | 39ecb0c274aedceb9f88e780bfadcf78b2d9ec30 /native_client_sdk | |
parent | a44f10db94596a2bb5f0c03487bb98bb7b1696cc (diff) | |
download | chromium_src-60c97c7102f43ac23277db385e0cccd9d25aa1fa.zip chromium_src-60c97c7102f43ac23277db385e0cccd9d25aa1fa.tar.gz chromium_src-60c97c7102f43ac23277db385e0cccd9d25aa1fa.tar.bz2 |
[NaCl SDK] nacl_io http mount caches content by default.
BUG=175337
R=sbc@chromium.org,noelallen@chromium.org
NOTRY=true
Review URL: https://chromiumcodereview.appspot.com/12354002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@185557 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
8 files changed, 654 insertions, 199 deletions
diff --git a/native_client_sdk/src/libraries/nacl_io/mount_http.cc b/native_client_sdk/src/libraries/nacl_io/mount_http.cc index e387dba..5786422 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_http.cc +++ b/native_client_sdk/src/libraries/nacl_io/mount_http.cc @@ -23,8 +23,10 @@ #endif +namespace { + typedef std::vector<char *> StringList_t; -static size_t SplitString(char *str, const char *delim, StringList_t* list) { +size_t SplitString(char *str, const char *delim, StringList_t* list) { char *item = strtok(str, delim); list->clear(); @@ -40,9 +42,9 @@ static size_t SplitString(char *str, const char *delim, StringList_t* list) { // 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. -static const size_t MAX_READ_BUFFER_SIZE = 64 * 1024; -static const int32_t STATUSCODE_OK = 200; -static const int32_t STATUSCODE_PARTIAL_CONTENT = 206; +const size_t MAX_READ_BUFFER_SIZE = 64 * 1024; +const int32_t STATUSCODE_OK = 200; +const int32_t STATUSCODE_PARTIAL_CONTENT = 206; std::string NormalizeHeaderKey(const std::string& s) { // Capitalize the first letter and any letter following a hyphen: @@ -149,6 +151,9 @@ bool ParseContentRange(const StringMap_t& headers, size_t* read_start, return false; } +} // namespace + + class MountNodeHttp : public MountNode { public: virtual int FSync(); @@ -160,7 +165,7 @@ class MountNodeHttp : public MountNode { virtual size_t GetSize(); protected: - MountNodeHttp(Mount* mount, const std::string& url); + MountNodeHttp(Mount* mount, const std::string& url, bool cache_content); private: bool OpenUrl(const char* method, @@ -171,8 +176,16 @@ class MountNodeHttp : public MountNode { int32_t* out_statuscode, StringMap_t* out_response_headers); + int DownloadToCache(); + int ReadPartialFromCache(size_t offs, void* buf, size_t count); + int DownloadPartial(size_t offs, void* buf, size_t count); + int DownloadToBuffer(PP_Resource loader, void* buf, size_t count); + std::string url_; std::vector<char> buffer_; + + bool cache_content_; + std::vector<char> cached_data_; friend class ::MountHttp; }; @@ -192,7 +205,7 @@ int MountNodeHttp::GetStat(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 (stat_.st_size == 0 || !mount->allow_stat_cache_) { + if (stat_.st_size == 0 || !mount->cache_stat_) { StringMap_t headers; PP_Resource loader; PP_Resource request; @@ -230,116 +243,16 @@ int MountNodeHttp::GetStat(struct stat* stat) { int MountNodeHttp::Read(size_t offs, void* buf, size_t count) { AutoLock lock(&lock_); - StringMap_t headers; - - char buffer[100]; - // Range request is inclusive: 0-99 returns 100 bytes. - snprintf(&buffer[0], sizeof(buffer), "bytes=%"PRIuS"-%"PRIuS, - offs, offs + count - 1); - headers["Range"] = buffer; - - PP_Resource loader; - PP_Resource request; - PP_Resource response; - int32_t statuscode; - StringMap_t response_headers; - if (!OpenUrl("GET", &headers, &loader, &request, &response, &statuscode, - &response_headers)) { - // errno is already set by OpenUrl. - return 0; - } - - PepperInterface* ppapi = mount_->ppapi(); - ScopedResource scoped_loader(ppapi, loader); - ScopedResource scoped_request(ppapi, request); - ScopedResource scoped_response(ppapi, response); - - size_t read_start = 0; - if (statuscode == STATUSCODE_OK) { - // No partial result, read everything starting from the part we care about. - size_t content_length; - if (ParseContentLength(response_headers, &content_length)) { - if (offs >= content_length) { - errno = EINVAL; - return 0; - } - - // Clamp count, if trying to read past the end of the file. - if (offs + count > content_length) { - count = content_length - offs; - } + if (cache_content_) { + if (cached_data_.empty()) { + if (DownloadToCache() < 0) + return -1; } - } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) { - // Determine from the headers where we are reading. - size_t read_end; - size_t entity_length; - if (ParseContentRange(response_headers, &read_start, &read_end, - &entity_length)) { - if (read_start > offs || read_start > read_end) { - // Shouldn't happen. - errno = EINVAL; - return 0; - } - // Clamp count, if trying to read past the end of the file. - count = std::min(read_end - read_start, count); - } else { - // Partial Content without Content-Range. Assume that the server gave us - // exactly what we asked for. This can happen even when the server - // returns 200 -- the cache may return 206 in this case, but not modify - // the headers. - read_start = offs; - } - } - - URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface(); - - size_t bytes_to_read; - int32_t bytes_read; - while (read_start < offs) { - if (!buffer_.size()) { - buffer_.resize(std::min(offs - read_start, MAX_READ_BUFFER_SIZE)); - } - - // We aren't yet at the location where we want to start reading. Read into - // our dummy buffer until then. - bytes_to_read = std::min(offs - read_start, buffer_.size()); - bytes_read = loader_interface->ReadResponseBody( - loader, buffer_.data(), bytes_to_read, PP_BlockUntilComplete()); - - if (bytes_read < 0) { - errno = PPErrorToErrno(bytes_read); - return 0; - } - - assert(bytes_read <= bytes_to_read); - read_start += bytes_read; - } - - // At the read start, now we can read into the correct buffer. - char* out_buffer = static_cast<char*>(buf); - bytes_to_read = count; - while (bytes_to_read > 0) { - bytes_read = loader_interface->ReadResponseBody( - loader, 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 - // more data than exists. - return count - bytes_to_read; - } - - if (bytes_read < 0) { - errno = PPErrorToErrno(bytes_read); - return count - bytes_to_read; - } - - assert(bytes_read <= bytes_to_read); - bytes_to_read -= bytes_read; - out_buffer += bytes_read; + return ReadPartialFromCache(offs, buf, count); } - return count; + return DownloadPartial(offs, buf, count); } int MountNodeHttp::Truncate(size_t size) { @@ -359,9 +272,11 @@ size_t MountNodeHttp::GetSize() { return stat_.st_size; } -MountNodeHttp::MountNodeHttp(Mount* mount, const std::string& url) +MountNodeHttp::MountNodeHttp(Mount* mount, const std::string& url, + bool cache_content) : MountNode(mount), - url_(url) { + url_(url), + cache_content_(cache_content) { } bool MountNodeHttp::OpenUrl(const char* method, @@ -448,6 +363,184 @@ bool MountNodeHttp::OpenUrl(const char* method, return true; } +int MountNodeHttp::DownloadToCache() { + StringMap_t headers; + PP_Resource loader; + PP_Resource request; + PP_Resource response; + int32_t statuscode; + StringMap_t response_headers; + if (!OpenUrl("GET", &headers, &loader, &request, &response, &statuscode, + &response_headers)) { + // errno is already set by OpenUrl. + return -1; + } + + 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 = DownloadToBuffer(loader, cached_data_.data(), + content_length); + if (real_size < 0) + return -1; + + stat_.st_size = real_size; + cached_data_.resize(real_size); + return real_size; + } + + // We don't know how big the file is. Read in chunks. + cached_data_.resize(MAX_READ_BUFFER_SIZE); + char* buf = cached_data_.data(); + size_t total_bytes_read = 0; + size_t bytes_to_read = MAX_READ_BUFFER_SIZE; + while (true) { + int bytes_read = DownloadToBuffer(loader, buf, bytes_to_read); + if (bytes_read < 0) + return -1; + + total_bytes_read += bytes_read; + + if (bytes_read < bytes_to_read) { + stat_.st_size = total_bytes_read; + cached_data_.resize(total_bytes_read); + return total_bytes_read; + } + + buf += bytes_read; + cached_data_.resize(total_bytes_read + bytes_to_read); + } +} + +int MountNodeHttp::ReadPartialFromCache(size_t offs, void* buf, size_t count) { + if (offs >= cached_data_.size()) { + errno = EINVAL; + return -1; + } + + count = std::min(count, cached_data_.size() - offs); + memcpy(buf, &cached_data_.data()[offs], count); + return count; +} + +int MountNodeHttp::DownloadPartial(size_t offs, void* buf, size_t count) { + StringMap_t headers; + + char buffer[100]; + // Range request is inclusive: 0-99 returns 100 bytes. + snprintf(&buffer[0], sizeof(buffer), "bytes=%"PRIuS"-%"PRIuS, + offs, offs + count - 1); + headers["Range"] = buffer; + + PP_Resource loader; + PP_Resource request; + PP_Resource response; + int32_t statuscode; + StringMap_t response_headers; + if (!OpenUrl("GET", &headers, &loader, &request, &response, &statuscode, + &response_headers)) { + // errno is already set by OpenUrl. + return -1; + } + + PepperInterface* ppapi = mount_->ppapi(); + ScopedResource scoped_loader(ppapi, loader); + ScopedResource scoped_request(ppapi, request); + ScopedResource scoped_response(ppapi, response); + + size_t read_start = 0; + if (statuscode == STATUSCODE_OK) { + // No partial result, read everything starting from the part we care about. + size_t content_length; + if (ParseContentLength(response_headers, &content_length)) { + if (offs >= content_length) { + errno = EINVAL; + return 0; + } + + // Clamp count, if trying to read past the end of the file. + if (offs + count > content_length) { + count = content_length - offs; + } + } + } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) { + // Determine from the headers where we are reading. + size_t read_end; + size_t entity_length; + if (ParseContentRange(response_headers, &read_start, &read_end, + &entity_length)) { + if (read_start > offs || read_start > read_end) { + // If this error occurs, the server is returning bogus values. + errno = EINVAL; + return -1; + } + + // Clamp count, if trying to read past the end of the file. + count = std::min(read_end - read_start, count); + } else { + // Partial Content without Content-Range. Assume that the server gave us + // exactly what we asked for. This can happen even when the server + // returns 200 -- the cache may return 206 in this case, but not modify + // the headers. + read_start = offs; + } + } + + if (read_start < 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 = 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 = DownloadToBuffer(loader, buffer_.data(), + buffer_.size()); + if (bytes_read < 0) + return -1; + + bytes_to_read -= bytes_read; + } + } + + return DownloadToBuffer(loader, buf, count); +} + +int MountNodeHttp::DownloadToBuffer(PP_Resource loader, void* buf, + size_t count) { + PepperInterface* ppapi = mount_->ppapi(); + URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface(); + + char* out_buffer = static_cast<char*>(buf); + size_t bytes_to_read = count; + while (bytes_to_read > 0) { + int32_t bytes_read = loader_interface->ReadResponseBody( + loader, 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 + // more data than exists. + return count - bytes_to_read; + } + + if (bytes_read < 0) { + errno = PPErrorToErrno(bytes_read); + return -1; + } + + assert(bytes_read <= bytes_to_read); + bytes_to_read -= bytes_read; + out_buffer += bytes_read; + } + + return count; +} + MountNode *MountHttp::Open(const Path& path, int mode) { assert(url_root_.empty() || url_root_[url_root_.length() - 1] == '/'); @@ -461,7 +554,7 @@ MountNode *MountHttp::Open(const Path& path, int mode) { path.Range(1, path.Size()) : path.Join()); - MountNodeHttp* node = new MountNodeHttp(this, url); + MountNodeHttp* node = new MountNodeHttp(this, url, cache_content_); if (!node->Init(mode) || (0 != node->GetStat(NULL))) { node->Release(); return NULL; @@ -542,7 +635,8 @@ PP_Resource MountHttp::MakeUrlRequestInfo( MountHttp::MountHttp() : allow_cors_(false), allow_credentials_(false), - allow_stat_cache_(true) { + cache_stat_(true), + cache_content_(true) { } bool MountHttp::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { @@ -568,8 +662,10 @@ bool MountHttp::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { allow_cors_ = iter->second == "true"; } else if (iter->first == "allow_credentials") { allow_credentials_ = iter->second == "true"; - } else if (iter->first == "allow_stat_cache") { - allow_stat_cache_ = iter->second == "true"; + } else if (iter->first == "cache_stat") { + cache_stat_ = iter->second == "true"; + } else if (iter->first == "cache_content") { + cache_content_ = iter->second == "true"; } else { // Assume it is a header to pass to an HTTP request. headers_[NormalizeHeaderKey(iter->first)] = iter->second; @@ -652,7 +748,7 @@ bool MountHttp::ParseManifest(char *text) { path.Range(1, path.Size()) : path.Join()); - MountNode* node = new MountNodeHttp(this, url); + MountNode* node = new MountNodeHttp(this, url, cache_content_); node->Init(mode); node->stat_.st_size = atoi(lenstr); diff --git a/native_client_sdk/src/libraries/nacl_io/mount_http.h b/native_client_sdk/src/libraries/nacl_io/mount_http.h index eacfb2c..3a842a04 100644 --- a/native_client_sdk/src/libraries/nacl_io/mount_http.h +++ b/native_client_sdk/src/libraries/nacl_io/mount_http.h @@ -44,7 +44,8 @@ class MountHttp : public Mount { NodeMap_t node_cache_; bool allow_cors_; bool allow_credentials_; - bool allow_stat_cache_; + bool cache_stat_; + bool cache_content_; friend class Mount; friend class MountNodeHttp; diff --git a/native_client_sdk/src/libraries/nacl_io_test/example.dsc b/native_client_sdk/src/libraries/nacl_io_test/example.dsc index ea533d3..a47ed63 100644 --- a/native_client_sdk/src/libraries/nacl_io_test/example.dsc +++ b/native_client_sdk/src/libraries/nacl_io_test/example.dsc @@ -14,6 +14,7 @@ 'kernel_proxy_mock.h', 'kernel_proxy_test.cc', 'kernel_wrap_test.cc', + 'mock_util.h', 'module.cc', 'mount_node_test.cc', 'mount_html5fs_test.cc', diff --git a/native_client_sdk/src/libraries/nacl_io_test/example.js b/native_client_sdk/src/libraries/nacl_io_test/example.js index 4e7cb0a..2d93739 100644 --- a/native_client_sdk/src/libraries/nacl_io_test/example.js +++ b/native_client_sdk/src/libraries/nacl_io_test/example.js @@ -11,5 +11,14 @@ function moduleDidLoad() { function handleMessage(event) { var logEl = document.getElementById('log'); - logEl.innerHTML += event.data + '<br>'; + var msg = event.data; + + // Perform some basic escaping. + msg = msg.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + logEl.innerHTML += msg + '\n'; } diff --git a/native_client_sdk/src/libraries/nacl_io_test/index.html b/native_client_sdk/src/libraries/nacl_io_test/index.html index 4e47334..424f557 100644 --- a/native_client_sdk/src/libraries/nacl_io_test/index.html +++ b/native_client_sdk/src/libraries/nacl_io_test/index.html @@ -13,11 +13,11 @@ found in the LICENSE file. <script type="text/javascript" src="example.js"></script> </head> <body {{attrs}}> - <h1><TITLE></h1> + <h1>{{title}}</h1> <h2>Status: <code id="statusField">NO-STATUS</code></h2> <!-- The NaCl plugin will be embedded inside the element with id "listener". See common.js.--> <div id="listener"></div> - <div id="log"></div> + <pre id="log"></pre> </body> </html> diff --git a/native_client_sdk/src/libraries/nacl_io_test/mock_util.h b/native_client_sdk/src/libraries/nacl_io_test/mock_util.h new file mode 100644 index 0000000..84145bf --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_io_test/mock_util.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef LIBRARIES_NACL_IO_TEST_MOCK_UTIL_H_ +#define LIBRARIES_NACL_IO_TEST_MOCK_UTIL_H_ + +#include <gmock/gmock.h> +#include <ppapi/c/pp_completion_callback.h> +#include <ppapi/c/pp_var.h> + +ACTION_TEMPLATE(CallCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(result)) { + PP_CompletionCallback callback = std::tr1::get<k>(args); + if (callback.func) { + (*callback.func)(callback.user_data, result); + } + + // Dummy return value. + return 0; +} + +MATCHER_P(IsEqualToVar, var, "") { + if (arg.type != var.type) + return false; + + switch (arg.type) { + case PP_VARTYPE_BOOL: + return arg.value.as_bool == var.value.as_bool; + + case PP_VARTYPE_INT32: + return arg.value.as_int == var.value.as_int; + + case PP_VARTYPE_DOUBLE: + return arg.value.as_double == var.value.as_double; + + case PP_VARTYPE_STRING: + return arg.value.as_id == var.value.as_id; + + case PP_VARTYPE_UNDEFINED: + case PP_VARTYPE_NULL: + return true; + + case PP_VARTYPE_ARRAY: + case PP_VARTYPE_ARRAY_BUFFER: + case PP_VARTYPE_DICTIONARY: + case PP_VARTYPE_OBJECT: + default: + // Not supported. + return false; + } +} + +#endif // LIBRARIES_NACL_IO_TEST_MOCK_UTIL_H_ diff --git a/native_client_sdk/src/libraries/nacl_io_test/mount_html5fs_test.cc b/native_client_sdk/src/libraries/nacl_io_test/mount_html5fs_test.cc index 259d6ae..0814a10 100644 --- a/native_client_sdk/src/libraries/nacl_io_test/mount_html5fs_test.cc +++ b/native_client_sdk/src/libraries/nacl_io_test/mount_html5fs_test.cc @@ -9,6 +9,7 @@ #include <ppapi/c/pp_errors.h> #include <ppapi/c/pp_instance.h> +#include "mock_util.h" #include "nacl_io/mount_html5fs.h" #include "nacl_io/osdirent.h" #include "pepper_interface_mock.h" @@ -21,54 +22,9 @@ using ::testing::StrEq; namespace { -ACTION_TEMPLATE(CallCallback, - HAS_1_TEMPLATE_PARAMS(int, k), - AND_1_VALUE_PARAMS(result)) { - PP_CompletionCallback callback = std::tr1::get<k>(args); - if (callback.func) { - (*callback.func)(callback.user_data, result); - } - - // Dummy return value. - return 0; -} - -MATCHER_P(IsEqualToVar, var, "") { - if (arg.type != var.type) - return false; - - switch (arg.type) { - case PP_VARTYPE_BOOL: - return arg.value.as_bool == var.value.as_bool; - - case PP_VARTYPE_INT32: - return arg.value.as_int == var.value.as_int; - - case PP_VARTYPE_DOUBLE: - return arg.value.as_double == var.value.as_double; - - case PP_VARTYPE_STRING: - return arg.value.as_id == var.value.as_id; - - case PP_VARTYPE_UNDEFINED: - case PP_VARTYPE_NULL: - return true; - - case PP_VARTYPE_ARRAY: - case PP_VARTYPE_ARRAY_BUFFER: - case PP_VARTYPE_DICTIONARY: - case PP_VARTYPE_OBJECT: - default: - // Not supported. - return false; - } -} - - class MountHtml5FsMock : public MountHtml5Fs { public: - explicit MountHtml5FsMock(StringMap_t map, PepperInterfaceMock* ppapi) - : MountHtml5Fs() { + MountHtml5FsMock(StringMap_t map, PepperInterfaceMock* ppapi) { Init(1, map, ppapi); } diff --git a/native_client_sdk/src/libraries/nacl_io_test/mount_http_test.cc b/native_client_sdk/src/libraries/nacl_io_test/mount_http_test.cc index edf35cb..f117093 100644 --- a/native_client_sdk/src/libraries/nacl_io_test/mount_http_test.cc +++ b/native_client_sdk/src/libraries/nacl_io_test/mount_http_test.cc @@ -11,6 +11,7 @@ #include <sys/stat.h> #include <sys/types.h> +#include "mock_util.h" #include "nacl_io/kernel_intercept.h" #include "nacl_io/mount_http.h" #include "nacl_io/mount_node_dir.h" @@ -19,71 +20,76 @@ using ::testing::_; using ::testing::DoAll; +using ::testing::Mock; using ::testing::Return; using ::testing::SetArgPointee; using ::testing::StrEq; -class MountHttpTest : public ::testing::Test { + +class MountHttpMock : public MountHttp { public: - MountHttpTest() { - ki_init(NULL); + MountHttpMock(StringMap_t map, PepperInterfaceMock* ppapi) { + EXPECT_TRUE(Init(1, map, ppapi)); } - ~MountHttpTest() { - ki_uninit(); + ~MountHttpMock() { + Destroy(); } + + NodeMap_t& GetMap() { return node_cache_; } + + using MountHttp::ParseManifest; + using MountHttp::FindOrCreateDir; }; -class MountHttpMock : public MountHttp { +class MountHttpTest : public ::testing::Test { public: - MountHttpMock() : MountHttp() {}; + MountHttpTest(); + ~MountHttpTest(); - virtual bool Init(int dev, StringMap_t& args, PepperInterface* ppapi) { - return MountHttp::Init(dev, args, ppapi); - } + protected: + PepperInterfaceMock ppapi_; + MountHttpMock* mnt_; - bool ParseManifest(char *txt) { - return MountHttp::ParseManifest(txt); - } + static const PP_Instance instance_ = 123; +}; - MountNodeDir* FindOrCreateDir(const Path& path) { - return MountHttp::FindOrCreateDir(path); - } +MountHttpTest::MountHttpTest() + : ppapi_(instance_), + mnt_(NULL) { +} - NodeMap_t& GetMap() { return node_cache_; } +MountHttpTest::~MountHttpTest() { + delete mnt_; +} - friend class MountHttpTest; -}; TEST_F(MountHttpTest, MountEmpty) { - MountHttpMock mnt; StringMap_t args; - - EXPECT_TRUE(mnt.Init(1, args, NULL)); + mnt_ = new MountHttpMock(args, &ppapi_); } TEST_F(MountHttpTest, ParseManifest) { - MountHttpMock mnt; StringMap_t args; + mnt_ = new MountHttpMock(args, &ppapi_); char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n"; - EXPECT_TRUE(mnt.Init(1, args, NULL)); - EXPECT_TRUE(mnt.ParseManifest(manifest)); + EXPECT_TRUE(mnt_->ParseManifest(manifest)); - MountNodeDir* root = mnt.FindOrCreateDir(Path("/")); + MountNodeDir* root = mnt_->FindOrCreateDir(Path("/")); EXPECT_EQ(2, root->ChildCount()); - MountNodeDir* dir = mnt.FindOrCreateDir(Path("/mydir")); + MountNodeDir* dir = mnt_->FindOrCreateDir(Path("/mydir")); EXPECT_EQ(1, dir->ChildCount()); - MountNode* node = mnt.GetMap()["/mydir/foo"]; + MountNode* node = mnt_->GetMap()["/mydir/foo"]; EXPECT_TRUE(node); EXPECT_EQ(123, node->GetSize()); // Since these files are cached thanks to the manifest, we can open them // without accessing the PPAPI URL API. - MountNode* foo = mnt.Open(Path("/mydir/foo"), O_RDONLY); - MountNode* bar = mnt.Open(Path("/thatdir/bar"), O_RDWR); + MountNode* foo = mnt_->Open(Path("/mydir/foo"), O_RDONLY); + MountNode* bar = mnt_->Open(Path("/thatdir/bar"), O_RDWR); struct stat sfoo; struct stat sbar; @@ -97,3 +103,333 @@ TEST_F(MountHttpTest, ParseManifest) { EXPECT_EQ(234, sbar.st_size); EXPECT_EQ(S_IFREG | S_IREAD | S_IWRITE, sbar.st_mode); } + + +class MountHttpNodeTest : public MountHttpTest { + public: + MountHttpNodeTest(); + virtual void TearDown(); + + void SetMountArgs(const StringMap_t& args); + void ExpectOpen(const char* method); + void ExpectHeaders(const char* headers); + void OpenNode(); + void SetResponse(int status_code, const char* headers); + void SetResponseBody(const char* body); + void ResetMocks(); + + protected: + MountHttpMock* mnt_; + MountNode* node_; + + VarInterfaceMock* var_; + URLLoaderInterfaceMock* loader_; + URLRequestInfoInterfaceMock* request_; + URLResponseInfoInterfaceMock* response_; + size_t response_body_offset_; + + static const char path_[]; + static const char rel_path_[]; + static const PP_Resource loader_resource_ = 235; + static const PP_Resource request_resource_ = 236; + static const PP_Resource response_resource_ = 237; +}; + +// static +const char MountHttpNodeTest::path_[] = "/foo"; +// static +const char MountHttpNodeTest::rel_path_[] = "foo"; + +MountHttpNodeTest::MountHttpNodeTest() + : mnt_(NULL), + node_(NULL) { +} + +static PP_Var MakeString(PP_Resource resource) { + PP_Var result = { PP_VARTYPE_STRING, 0, {PP_FALSE} }; + result.value.as_id = resource; + return result; +} + +void MountHttpNodeTest::SetMountArgs(const StringMap_t& args) { + mnt_ = new MountHttpMock(args, &ppapi_); +} + +void MountHttpNodeTest::ExpectOpen(const char* method) { + loader_ = ppapi_.GetURLLoaderInterface(); + request_ = ppapi_.GetURLRequestInfoInterface(); + response_ = ppapi_.GetURLResponseInfoInterface(); + var_ = ppapi_.GetVarInterface(); + + ON_CALL(*request_, SetProperty(request_resource_, _, _)) + .WillByDefault(Return(PP_TRUE)); + ON_CALL(*var_, VarFromUtf8(_, _)).WillByDefault(Return(PP_MakeUndefined())); + + EXPECT_CALL(*loader_, Create(instance_)).WillOnce(Return(loader_resource_)); + EXPECT_CALL(*request_, Create(instance_)).WillOnce(Return(request_resource_)); + + PP_Var var_head = MakeString(345); + PP_Var var_url = MakeString(346); + EXPECT_CALL(*var_, VarFromUtf8(StrEq(method), _)).WillOnce(Return(var_head)); + EXPECT_CALL(*var_, VarFromUtf8(StrEq(rel_path_), _)) + .WillOnce(Return(var_url)); + +#define EXPECT_SET_PROPERTY(NAME, VAR) \ + EXPECT_CALL(*request_, SetProperty(request_resource_, NAME, VAR)) + + EXPECT_SET_PROPERTY(PP_URLREQUESTPROPERTY_URL, IsEqualToVar(var_url)); + EXPECT_SET_PROPERTY(PP_URLREQUESTPROPERTY_METHOD, IsEqualToVar(var_head)); + EXPECT_SET_PROPERTY(PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS, _); + EXPECT_SET_PROPERTY(PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS, _); + +#undef EXPECT_SET_PROPERTY + + EXPECT_CALL(*loader_, Open(loader_resource_, request_resource_, _)) + .WillOnce(CallCallback<2>(int32_t(PP_OK))); + EXPECT_CALL(*loader_, GetResponseInfo(loader_resource_)) + .WillOnce(Return(response_resource_)); + + EXPECT_CALL(ppapi_, ReleaseResource(loader_resource_)); + EXPECT_CALL(ppapi_, ReleaseResource(request_resource_)); + EXPECT_CALL(ppapi_, ReleaseResource(response_resource_)); +} + +void MountHttpNodeTest::ExpectHeaders(const char* headers) { + PP_Var var_headers = MakeString(347); + var_ = ppapi_.GetVarInterface(); + EXPECT_CALL(*var_, VarFromUtf8(StrEq(headers), _)) + .WillOnce(Return(var_headers)); + + EXPECT_CALL(*request_, SetProperty(request_resource_, + PP_URLREQUESTPROPERTY_HEADERS, + IsEqualToVar(var_headers))).Times(1); +} + +void MountHttpNodeTest::SetResponse(int status_code, const char* headers) { + ON_CALL(*response_, GetProperty(response_resource_, _)) + .WillByDefault(Return(PP_MakeUndefined())); + + PP_Var var_headers = MakeString(348); + EXPECT_CALL(*response_, + GetProperty(response_resource_, + PP_URLRESPONSEPROPERTY_STATUSCODE)) + .WillOnce(Return(PP_MakeInt32(status_code))); + EXPECT_CALL(*response_, + GetProperty(response_resource_, PP_URLRESPONSEPROPERTY_HEADERS)) + .WillOnce(Return(var_headers)); + EXPECT_CALL(*var_, VarToUtf8(IsEqualToVar(var_headers), _)) + .WillOnce(DoAll(SetArgPointee<1>(strlen(headers)), + Return(headers))); +} + +ACTION_P3(ReadResponseBodyAction, offset, body, body_length) { + char* buf = static_cast<char*>(arg1); + size_t read_length = arg2; + PP_CompletionCallback callback = arg3; + if (*offset >= body_length) + return 0; + + read_length = std::min(read_length, body_length - *offset); + memcpy(buf, body + *offset, read_length); + *offset += read_length; + + // Also call the callback. + if (callback.func) + (*callback.func)(callback.user_data, PP_OK); + + return read_length; +} + +void MountHttpNodeTest::SetResponseBody(const char* body) { + response_body_offset_ = 0; + EXPECT_CALL(*loader_, ReadResponseBody(loader_resource_, _, _, _)) + .WillRepeatedly(ReadResponseBodyAction( + &response_body_offset_, body, strlen(body))); +} + +void MountHttpNodeTest::OpenNode() { + node_ = mnt_->Open(Path(path_), O_RDONLY); + ASSERT_NE((MountNode*)NULL, node_); +} + +void MountHttpNodeTest::ResetMocks() { + Mock::VerifyAndClearExpectations(&ppapi_); + Mock::VerifyAndClearExpectations(loader_); + Mock::VerifyAndClearExpectations(request_); + Mock::VerifyAndClearExpectations(response_); + Mock::VerifyAndClearExpectations(var_); +} + +void MountHttpNodeTest::TearDown() { + if (node_) + mnt_->ReleaseNode(node_); + delete mnt_; +} + +TEST_F(MountHttpNodeTest, OpenAndClose) { + SetMountArgs(StringMap_t()); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, ""); + OpenNode(); +} + +TEST_F(MountHttpNodeTest, ReadCached) { + SetMountArgs(StringMap_t()); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, "Content-Length: 42\n"); + OpenNode(); + ResetMocks(); + + EXPECT_EQ(42, node_->GetSize()); + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ExpectOpen("GET"); + ExpectHeaders(""); + SetResponse(200, "Content-Length: 42\n"); + SetResponseBody("Here is some response text. And some more."); + node_->Read(0, buf, sizeof(buf) - 1); + EXPECT_STREQ("Here is s", &buf[0]); + ResetMocks(); + + // Further reads should be cached. + node_->Read(0, buf, sizeof(buf) - 1); + EXPECT_STREQ("Here is s", &buf[0]); + node_->Read(10, buf, sizeof(buf) - 1); + EXPECT_STREQ("me respon", &buf[0]); + + EXPECT_EQ(42, node_->GetSize()); +} + +TEST_F(MountHttpNodeTest, ReadCachedNoContentLength) { + SetMountArgs(StringMap_t()); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, ""); + OpenNode(); + ResetMocks(); + + // Unknown size. + EXPECT_EQ(0, node_->GetSize()); + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ExpectOpen("GET"); + ExpectHeaders(""); + SetResponse(200, ""); // No Content-Length response here. + SetResponseBody("Here is some response text. And some more."); + node_->Read(0, buf, sizeof(buf) - 1); + EXPECT_STREQ("Here is s", &buf[0]); + ResetMocks(); + + // Further reads should be cached. + node_->Read(0, buf, sizeof(buf) - 1); + EXPECT_STREQ("Here is s", &buf[0]); + node_->Read(10, buf, sizeof(buf) - 1); + EXPECT_STREQ("me respon", &buf[0]); + + EXPECT_EQ(42, node_->GetSize()); +} + +TEST_F(MountHttpNodeTest, ReadCachedUnderrun) { + SetMountArgs(StringMap_t()); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, "Content-Length: 100\n"); + OpenNode(); + ResetMocks(); + + EXPECT_EQ(100, node_->GetSize()); + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ExpectOpen("GET"); + ExpectHeaders(""); + SetResponse(200, "Content-Length: 100\n"); + SetResponseBody("abcdefghijklmnopqrstuvwxyz"); + node_->Read(0, buf, sizeof(buf) - 1); + EXPECT_STREQ("abcdefghi", &buf[0]); + ResetMocks(); + + EXPECT_EQ(26, node_->GetSize()); +} + +TEST_F(MountHttpNodeTest, ReadCachedOverrun) { + SetMountArgs(StringMap_t()); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, "Content-Length: 15\n"); + OpenNode(); + ResetMocks(); + + EXPECT_EQ(15, node_->GetSize()); + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ExpectOpen("GET"); + ExpectHeaders(""); + SetResponse(200, "Content-Length: 15\n"); + SetResponseBody("01234567890123456789"); + node_->Read(10, buf, sizeof(buf) - 1); + EXPECT_STREQ("01234", &buf[0]); + ResetMocks(); + + EXPECT_EQ(15, node_->GetSize()); +} + +TEST_F(MountHttpNodeTest, ReadPartial) { + StringMap_t args; + args["cache_content"] = "false"; + SetMountArgs(args); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, ""); + OpenNode(); + ResetMocks(); + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ExpectOpen("GET"); + ExpectHeaders("Range: bytes=0-8\n"); + SetResponse(206, "Content-Length: 9\nContent-Range: bytes=0-8\n"); + SetResponseBody("012345678"); + node_->Read(0, buf, sizeof(buf) - 1); + EXPECT_STREQ("012345678", &buf[0]); + ResetMocks(); + + // Another read is another request. + ExpectOpen("GET"); + ExpectHeaders("Range: bytes=10-18\n"); + SetResponse(206, "Content-Length: 9\nContent-Range: bytes=10-18\n"); + SetResponseBody("abcdefghi"); + node_->Read(10, buf, sizeof(buf) - 1); + EXPECT_STREQ("abcdefghi", &buf[0]); +} + +TEST_F(MountHttpNodeTest, ReadPartialNoServerSupport) { + StringMap_t args; + args["cache_content"] = "false"; + SetMountArgs(args); + ExpectOpen("HEAD"); + ExpectHeaders(""); + SetResponse(200, ""); + OpenNode(); + ResetMocks(); + + char buf[10]; + memset(&buf[0], 0, sizeof(buf)); + + ExpectOpen("GET"); + ExpectHeaders("Range: bytes=10-18\n"); + SetResponse(200, "Content-Length: 20\n"); + SetResponseBody("0123456789abcdefghij"); + node_->Read(10, buf, sizeof(buf) - 1); + EXPECT_STREQ("abcdefghi", &buf[0]); +} |