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