1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "nacl_io/httpfs/http_fs.h" 6 7#include <assert.h> 8#include <ctype.h> 9#include <errno.h> 10#include <fcntl.h> 11#include <stdio.h> 12#include <string.h> 13#include <sys/stat.h> 14#include <sys/types.h> 15 16#include <vector> 17 18#include <ppapi/c/pp_errors.h> 19 20#include "nacl_io/dir_node.h" 21#include "nacl_io/httpfs/http_fs_node.h" 22#include "nacl_io/kernel_handle.h" 23#include "nacl_io/log.h" 24#include "nacl_io/osinttypes.h" 25#include "nacl_io/osunistd.h" 26#include "sdk_util/string_util.h" 27 28namespace nacl_io { 29 30std::string NormalizeHeaderKey(const std::string& s) { 31 // Capitalize the first letter and any letter following a hyphen: 32 // e.g. ACCEPT-ENCODING -> Accept-Encoding 33 std::string result; 34 bool upper = true; 35 for (size_t i = 0; i < s.length(); ++i) { 36 char c = s[i]; 37 result += upper ? toupper(c) : tolower(c); 38 upper = c == '-'; 39 } 40 41 return result; 42} 43 44Error HttpFs::OpenWithMode(const Path& path, int open_flags, mode_t mode, 45 ScopedNode* out_node) { 46 out_node->reset(NULL); 47 48 ScopedNode node = FindExistingNode(path); 49 if (node.get() != NULL) { 50 *out_node = node; 51 return 0; 52 } 53 54 // If we can't find the node in the cache, create it 55 std::string url = MakeUrl(path); 56 node.reset(new HttpFsNode(this, url, cache_content_)); 57 Error error = node->Init(open_flags); 58 if (error) 59 return error; 60 61 error = node->GetStat(NULL); 62 if (error) 63 return error; 64 65 ScopedNode parent; 66 error = FindOrCreateDir(path.Parent(), &parent); 67 if (error) 68 return error; 69 70 error = parent->AddChild(path.Basename(), node); 71 if (error) 72 return error; 73 74 node_cache_[path.Join()] = node; 75 *out_node = node; 76 return 0; 77} 78 79ScopedNode HttpFs::FindExistingNode(const Path& path) { 80 NodeMap_t::iterator iter = node_cache_.find(path.Join()); 81 if (iter == node_cache_.end()) 82 return ScopedNode(); 83 return iter->second; 84} 85 86Error HttpFs::Unlink(const Path& path) { 87 ScopedNode node = FindExistingNode(path); 88 if (node.get() == NULL) 89 return ENOENT; 90 91 if (node->IsaDir()) 92 return EISDIR; 93 94 return EACCES; 95} 96 97Error HttpFs::Mkdir(const Path& path, int permissions) { 98 ScopedNode node = FindExistingNode(path); 99 if (node.get() != NULL && node->IsaDir()) 100 return EEXIST; 101 102 return EACCES; 103} 104 105Error HttpFs::Rmdir(const Path& path) { 106 ScopedNode node = FindExistingNode(path); 107 if (node.get() == NULL) 108 return ENOENT; 109 110 if (!node->IsaDir()) 111 return ENOTDIR; 112 113 return EACCES; 114} 115 116Error HttpFs::Remove(const Path& path) { 117 ScopedNode node = FindExistingNode(path); 118 if (node.get() == NULL) 119 return ENOENT; 120 121 return EACCES; 122} 123 124Error HttpFs::Rename(const Path& path, const Path& newpath) { 125 ScopedNode node = FindExistingNode(path); 126 if (node.get() == NULL) 127 return ENOENT; 128 129 return EACCES; 130} 131 132PP_Resource HttpFs::MakeUrlRequestInfo(const std::string& url, 133 const char* method, 134 StringMap_t* additional_headers) { 135 URLRequestInfoInterface* interface = ppapi_->GetURLRequestInfoInterface(); 136 VarInterface* var_interface = ppapi_->GetVarInterface(); 137 138 PP_Resource request_info = interface->Create(ppapi_->GetInstance()); 139 if (!request_info) 140 return 0; 141 142 interface->SetProperty(request_info, 143 PP_URLREQUESTPROPERTY_URL, 144 var_interface->VarFromUtf8(url.c_str(), url.length())); 145 interface->SetProperty(request_info, 146 PP_URLREQUESTPROPERTY_METHOD, 147 var_interface->VarFromUtf8(method, strlen(method))); 148 interface->SetProperty(request_info, 149 PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS, 150 PP_MakeBool(allow_cors_ ? PP_TRUE : PP_FALSE)); 151 interface->SetProperty(request_info, 152 PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS, 153 PP_MakeBool(allow_credentials_ ? PP_TRUE : PP_FALSE)); 154 155 // Merge the filesystem headers with the request headers. If the field is 156 // already set it |additional_headers|, don't use the one from headers_. 157 for (StringMap_t::iterator iter = headers_.begin(); iter != headers_.end(); 158 ++iter) { 159 const std::string& key = NormalizeHeaderKey(iter->first); 160 if (additional_headers->find(key) == additional_headers->end()) { 161 additional_headers->insert(std::make_pair(key, iter->second)); 162 } 163 } 164 165 // Join the headers into one string. 166 std::string headers; 167 for (StringMap_t::iterator iter = additional_headers->begin(); 168 iter != additional_headers->end(); 169 ++iter) { 170 headers += iter->first + ": " + iter->second + '\n'; 171 } 172 173 interface->SetProperty( 174 request_info, 175 PP_URLREQUESTPROPERTY_HEADERS, 176 var_interface->VarFromUtf8(headers.c_str(), headers.length())); 177 178 return request_info; 179} 180 181HttpFs::HttpFs() 182 : allow_cors_(false), 183 allow_credentials_(false), 184 cache_stat_(true), 185 cache_content_(true), 186 is_blob_url_(false) { 187} 188 189Error HttpFs::Init(const FsInitArgs& args) { 190 Error error = Filesystem::Init(args); 191 if (error) 192 return error; 193 194 // Parse filesystem args. 195 for (StringMap_t::const_iterator iter = args.string_map.begin(); 196 iter != args.string_map.end(); 197 ++iter) { 198 if (iter->first == "SOURCE") { 199 url_root_ = iter->second; 200 is_blob_url_ = strncmp(url_root_.c_str(), "blob:", 5) == 0; 201 202 } else if (iter->first == "manifest") { 203 char* text; 204 error = LoadManifest(iter->second, &text); 205 if (error) 206 return error; 207 208 error = ParseManifest(text); 209 if (error) { 210 free(text); 211 return error; 212 } 213 214 free(text); 215 } else if (iter->first == "allow_cross_origin_requests") { 216 allow_cors_ = iter->second == "true"; 217 } else if (iter->first == "allow_credentials") { 218 allow_credentials_ = iter->second == "true"; 219 } else if (iter->first == "cache_stat") { 220 cache_stat_ = iter->second == "true"; 221 } else if (iter->first == "cache_content") { 222 cache_content_ = iter->second == "true"; 223 } else { 224 // Assume it is a header to pass to an HTTP request. 225 headers_[NormalizeHeaderKey(iter->first)] = iter->second; 226 } 227 } 228 229 if (!is_blob_url_) { 230 if (!url_root_.empty() && url_root_[url_root_.length() - 1] != '/') { 231 // Make sure url_root_ ends with a slash, except for blob URLs. 232 url_root_ += '/'; 233 } 234 235 ScopedNode root; 236 error = FindOrCreateDir(Path("/"), &root); 237 if (error) 238 return error; 239 } 240 241 return 0; 242} 243 244void HttpFs::Destroy() { 245} 246 247Error HttpFs::FindOrCreateDir(const Path& path, ScopedNode* out_node) { 248 out_node->reset(NULL); 249 250 ScopedNode node = FindExistingNode(path); 251 if (node.get() != NULL) { 252 *out_node = node; 253 return 0; 254 } 255 256 // If the node does not exist, create it. 257 node.reset(new DirNode(this)); 258 Error error = node->Init(0); 259 if (error) 260 return error; 261 // Directorys in http filesystems are never writable. 262 node->SetMode(node->GetMode() & ~S_IWALL); 263 264 // If not the root node, find the parent node and add it to the parent 265 if (!path.IsRoot()) { 266 ScopedNode parent; 267 error = FindOrCreateDir(path.Parent(), &parent); 268 if (error) 269 return error; 270 271 error = parent->AddChild(path.Basename(), node); 272 if (error) 273 return error; 274 } 275 276 // Add it to the node cache. 277 node_cache_[path.Join()] = node; 278 *out_node = node; 279 return 0; 280} 281 282Error HttpFs::ParseManifest(const char* text) { 283 std::vector<std::string> lines; 284 sdk_util::SplitString(text, '\n', &lines); 285 286 for (size_t i = 0; i < lines.size(); i++) { 287 std::vector<std::string> words; 288 sdk_util::SplitString(lines[i], ' ', &words); 289 290 // Remove empty words (due to multiple consecutive spaces). 291 std::vector<std::string> non_empty_words; 292 for (std::vector<std::string>::const_iterator it = words.begin(); 293 it != words.end(); 294 ++it) { 295 if (!it->empty()) 296 non_empty_words.push_back(*it); 297 } 298 299 if (non_empty_words.size() == 3) { 300 const std::string& modestr = non_empty_words[0]; 301 const std::string& lenstr = non_empty_words[1]; 302 const std::string& name = non_empty_words[2]; 303 304 assert(modestr.size() == 4); 305 assert(name[0] == '/'); 306 307 // Only support regular and streams for now 308 // Ignore EXEC bit 309 int type = 0; 310 switch (modestr[0]) { 311 case '-': 312 type = S_IFREG; 313 break; 314 case 'c': 315 type = S_IFCHR; 316 break; 317 default: 318 LOG_ERROR("Unable to parse type %s for %s.", 319 modestr.c_str(), 320 name.c_str()); 321 return EINVAL; 322 } 323 324 int mode = 0; 325 switch (modestr[1]) { 326 case '-': 327 break; 328 case 'r': 329 mode |= S_IRUSR | S_IRGRP | S_IROTH; 330 break; 331 default: 332 LOG_ERROR("Unable to parse read %s for %s.", 333 modestr.c_str(), 334 name.c_str()); 335 return EINVAL; 336 } 337 338 switch (modestr[2]) { 339 case '-': 340 break; 341 case 'w': 342 mode |= S_IWUSR | S_IWGRP | S_IWOTH; 343 break; 344 default: 345 LOG_ERROR("Unable to parse write %s for %s.", 346 modestr.c_str(), 347 name.c_str()); 348 return EINVAL; 349 } 350 351 Path path(name); 352 std::string url = MakeUrl(path); 353 354 HttpFsNode* http_node = new HttpFsNode(this, url, cache_content_); 355 ScopedNode node(http_node); 356 node->SetMode(mode); 357 node->SetType(type); 358 359 Error error = node->Init(0); 360 if (error) 361 return error; 362 http_node->SetCachedSize(atoi(lenstr.c_str())); 363 364 ScopedNode dir_node; 365 error = FindOrCreateDir(path.Parent(), &dir_node); 366 if (error) 367 return error; 368 369 error = dir_node->AddChild(path.Basename(), node); 370 if (error) 371 return error; 372 373 node_cache_[path.Join()] = node; 374 } 375 } 376 377 return 0; 378} 379 380Error HttpFs::LoadManifest(const std::string& manifest_name, 381 char** out_manifest) { 382 Path manifest_path(manifest_name); 383 ScopedNode manifest_node; 384 *out_manifest = NULL; 385 386 int error = Open(manifest_path, O_RDONLY, &manifest_node); 387 if (error) 388 return error; 389 390 off_t size; 391 error = manifest_node->GetSize(&size); 392 if (error) 393 return error; 394 395 char* text = (char*)malloc(size + 1); 396 assert(text != NULL); 397 if (text == NULL) 398 return ENOMEM; 399 int len; 400 error = manifest_node->Read(HandleAttr(), text, size, &len); 401 if (error) 402 return error; 403 404 text[len] = 0; 405 *out_manifest = text; 406 return 0; 407} 408 409std::string HttpFs::MakeUrl(const Path& path) { 410 return url_root_ + 411 (path.IsAbsolute() ? path.Range(1, path.Size()) : path.Join()); 412} 413 414} // namespace nacl_io 415