| // Copyright 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. |
| |
| #include "nacl_io/httpfs/http_fs.h" |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <vector> |
| |
| #include <ppapi/c/pp_errors.h> |
| |
| #include "nacl_io/dir_node.h" |
| #include "nacl_io/httpfs/http_fs_node.h" |
| #include "nacl_io/kernel_handle.h" |
| #include "nacl_io/log.h" |
| #include "nacl_io/osinttypes.h" |
| #include "nacl_io/osunistd.h" |
| #include "sdk_util/string_util.h" |
| |
| namespace nacl_io { |
| |
| 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; |
| } |
| |
| Error HttpFs::Access(const Path& path, int a_mode) { |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter == node_cache_.end()) { |
| // If we can't find the node in the cache, fetch it |
| std::string url = MakeUrl(path); |
| ScopedNode node(new HttpFsNode(this, url, cache_content_)); |
| Error error = node->Init(0); |
| if (error) |
| return error; |
| |
| error = node->GetStat(NULL); |
| if (error) |
| return error; |
| } |
| |
| // Don't allow write or execute access. |
| if (a_mode & (W_OK | X_OK)) |
| return EACCES; |
| |
| return 0; |
| } |
| |
| Error HttpFs::Open(const Path& path, int open_flags, ScopedNode* out_node) { |
| out_node->reset(NULL); |
| |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter != node_cache_.end()) { |
| *out_node = iter->second; |
| return 0; |
| } |
| |
| // If we can't find the node in the cache, create it |
| std::string url = MakeUrl(path); |
| ScopedNode node(new HttpFsNode(this, url, cache_content_)); |
| Error error = node->Init(open_flags); |
| if (error) |
| return error; |
| |
| error = node->GetStat(NULL); |
| if (error) |
| return error; |
| |
| ScopedNode parent; |
| error = FindOrCreateDir(path.Parent(), &parent); |
| if (error) |
| return error; |
| |
| error = parent->AddChild(path.Basename(), node); |
| if (error) |
| return error; |
| |
| node_cache_[path.Join()] = node; |
| *out_node = node; |
| return 0; |
| } |
| |
| Error HttpFs::Unlink(const Path& path) { |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter == node_cache_.end()) |
| return ENOENT; |
| |
| if (iter->second->IsaDir()) |
| return EISDIR; |
| |
| return EACCES; |
| } |
| |
| Error HttpFs::Mkdir(const Path& path, int permissions) { |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter != node_cache_.end()) { |
| if (iter->second->IsaDir()) |
| return EEXIST; |
| } |
| return EACCES; |
| } |
| |
| Error HttpFs::Rmdir(const Path& path) { |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter == node_cache_.end()) |
| return ENOENT; |
| |
| if (!iter->second->IsaDir()) |
| return ENOTDIR; |
| |
| return EACCES; |
| } |
| |
| Error HttpFs::Remove(const Path& path) { |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter == node_cache_.end()) |
| return ENOENT; |
| |
| return EACCES; |
| } |
| |
| Error HttpFs::Rename(const Path& path, const Path& newpath) { |
| NodeMap_t::iterator iter = node_cache_.find(path.Join()); |
| if (iter == node_cache_.end()) |
| return ENOENT; |
| |
| return EACCES; |
| } |
| |
| PP_Resource HttpFs::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 filesystem 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; |
| } |
| |
| HttpFs::HttpFs() |
| : allow_cors_(false), |
| allow_credentials_(false), |
| cache_stat_(true), |
| cache_content_(true) { |
| } |
| |
| Error HttpFs::Init(const FsInitArgs& args) { |
| Error error = Filesystem::Init(args); |
| if (error) |
| return error; |
| |
| // Parse filesystem args. |
| for (StringMap_t::const_iterator iter = args.string_map.begin(); |
| iter != args.string_map.end(); |
| ++iter) { |
| if (iter->first == "SOURCE") { |
| url_root_ = iter->second; |
| is_blob_url_ = strncmp(url_root_.c_str(), "blob:", 5) == 0; |
| |
| // Make sure url_root_ ends with a slash, except for blob URLs. |
| if (!is_blob_url_ && !url_root_.empty() && |
| url_root_[url_root_.length() - 1] != '/') { |
| url_root_ += '/'; |
| } |
| } else if (iter->first == "manifest") { |
| char* text; |
| error = LoadManifest(iter->second, &text); |
| if (error) |
| return error; |
| |
| error = ParseManifest(text); |
| if (error) { |
| delete[] text; |
| return error; |
| } |
| |
| delete[] text; |
| } 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 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; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void HttpFs::Destroy() { |
| } |
| |
| Error HttpFs::FindOrCreateDir(const Path& path, ScopedNode* out_node) { |
| out_node->reset(NULL); |
| std::string strpath = path.Join(); |
| NodeMap_t::iterator iter = node_cache_.find(strpath); |
| if (iter != node_cache_.end()) { |
| *out_node = iter->second; |
| return 0; |
| } |
| |
| // If the node does not exist, create it. |
| ScopedNode node(new DirNode(this)); |
| Error error = node->Init(0); |
| if (error) |
| return error; |
| |
| // If not the root node, find the parent node and add it to the parent |
| if (!path.IsRoot()) { |
| ScopedNode parent; |
| error = FindOrCreateDir(path.Parent(), &parent); |
| if (error) |
| return error; |
| |
| error = parent->AddChild(path.Basename(), node); |
| if (error) |
| return error; |
| } |
| |
| // Add it to the node cache. |
| node_cache_[strpath] = node; |
| *out_node = node; |
| return 0; |
| } |
| |
| Error HttpFs::ParseManifest(const char* text) { |
| std::vector<std::string> lines; |
| sdk_util::SplitString(text, '\n', &lines); |
| |
| for (size_t i = 0; i < lines.size(); i++) { |
| std::vector<std::string> words; |
| sdk_util::SplitString(lines[i], ' ', &words); |
| |
| // Remove empty words (due to multiple consecutive spaces). |
| std::vector<std::string> non_empty_words; |
| for (std::vector<std::string>::const_iterator it = words.begin(); |
| it != words.end(); |
| ++it) { |
| if (!it->empty()) |
| non_empty_words.push_back(*it); |
| } |
| |
| if (non_empty_words.size() == 3) { |
| const std::string& modestr = non_empty_words[0]; |
| const std::string& lenstr = non_empty_words[1]; |
| const std::string& name = non_empty_words[2]; |
| |
| assert(modestr.size() == 4); |
| assert(name[0] == '/'); |
| |
| // Only support regular and streams for now |
| // Ignore EXEC bit |
| int mode = S_IFREG; |
| switch (modestr[0]) { |
| case '-': |
| mode = S_IFREG; |
| break; |
| case 'c': |
| mode = S_IFCHR; |
| break; |
| default: |
| LOG_ERROR("Unable to parse type %s for %s.", |
| modestr.c_str(), |
| name.c_str()); |
| return EINVAL; |
| } |
| |
| switch (modestr[1]) { |
| case '-': |
| break; |
| case 'r': |
| mode |= S_IRUSR | S_IRGRP | S_IROTH; |
| break; |
| default: |
| LOG_ERROR("Unable to parse read %s for %s.", |
| modestr.c_str(), |
| name.c_str()); |
| return EINVAL; |
| } |
| |
| switch (modestr[2]) { |
| case '-': |
| break; |
| case 'w': |
| mode |= S_IWUSR | S_IWGRP | S_IWOTH; |
| break; |
| default: |
| LOG_ERROR("Unable to parse write %s for %s.", |
| modestr.c_str(), |
| name.c_str()); |
| return EINVAL; |
| } |
| |
| Path path(name); |
| std::string url = MakeUrl(path); |
| |
| HttpFsNode* http_node = new HttpFsNode(this, url, cache_content_); |
| http_node->SetMode(mode); |
| ScopedNode node(http_node); |
| |
| Error error = node->Init(0); |
| if (error) |
| return error; |
| http_node->SetCachedSize(atoi(lenstr.c_str())); |
| |
| ScopedNode dir_node; |
| error = FindOrCreateDir(path.Parent(), &dir_node); |
| if (error) |
| return error; |
| |
| error = dir_node->AddChild(path.Basename(), node); |
| if (error) |
| return error; |
| |
| std::string pname = path.Join(); |
| node_cache_[pname] = node; |
| } |
| } |
| |
| return 0; |
| } |
| |
| Error HttpFs::LoadManifest(const std::string& manifest_name, |
| char** out_manifest) { |
| Path manifest_path(manifest_name); |
| ScopedNode manifest_node; |
| *out_manifest = NULL; |
| |
| int error = Open(manifest_path, O_RDONLY, &manifest_node); |
| if (error) |
| return error; |
| |
| off_t size; |
| error = manifest_node->GetSize(&size); |
| if (error) |
| return error; |
| |
| char* text = new char[size + 1]; |
| int len; |
| error = manifest_node->Read(HandleAttr(), text, size, &len); |
| if (error) |
| return error; |
| |
| text[len] = 0; |
| *out_manifest = text; |
| return 0; |
| } |
| |
| std::string HttpFs::MakeUrl(const Path& path) { |
| return url_root_ + |
| (path.IsAbsolute() ? path.Range(1, path.Size()) : path.Join()); |
| } |
| |
| } // namespace nacl_io |