From b3c1be96db0c7b8b0dfc52a8556e6459feefe62b Mon Sep 17 00:00:00 2001 From: "binji@chromium.org" Date: Sat, 19 Jan 2013 04:05:22 +0000 Subject: [NaCl SDK] Add HTTP mount. BUG=156781 R=noelallen@chromium.org NOTRY=true Review URL: https://chromiumcodereview.appspot.com/11887021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@177823 0039d316-1c4b-4281-b951-d872f2087c98 --- .../src/examples/hello_nacl_mounts/example.js | 8 + .../examples/hello_nacl_mounts/hello_nacl_mounts.c | 7 + .../src/examples/hello_nacl_mounts/index.html | 2 + .../src/libraries/nacl_mounts/Makefile | 2 +- .../src/libraries/nacl_mounts/kernel_proxy.cc | 2 + .../src/libraries/nacl_mounts/library.dsc | 3 + .../src/libraries/nacl_mounts/mount_html5fs.cc | 24 +- .../src/libraries/nacl_mounts/mount_http.cc | 580 +++++++++++++++++++++ .../src/libraries/nacl_mounts/mount_http.h | 43 ++ .../src/libraries/nacl_mounts/mount_node.h | 1 + .../libraries/nacl_mounts/mount_node_html5fs.cc | 15 +- .../src/libraries/nacl_mounts/nacl_mounts.h | 16 + .../src/libraries/nacl_mounts/osinttypes.h | 34 ++ .../src/libraries/nacl_mounts/path.cc | 7 +- .../libraries/nacl_mounts/pepper/all_interfaces.h | 23 + .../src/libraries/nacl_mounts/pepper_interface.cc | 14 +- .../src/libraries/nacl_mounts/pepper_interface.h | 12 +- .../src/libraries/nacl_mounts_test/path_test.cc | 14 + 18 files changed, 771 insertions(+), 36 deletions(-) create mode 100644 native_client_sdk/src/libraries/nacl_mounts/mount_http.cc create mode 100644 native_client_sdk/src/libraries/nacl_mounts/mount_http.h create mode 100644 native_client_sdk/src/libraries/nacl_mounts/osinttypes.h (limited to 'native_client_sdk') diff --git a/native_client_sdk/src/examples/hello_nacl_mounts/example.js b/native_client_sdk/src/examples/hello_nacl_mounts/example.js index c2376ea..c355b4d 100644 --- a/native_client_sdk/src/examples/hello_nacl_mounts/example.js +++ b/native_client_sdk/src/examples/hello_nacl_mounts/example.js @@ -140,6 +140,14 @@ function startsWith(s, prefix) { function logMessage(msg) { var logEl = document.getElementById('log'); + + // Perform some basic escaping. + msg = msg.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + logEl.innerHTML += msg + '
'; } diff --git a/native_client_sdk/src/examples/hello_nacl_mounts/hello_nacl_mounts.c b/native_client_sdk/src/examples/hello_nacl_mounts/hello_nacl_mounts.c index bf5ff77..63543d7 100644 --- a/native_client_sdk/src/examples/hello_nacl_mounts/hello_nacl_mounts.c +++ b/native_client_sdk/src/examples/hello_nacl_mounts/hello_nacl_mounts.c @@ -283,6 +283,13 @@ static PP_Bool Instance_DidCreate(PP_Instance instance, 0, /* mountflags */ "type=PERSISTENT,expected_size=1048576"); /* data */ + mount( + "", /* source. Use relative URL */ + "/http", /* target */ + "httpfs", /* filesystemtype */ + 0, /* mountflags */ + ""); /* data */ + pthread_create(&g_handle_message_thread, NULL, &HandleMessageThread, NULL); InitializeMessageQueue(); diff --git a/native_client_sdk/src/examples/hello_nacl_mounts/index.html b/native_client_sdk/src/examples/hello_nacl_mounts/index.html index 2b31ba8..2f9761c 100644 --- a/native_client_sdk/src/examples/hello_nacl_mounts/index.html +++ b/native_client_sdk/src/examples/hello_nacl_mounts/index.html @@ -32,6 +32,8 @@ found in the LICENSE file. is non-persistent.
  • /persistent a persistent storage area. Any data written here can be read back after Chrome is restarted.
  • +
  • /http a mount that can read from a URL. Try reading from + /http/index.html.
  • /dev a mount containing some utility files. /dev/null, /dev/zero, etc.
  • diff --git a/native_client_sdk/src/libraries/nacl_mounts/Makefile b/native_client_sdk/src/libraries/nacl_mounts/Makefile index 31daf35..5597f0c 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/Makefile +++ b/native_client_sdk/src/libraries/nacl_mounts/Makefile @@ -42,7 +42,7 @@ TARGET=nacl_mounts # SOURCES:=kernel_handle.cc kernel_intercept.cc kernel_object.cc kernel_proxy.cc SOURCES+=kernel_wrap_glibc.cc kernel_wrap_newlib.cc kernel_wrap_win.cc -SOURCES+=mount.cc mount_dev.cc mount_html5fs.cc mount_mem.cc +SOURCES+=mount.cc mount_dev.cc mount_html5fs.cc mount_http.cc mount_mem.cc SOURCES+=mount_node.cc mount_node_dir.cc mount_node_html5fs.cc mount_node_mem.cc SOURCES+=nacl_mounts.cc path.cc pepper_interface.cc real_pepper_interface.cc diff --git a/native_client_sdk/src/libraries/nacl_mounts/kernel_proxy.cc b/native_client_sdk/src/libraries/nacl_mounts/kernel_proxy.cc index a693598..745e375 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/kernel_proxy.cc +++ b/native_client_sdk/src/libraries/nacl_mounts/kernel_proxy.cc @@ -14,6 +14,7 @@ #include "nacl_mounts/mount.h" #include "nacl_mounts/mount_dev.h" #include "nacl_mounts/mount_html5fs.h" +#include "nacl_mounts/mount_http.h" #include "nacl_mounts/mount_mem.h" #include "nacl_mounts/mount_node.h" #include "nacl_mounts/osstat.h" @@ -48,6 +49,7 @@ void KernelProxy::Init(PepperInterface* ppapi) { factories_["memfs"] = MountMem::Create; factories_["dev"] = MountDev::Create; factories_["html5fs"] = MountHtml5Fs::Create; + factories_["httpfs"] = MountHttp::Create; // Create memory mount at root StringMap_t smap; diff --git a/native_client_sdk/src/libraries/nacl_mounts/library.dsc b/native_client_sdk/src/libraries/nacl_mounts/library.dsc index 1d5ca15..83e95eb 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/library.dsc +++ b/native_client_sdk/src/libraries/nacl_mounts/library.dsc @@ -22,6 +22,7 @@ "mount.cc", "mount_dev.cc", "mount_html5fs.cc", + "mount_http.cc", "mount_mem.cc", "mount_node.cc", "mount_node_dir.cc", @@ -45,6 +46,7 @@ "mount.h", "mount_dev.h", "mount_html5fs.h", + "mount_http.h", "mount_mem.h", "mount_node_dir.h", "mount_node.h", @@ -52,6 +54,7 @@ "mount_node_mem.h", "nacl_mounts.h", "osdirent.h", + "osinttypes.h", "osstat.h", "ostypes.h", "path.h", diff --git a/native_client_sdk/src/libraries/nacl_mounts/mount_html5fs.cc b/native_client_sdk/src/libraries/nacl_mounts/mount_html5fs.cc index a417b6a..b8aed8a 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/mount_html5fs.cc +++ b/native_client_sdk/src/libraries/nacl_mounts/mount_html5fs.cc @@ -69,18 +69,16 @@ int MountHtml5Fs::Mkdir(const Path& path, int permissions) { return -1; } - PP_Resource fileref_resource = ppapi()->GetFileRefInterface()->Create( - filesystem_resource_, path.Join().c_str()); - if (!fileref_resource) { + ScopedResource fileref_resource( + ppapi(), ppapi()->GetFileRefInterface()->Create(filesystem_resource_, + path.Join().c_str())); + if (!fileref_resource.pp_resource()) { errno = EINVAL; return -1; } - ScopedResource scoped_resource(ppapi_, fileref_resource, - ScopedResource::NoAddRef()); - int32_t result = ppapi()->GetFileRefInterface()->MakeDirectory( - fileref_resource, PP_FALSE, PP_BlockUntilComplete()); + fileref_resource.pp_resource(), PP_FALSE, PP_BlockUntilComplete()); if (result != PP_OK) { errno = PPErrorToErrno(result); return -1; @@ -99,18 +97,16 @@ int MountHtml5Fs::Remove(const Path& path) { return -1; } - PP_Resource fileref_resource = ppapi()->GetFileRefInterface()->Create( - filesystem_resource_, path.Join().c_str()); - if (!fileref_resource) { + ScopedResource fileref_resource( + ppapi(), ppapi()->GetFileRefInterface()->Create(filesystem_resource_, + path.Join().c_str())); + if (!fileref_resource.pp_resource()) { errno = EINVAL; return -1; } - ScopedResource scoped_resource(ppapi_, fileref_resource, - ScopedResource::NoAddRef()); - int32_t result = ppapi()->GetFileRefInterface()->Delete( - fileref_resource, + fileref_resource.pp_resource(), PP_BlockUntilComplete()); if (result != PP_OK) { errno = PPErrorToErrno(result); diff --git a/native_client_sdk/src/libraries/nacl_mounts/mount_http.cc b/native_client_sdk/src/libraries/nacl_mounts/mount_http.cc new file mode 100644 index 0000000..0cede9c --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_mounts/mount_http.cc @@ -0,0 +1,580 @@ +/* Copyright (c) 2012 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. + */ + +#include "nacl_mounts/mount_http.h" +#include +#include +#include +#include +#include +#include +#include +#include "nacl_mounts/osinttypes.h" +#include "utils/auto_lock.h" + +#if defined(WIN32) +#define snprintf _snprintf +#endif + + +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. +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; + +std::string NormalizeHeaderKey(const std::string& s) { + // Capitalize the first letter and any letter following a hyphen: + // e.g. ACCEPT-ENCODING -> Accept-Encoding + std::string result; + bool upper = true; + for (size_t i = 0; i < s.length(); ++i) { + char c = s[i]; + result += upper ? toupper(c) : tolower(c); + upper = c == '-'; + } + + return result; +} + +StringMap_t ParseHeaders(const char* headers, int32_t headers_length) { + enum State { + FINDING_KEY, + SKIPPING_WHITESPACE, + FINDING_VALUE, + }; + + StringMap_t result; + std::string key; + std::string value; + + State state = FINDING_KEY; + const char* start = headers; + for (int i = 0; i < headers_length; ++i) { + switch (state) { + case FINDING_KEY: + if (headers[i] == ':') { + // Found key. + key.assign(start, &headers[i] - start); + key = NormalizeHeaderKey(key); + state = SKIPPING_WHITESPACE; + } + break; + + case SKIPPING_WHITESPACE: + if (headers[i] == ' ') { + // Found whitespace, keep going... + break; + } + + // Found a non-whitespace, mark this as the start of the value. + start = &headers[i + 1]; + state = FINDING_VALUE; + // Fallthrough to start processing value without incrementing i. + + case FINDING_VALUE: + if (headers[i] == '\n') { + // Found value. + value.assign(start, &headers[i] - start); + result[key] = value; + + start = &headers[i + 1]; + state = FINDING_KEY; + } + break; + } + } + + return result; +} + +bool ParseContentLength(const StringMap_t& headers, size_t* content_length) { + StringMap_t::const_iterator iter = headers.find("Content-Length"); + if (iter == headers.end()) + return false; + + *content_length = strtoul(iter->second.c_str(), NULL, 10); + return true; +} + +bool ParseContentRange(const StringMap_t& headers, size_t* read_start, + size_t* read_end, size_t* entity_length) { + StringMap_t::const_iterator iter = headers.find("Content-Range"); + if (iter == headers.end()) + return false; + + // The key should look like "bytes ##-##/##" or "bytes ##-##/*". The last + // value is the entity length, which can potentially be * (i.e. unknown). + int read_start_int; + int read_end_int; + int entity_length_int; + int result = sscanf(iter->second.c_str(), "bytes %"SCNuS"-%"SCNuS"/%"SCNuS, + &read_start_int, &read_end_int, &entity_length_int); + + if (result == 2) { + *read_start = read_start_int; + *read_end = read_end_int; + *entity_length = 0; + return true; + } else if (result == 3) { + *read_start = read_start_int; + *read_end = read_end_int; + *entity_length = entity_length_int; + return true; + } + + return false; +} + +class MountNodeHttp : public MountNode { + public: + virtual int FSync(); + virtual int GetDents(size_t offs, struct dirent* pdir, size_t count); + virtual int GetStat(struct stat* stat); + virtual int Read(size_t offs, void* buf, size_t count); + virtual int Truncate(size_t size); + virtual int Write(size_t offs, const void* buf, size_t count); + virtual size_t GetSize(); + + protected: + MountNodeHttp(Mount* mount, int ino, int dev, const std::string& url); + virtual bool Init(int mode, short uid, short gid); + virtual int Close(); + + private: + bool OpenUrl(const char* method, + StringMap_t* request_headers, + PP_Resource* out_loader, + PP_Resource* out_request, + PP_Resource* out_response, + int32_t* out_statuscode, + StringMap_t* out_response_headers); + + std::string url_; + std::vector buffer_; + + friend class ::MountHttp; +}; + +int MountNodeHttp::FSync() { + errno = ENOSYS; + return -1; +} + +int MountNodeHttp::GetDents(size_t offs, struct dirent* pdir, size_t count) { + errno = ENOSYS; + return -1; +} + +int MountNodeHttp::GetStat(struct stat* stat) { + AutoLock lock(&lock_); + + StringMap_t headers; + PP_Resource loader; + PP_Resource request; + PP_Resource response; + int32_t statuscode; + StringMap_t response_headers; + if (!OpenUrl("HEAD", &headers, &loader, &request, &response, &statuscode, + &response_headers)) { + // errno is already set by OpenUrl. + return -1; + } + + ScopedResource scoped_loader(mount_->ppapi(), loader); + ScopedResource scoped_request(mount_->ppapi(), request); + ScopedResource scoped_response(mount_->ppapi(), response); + + // Fill in known info here. + memcpy(stat, &stat_, sizeof(stat_)); + + size_t entity_length; + if (ParseContentLength(response_headers, &entity_length)) + stat->st_size = static_cast(entity_length); + else + stat->st_size = 0; + + stat->st_atime = 0; // TODO(binji): Use "Last-Modified". + stat->st_mtime = 0; + stat->st_ctime = 0; + + return 0; +} + +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; + } + } + } 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(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 count; +} + +int MountNodeHttp::Truncate(size_t size) { + errno = ENOSYS; + return -1; +} + +int MountNodeHttp::Write(size_t offs, const void* buf, size_t count) { + // TODO(binji): supprt POST? + errno = ENOSYS; + return -1; +} + +size_t MountNodeHttp::GetSize() { + struct stat stat; + if (GetStat(&stat) == -1) { + // errno is already set by GetStat. + return -1; + } + + return stat.st_size; +} + +MountNodeHttp::MountNodeHttp(Mount* mount, int ino, int dev, + const std::string& url) + : MountNode(mount, ino, dev), + url_(url) { +} + +bool MountNodeHttp::Init(int mode, short uid, short gid) { + if (!MountNode::Init(mode, uid, gid)) { + return false; + } + + struct stat stat; + if (GetStat(&stat) == -1) { + return false; + } + + memcpy(&stat_, &stat, sizeof(stat_)); + + return true; +} + +int MountNodeHttp::Close() { + return 0; +} + +bool MountNodeHttp::OpenUrl(const char* method, + StringMap_t* request_headers, + PP_Resource* out_loader, + PP_Resource* out_request, + PP_Resource* out_response, + int32_t* out_statuscode, + StringMap_t* out_response_headers) { + // Assume lock_ is already held. + + PepperInterface* ppapi = mount_->ppapi(); + + MountHttp* mount_http = static_cast(mount_); + ScopedResource request(ppapi, + mount_http->MakeUrlRequestInfo(url_, method, + request_headers)); + if (!request.pp_resource()) { + errno = EINVAL; + return false; + } + + URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface(); + URLResponseInfoInterface* response_interface = + ppapi->GetURLResponseInfoInterface(); + VarInterface* var_interface = ppapi->GetVarInterface(); + + ScopedResource loader(ppapi, loader_interface->Create(ppapi->GetInstance())); + if (!loader.pp_resource()) { + errno = EINVAL; + return false; + } + + int32_t result = loader_interface->Open( + loader.pp_resource(), request.pp_resource(), PP_BlockUntilComplete()); + if (result != PP_OK) { + errno = PPErrorToErrno(result); + return false; + } + + ScopedResource response( + ppapi, + loader_interface->GetResponseInfo(loader.pp_resource())); + if (!response.pp_resource()) { + errno = EINVAL; + return false; + } + + // Get response statuscode. + PP_Var statuscode = response_interface->GetProperty( + response.pp_resource(), + PP_URLRESPONSEPROPERTY_STATUSCODE); + + if (statuscode.type != PP_VARTYPE_INT32) { + errno = EINVAL; + return false; + } + + *out_statuscode = statuscode.value.as_int; + + // Only accept OK or Partial Content. + if (*out_statuscode != STATUSCODE_OK && + *out_statuscode != STATUSCODE_PARTIAL_CONTENT) { + errno = EINVAL; + return false; + } + + // Get response headers. + PP_Var response_headers_var = response_interface->GetProperty( + 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); + + return true; +} + + +} // namespace + +MountNode *MountHttp::Open(const Path& path, int mode) { + assert(url_root_.empty() || url_root_[url_root_.length() - 1] == '/'); + + std::string url = url_root_ + (path.IsAbsolute() ? + path.Range(1, path.Size()) : + path.Join()); + + const int ino = 1; + const int USR_ID = 1001; + const int GRP_ID = 1002; + MountNodeHttp* node = new MountNodeHttp(this, ino, dev_, url); + if (!node->Init(mode, USR_ID, GRP_ID)) { + node->Release(); + return NULL; + } + + return node; +} + +int MountHttp::Close(MountNode* node) { + AutoLock lock(&lock_); + node->Close(); + node->Release(); + return 0; +} + +int MountHttp::Unlink(const Path& path) { + errno = ENOSYS; + return -1; +} + +int MountHttp::Mkdir(const Path& path, int permissions) { + errno = ENOSYS; + return -1; +} + +int MountHttp::Rmdir(const Path& path) { + errno = ENOSYS; + return -1; +} + +int MountHttp::Remove(const Path& path) { + errno = ENOSYS; + return -1; +} + +PP_Resource MountHttp::MakeUrlRequestInfo( + const std::string& url, + const char* method, + StringMap_t* additional_headers) { + URLRequestInfoInterface* interface = ppapi_->GetURLRequestInfoInterface(); + VarInterface* var_interface = ppapi_->GetVarInterface(); + + PP_Resource request_info = interface->Create(ppapi_->GetInstance()); + if (!request_info) + return 0; + + interface->SetProperty( + request_info, PP_URLREQUESTPROPERTY_URL, + var_interface->VarFromUtf8(url.c_str(), url.length())); + interface->SetProperty(request_info, PP_URLREQUESTPROPERTY_METHOD, + var_interface->VarFromUtf8(method, strlen(method))); + interface->SetProperty(request_info, + PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS, + PP_MakeBool(allow_cors_ ? PP_TRUE : PP_FALSE)); + interface->SetProperty(request_info, PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS, + PP_MakeBool(allow_credentials_ ? PP_TRUE : PP_FALSE)); + + // Merge the mount headers with the request headers. If the field is already + // set it |additional_headers|, don't use the one from headers_. + for (StringMap_t::iterator iter = headers_.begin(); iter != headers_.end(); + ++iter) { + const std::string& key = NormalizeHeaderKey(iter->first); + if (additional_headers->find(key) == additional_headers->end()) { + additional_headers->insert(std::make_pair(key, iter->second)); + } + } + + // Join the headers into one string. + std::string headers; + for (StringMap_t::iterator iter = additional_headers->begin(); + iter != additional_headers->end(); ++iter) { + headers += iter->first + ": " + iter->second + '\n'; + } + + interface->SetProperty( + request_info, PP_URLREQUESTPROPERTY_HEADERS, + var_interface->VarFromUtf8(headers.c_str(), headers.length())); + + return request_info; +} + +MountHttp::MountHttp() + : allow_cors_(false), + allow_credentials_(false) { +} + +bool MountHttp::Init(int dev, StringMap_t& args, PepperInterface* ppapi) { + if (!Mount::Init(dev, args, ppapi)) + return false; + + // Parse mount args. + for (StringMap_t::iterator iter = args.begin(); iter != args.end(); ++iter) { + if (iter->first == "SOURCE") { + url_root_ = iter->second; + + // Make sure url_root_ ends with a slash. + if (!url_root_.empty() && url_root_[url_root_.length() - 1] != '/') { + url_root_ += '/'; + } + } else if (iter->first == "allow_cross_origin_requests") { + allow_cors_ = iter->second == "true"; + } else if (iter->first == "allow_credentials") { + allow_credentials_ = iter->second == "true"; + } else { + // Assume it is a header to pass to an HTTP request. + headers_[NormalizeHeaderKey(iter->first)] = iter->second; + } + } + + return true; +} + +void MountHttp::Destroy() { +} diff --git a/native_client_sdk/src/libraries/nacl_mounts/mount_http.h b/native_client_sdk/src/libraries/nacl_mounts/mount_http.h new file mode 100644 index 0000000..6ba47b2 --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_mounts/mount_http.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2012 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_MOUNTS_MOUNT_HTTP_H_ +#define LIBRARIES_NACL_MOUNTS_MOUNT_HTTP_H_ + +#include +#include "nacl_mounts/mount.h" +#include "nacl_mounts/pepper_interface.h" + +class MountNode; + +class MountHttp : public Mount { + public: + virtual MountNode *Open(const Path& path, int mode); + virtual int Close(MountNode* node); + virtual int Unlink(const Path& path); + virtual int Mkdir(const Path& path, int permissions); + virtual int Rmdir(const Path& path); + virtual int Remove(const Path& path); + + PP_Resource MakeUrlRequestInfo(const std::string& url, + const char* method, + StringMap_t* additional_headers); + + protected: + MountHttp(); + + virtual bool Init(int dev, StringMap_t& args, PepperInterface* ppapi); + virtual void Destroy(); + + private: + std::string url_root_; + StringMap_t headers_; + bool allow_cors_; + bool allow_credentials_; + + friend class Mount; +}; + +#endif // LIBRARIES_NACL_MOUNTS_MOUNT_HTTP_H_ diff --git a/native_client_sdk/src/libraries/nacl_mounts/mount_node.h b/native_client_sdk/src/libraries/nacl_mounts/mount_node.h index 2592c8e..a11a6a9 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/mount_node.h +++ b/native_client_sdk/src/libraries/nacl_mounts/mount_node.h @@ -65,6 +65,7 @@ protected: friend class MountDev; friend class MountHtml5Fs; + friend class MountHttp; friend class MountMem; friend class MountNodeDir; }; diff --git a/native_client_sdk/src/libraries/nacl_mounts/mount_node_html5fs.cc b/native_client_sdk/src/libraries/nacl_mounts/mount_node_html5fs.cc index 648b94b..7652946 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/mount_node_html5fs.cc +++ b/native_client_sdk/src/libraries/nacl_mounts/mount_node_html5fs.cc @@ -76,24 +76,21 @@ int MountNodeHtml5Fs::GetDents(size_t offs, struct dirent* pdir, size_t size) { return -1; } - PP_Resource directory_reader_resource = - mount_->ppapi()->GetDirectoryReaderInterface()->Create(fileref_resource_); - if (!directory_reader_resource) { + ScopedResource directory_reader( + mount_->ppapi(), + mount_->ppapi()->GetDirectoryReaderInterface()->Create( + fileref_resource_)); + if (!directory_reader.pp_resource()) { errno = ENOSYS; return -1; } - ScopedResource scoped_directory_reader( - mount_->ppapi(), - directory_reader_resource, - ScopedResource::NoAddRef()); - std::vector dirents; PP_DirectoryEntry_Dev directory_entry = {0}; while (1) { int32_t result = mount_->ppapi()->GetDirectoryReaderInterface()->GetNextEntry( - directory_reader_resource, &directory_entry, + directory_reader.pp_resource(), &directory_entry, PP_BlockUntilComplete()); if (result != PP_OK) { errno = PPErrorToErrno(result); diff --git a/native_client_sdk/src/libraries/nacl_mounts/nacl_mounts.h b/native_client_sdk/src/libraries/nacl_mounts/nacl_mounts.h index 6a09c429..b70957f 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/nacl_mounts.h +++ b/native_client_sdk/src/libraries/nacl_mounts/nacl_mounts.h @@ -78,6 +78,22 @@ void nacl_mounts_init_ppapi(PP_Instance instance, * "expected_size": The expected file-system size. Note that this does * not request quota -- you must do that from JavaScript. * + * "httpfs": A filesystem that reads from a URL via HTTP. + * source: The root URL to read from. All paths read from this filesystem + * will be appended to this root. + * e.g. If source == "http://example.com/path", reading from + * "foo/bar.txt" will attempt to read from the URL + * "http://example.com/path/foo/bar.txt". + * data: A string of parameters: + * "allow_cross_origin_request": If "true", then reads from this + * filesystem will follow the CORS standard for cross-origin requests. + * See http://www.w3.org/TR/access-control. + * "allow_credentials": If "true", credentials are sent with cross-origin + * requests. If false, no credentials are sent with the request and + * cookies are ignored in the response. + * All other key/value pairs are assumed to be headers to use with + * HTTP requests. + * * * @param[in] source Depends on the filesystem type. See above. * @param[in] target The absolute path to mount the filesystem. diff --git a/native_client_sdk/src/libraries/nacl_mounts/osinttypes.h b/native_client_sdk/src/libraries/nacl_mounts/osinttypes.h new file mode 100644 index 0000000..e6705f0 --- /dev/null +++ b/native_client_sdk/src/libraries/nacl_mounts/osinttypes.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2012 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 NACL_MOUNTS_OSINTTYPES_H_ +#define NACL_MOUNTS_OSINTTYPES_H_ + +// Define printf/scanf format strings for size_t. + +#if defined(WIN32) + +#if !defined(PRIuS) +#define PRIuS "Iu" +#endif + +#if !defined(SCNuS) +#define SCNuS "Iu" +#endif + +#elif defined(__native_client__) + +#include + +#if !defined(PRIuS) +#define PRIuS "zu" +#endif + +#if !defined(SCNuS) +#define SCNuS "zu" +#endif + +#endif // defined(__native_client__) + +#endif // NACL_MOUNTS_OSINTTYPES_H_ diff --git a/native_client_sdk/src/libraries/nacl_mounts/path.cc b/native_client_sdk/src/libraries/nacl_mounts/path.cc index 47d4f45..63978b7 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/path.cc +++ b/native_client_sdk/src/libraries/nacl_mounts/path.cc @@ -146,14 +146,17 @@ std::string Path::Range(const StringArray_t& paths, size_t start, size_t end) { if (end > paths.size()) end = paths.size(); + // If this is an absolute path, paths[0] == "/". In this case, we don't want + // to add an additional / separator. if (start == 0 && end > 0 && paths[0] == "/") { - if (end == 1) return std::string("/"); + out_path += "/"; index++; } for (; index < end; index++) { - if (index) out_path += "/"; out_path += paths[index]; + if (index < end - 1) + out_path += "/"; } return out_path; diff --git a/native_client_sdk/src/libraries/nacl_mounts/pepper/all_interfaces.h b/native_client_sdk/src/libraries/nacl_mounts/pepper/all_interfaces.h index 1dd46b1..0851dc4 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/pepper/all_interfaces.h +++ b/native_client_sdk/src/libraries/nacl_mounts/pepper/all_interfaces.h @@ -69,3 +69,26 @@ BEGIN_INTERFACE(VarInterface, PPB_Var, PPB_VAR_INTERFACE) METHOD2(VarInterface, struct PP_Var, VarFromUtf8, const char *, uint32_t) METHOD2(VarInterface, const char*, VarToUtf8, PP_Var, uint32_t*) END_INTERFACE(VarInterface, PPB_Var) + +BEGIN_INTERFACE(URLLoaderInterface, PPB_URLLoader, PPB_URLLOADER_INTERFACE) + METHOD1(URLLoaderInterface, PP_Resource, Create, PP_Instance) + METHOD3(URLLoaderInterface, int32_t, Open, PP_Resource, PP_Resource, + PP_CompletionCallback) + METHOD1(URLLoaderInterface, PP_Resource, GetResponseInfo, PP_Resource) + METHOD4(URLLoaderInterface, int32_t, ReadResponseBody, PP_Resource, void*, + int32_t, PP_CompletionCallback) + METHOD1(URLLoaderInterface, void, Close, PP_Resource) +END_INTERFACE(URLLoaderInterface, PPB_URLLoader) + +BEGIN_INTERFACE(URLRequestInfoInterface, PPB_URLRequestInfo, + PPB_URLREQUESTINFO_INTERFACE) + METHOD1(URLRequestInfoInterface, PP_Resource, Create, PP_Instance) + METHOD3(URLRequestInfoInterface, PP_Bool, SetProperty, PP_Resource, + PP_URLRequestProperty, PP_Var) +END_INTERFACE(URLRequestInfoInterface, PPB_URLRequestInfo) + +BEGIN_INTERFACE(URLResponseInfoInterface, PPB_URLResponseInfo, + PPB_URLRESPONSEINFO_INTERFACE) + METHOD2(URLResponseInfoInterface, PP_Var, GetProperty, PP_Resource, + PP_URLResponseProperty) +END_INTERFACE(URLResponseInfoInterface, PPB_URLResponseInfo) diff --git a/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.cc b/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.cc index 16c6b8e..51dff4e 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.cc +++ b/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.cc @@ -9,17 +9,17 @@ ScopedResource::ScopedResource(PepperInterface* ppapi, PP_Resource resource) : ppapi_(ppapi), resource_(resource) { - ppapi_->AddRefResource(resource_); } -ScopedResource::ScopedResource(PepperInterface* ppapi, PP_Resource resource, - NoAddRef) - : ppapi_(ppapi), - resource_(resource) { +ScopedResource::~ScopedResource() { + if (resource_) + ppapi_->ReleaseResource(resource_); } -ScopedResource::~ScopedResource() { - ppapi_->ReleaseResource(resource_); +PP_Resource ScopedResource::Release() { + PP_Resource result = resource_; + resource_ = 0; + return result; } int PPErrorToErrno(int32_t err) { diff --git a/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.h b/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.h index 399c805..2c5c004 100644 --- a/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.h +++ b/native_client_sdk/src/libraries/nacl_mounts/pepper_interface.h @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include #include @@ -79,12 +82,15 @@ class PepperInterface { class ScopedResource { public: - struct NoAddRef {}; - + // Does not AddRef by default. ScopedResource(PepperInterface* ppapi, PP_Resource resource); - ScopedResource(PepperInterface* ppapi, PP_Resource resource, NoAddRef); ~ScopedResource(); + PP_Resource pp_resource() { return resource_; } + + // Return the resource without decrementing its refcount. + PP_Resource Release(); + private: PepperInterface* ppapi_; PP_Resource resource_; diff --git a/native_client_sdk/src/libraries/nacl_mounts_test/path_test.cc b/native_client_sdk/src/libraries/nacl_mounts_test/path_test.cc index 679c3a1..ea89d42 100644 --- a/native_client_sdk/src/libraries/nacl_mounts_test/path_test.cc +++ b/native_client_sdk/src/libraries/nacl_mounts_test/path_test.cc @@ -242,3 +242,17 @@ TEST(PathTest, Invalid) { test.Append("../../../foo"); EXPECT_EQ("../foo", test.Join()); } + +TEST(PathTest, Range) { + Path p("/an/absolute/path"); + + // p's parts should be ["/", "an", "absolute", "path"]. + EXPECT_EQ("/an/absolute/path", p.Range(0, 4)); + EXPECT_EQ("an/absolute/path", p.Range(1, 4)); + EXPECT_EQ("absolute/path", p.Range(2, 4)); + EXPECT_EQ("path", p.Range(3, 4)); + + EXPECT_EQ("/an/absolute", p.Range(0, 3)); + EXPECT_EQ("an/absolute", p.Range(1, 3)); + EXPECT_EQ("absolute", p.Range(2, 3)); +} -- cgit v1.1