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