Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Side by Side Diff: native_client_sdk/src/libraries/nacl_mounts/mount_http.cc

Issue 12194030: Rename mount (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix whitespace Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 /* Copyright (c) 2012 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
6 #include "nacl_mounts/mount_http.h"
7 #include <assert.h>
8 #include <ctype.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <ppapi/c/pp_errors.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <vector>
17 #include "nacl_mounts/mount_node_dir.h"
18 #include "nacl_mounts/osinttypes.h"
19 #include "utils/auto_lock.h"
20
21 #if defined(WIN32)
22 #define snprintf _snprintf
23 #endif
24
25
26 typedef std::vector<char *> StringList_t;
27 static size_t SplitString(char *str, const char *delim, StringList_t* list) {
28 char *item = strtok(str, delim);
29
30 list->clear();
31 while (item) {
32 list->push_back(item);
33 item = strtok(NULL, delim);
34 }
35
36 return list->size();
37 }
38
39
40 // If we're attempting to read a partial request, but the server returns a full
41 // request, we need to read all of the data up to the start of our partial
42 // request into a dummy buffer. This is the maximum size of that buffer.
43 static const size_t MAX_READ_BUFFER_SIZE = 64 * 1024;
44 static const int32_t STATUSCODE_OK = 200;
45 static const int32_t STATUSCODE_PARTIAL_CONTENT = 206;
46
47 std::string NormalizeHeaderKey(const std::string& s) {
48 // Capitalize the first letter and any letter following a hyphen:
49 // e.g. ACCEPT-ENCODING -> Accept-Encoding
50 std::string result;
51 bool upper = true;
52 for (size_t i = 0; i < s.length(); ++i) {
53 char c = s[i];
54 result += upper ? toupper(c) : tolower(c);
55 upper = c == '-';
56 }
57
58 return result;
59 }
60
61 StringMap_t ParseHeaders(const char* headers, int32_t headers_length) {
62 enum State {
63 FINDING_KEY,
64 SKIPPING_WHITESPACE,
65 FINDING_VALUE,
66 };
67
68 StringMap_t result;
69 std::string key;
70 std::string value;
71
72 State state = FINDING_KEY;
73 const char* start = headers;
74 for (int i = 0; i < headers_length; ++i) {
75 switch (state) {
76 case FINDING_KEY:
77 if (headers[i] == ':') {
78 // Found key.
79 key.assign(start, &headers[i] - start);
80 key = NormalizeHeaderKey(key);
81 state = SKIPPING_WHITESPACE;
82 }
83 break;
84
85 case SKIPPING_WHITESPACE:
86 if (headers[i] == ' ') {
87 // Found whitespace, keep going...
88 break;
89 }
90
91 // Found a non-whitespace, mark this as the start of the value.
92 start = &headers[i];
93 state = FINDING_VALUE;
94 // Fallthrough to start processing value without incrementing i.
95
96 case FINDING_VALUE:
97 if (headers[i] == '\n') {
98 // Found value.
99 value.assign(start, &headers[i] - start);
100 result[key] = value;
101 start = &headers[i + 1];
102 state = FINDING_KEY;
103 }
104 break;
105 }
106 }
107
108 return result;
109 }
110
111 bool ParseContentLength(const StringMap_t& headers, size_t* content_length) {
112 StringMap_t::const_iterator iter = headers.find("Content-Length");
113 if (iter == headers.end())
114 return false;
115
116 *content_length = strtoul(iter->second.c_str(), NULL, 10);
117 return true;
118 }
119
120 bool ParseContentRange(const StringMap_t& headers, size_t* read_start,
121 size_t* read_end, size_t* entity_length) {
122 StringMap_t::const_iterator iter = headers.find("Content-Range");
123 if (iter == headers.end())
124 return false;
125
126 // The key should look like "bytes ##-##/##" or "bytes ##-##/*". The last
127 // value is the entity length, which can potentially be * (i.e. unknown).
128 int read_start_int;
129 int read_end_int;
130 int entity_length_int;
131 int result = sscanf(iter->second.c_str(), "bytes %"SCNuS"-%"SCNuS"/%"SCNuS,
132 &read_start_int, &read_end_int, &entity_length_int);
133
134 // The Content-Range header specifies an inclusive range: e.g. the first ten
135 // bytes is "bytes 0-9/*". Convert it to a half-open range by incrementing
136 // read_end.
137 if (result == 2) {
138 *read_start = read_start_int;
139 *read_end = read_end_int + 1;
140 *entity_length = 0;
141 return true;
142 } else if (result == 3) {
143 *read_start = read_start_int;
144 *read_end = read_end_int + 1;
145 *entity_length = entity_length_int;
146 return true;
147 }
148
149 return false;
150 }
151
152 class MountNodeHttp : public MountNode {
153 public:
154 virtual int FSync();
155 virtual int GetDents(size_t offs, struct dirent* pdir, size_t count);
156 virtual int GetStat(struct stat* stat);
157 virtual int Read(size_t offs, void* buf, size_t count);
158 virtual int Truncate(size_t size);
159 virtual int Write(size_t offs, const void* buf, size_t count);
160 virtual size_t GetSize();
161
162 protected:
163 MountNodeHttp(Mount* mount, const std::string& url);
164
165 private:
166 bool OpenUrl(const char* method,
167 StringMap_t* request_headers,
168 PP_Resource* out_loader,
169 PP_Resource* out_request,
170 PP_Resource* out_response,
171 int32_t* out_statuscode,
172 StringMap_t* out_response_headers);
173
174 std::string url_;
175 std::vector<char> buffer_;
176 friend class ::MountHttp;
177 };
178
179 int MountNodeHttp::FSync() {
180 errno = ENOSYS;
181 return -1;
182 }
183
184 int MountNodeHttp::GetDents(size_t offs, struct dirent* pdir, size_t count) {
185 errno = ENOSYS;
186 return -1;
187 }
188
189 int MountNodeHttp::GetStat(struct stat* stat) {
190 AutoLock lock(&lock_);
191
192 // Assume we need to 'HEAD' if we do not know the size, otherwise, assume
193 // that the information is constant. We can add a timeout if needed.
194 MountHttp* mount = static_cast<MountHttp*>(mount_);
195 if (stat_.st_size == 0 || !mount->allow_stat_cache_) {
196 StringMap_t headers;
197 PP_Resource loader;
198 PP_Resource request;
199 PP_Resource response;
200 int32_t statuscode;
201 StringMap_t response_headers;
202 if (!OpenUrl("HEAD", &headers, &loader, &request, &response, &statuscode,
203 &response_headers)) {
204 // errno is already set by OpenUrl.
205 return -1;
206 }
207
208 ScopedResource scoped_loader(mount_->ppapi(), loader);
209 ScopedResource scoped_request(mount_->ppapi(), request);
210 ScopedResource scoped_response(mount_->ppapi(), response);
211
212
213 size_t entity_length;
214 if (ParseContentLength(response_headers, &entity_length))
215 stat_.st_size = static_cast<off_t>(entity_length);
216 else
217 stat_.st_size = 0;
218
219 stat_.st_atime = 0; // TODO(binji): Use "Last-Modified".
220 stat_.st_mtime = 0;
221 stat_.st_ctime = 0;
222 }
223
224 // Fill the stat structure if provided
225 if (stat) {
226 memcpy(stat, &stat_, sizeof(stat_));
227 }
228 return 0;
229 }
230
231 int MountNodeHttp::Read(size_t offs, void* buf, size_t count) {
232 AutoLock lock(&lock_);
233 StringMap_t headers;
234
235 char buffer[100];
236 // Range request is inclusive: 0-99 returns 100 bytes.
237 snprintf(&buffer[0], sizeof(buffer), "bytes=%"PRIuS"-%"PRIuS,
238 offs, offs + count - 1);
239 headers["Range"] = buffer;
240
241 PP_Resource loader;
242 PP_Resource request;
243 PP_Resource response;
244 int32_t statuscode;
245 StringMap_t response_headers;
246 if (!OpenUrl("GET", &headers, &loader, &request, &response, &statuscode,
247 &response_headers)) {
248 // errno is already set by OpenUrl.
249 return 0;
250 }
251
252 PepperInterface* ppapi = mount_->ppapi();
253 ScopedResource scoped_loader(ppapi, loader);
254 ScopedResource scoped_request(ppapi, request);
255 ScopedResource scoped_response(ppapi, response);
256
257 size_t read_start = 0;
258 if (statuscode == STATUSCODE_OK) {
259 // No partial result, read everything starting from the part we care about.
260 size_t content_length;
261 if (ParseContentLength(response_headers, &content_length)) {
262 if (offs >= content_length) {
263 errno = EINVAL;
264 return 0;
265 }
266
267 // Clamp count, if trying to read past the end of the file.
268 if (offs + count > content_length) {
269 count = content_length - offs;
270 }
271 }
272 } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) {
273 // Determine from the headers where we are reading.
274 size_t read_end;
275 size_t entity_length;
276 if (ParseContentRange(response_headers, &read_start, &read_end,
277 &entity_length)) {
278 if (read_start > offs || read_start > read_end) {
279 // Shouldn't happen.
280 errno = EINVAL;
281 return 0;
282 }
283
284 // Clamp count, if trying to read past the end of the file.
285 count = std::min(read_end - read_start, count);
286 } else {
287 // Partial Content without Content-Range. Assume that the server gave us
288 // exactly what we asked for. This can happen even when the server
289 // returns 200 -- the cache may return 206 in this case, but not modify
290 // the headers.
291 read_start = offs;
292 }
293 }
294
295 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
296
297 size_t bytes_to_read;
298 int32_t bytes_read;
299 while (read_start < offs) {
300 if (!buffer_.size()) {
301 buffer_.resize(std::min(offs - read_start, MAX_READ_BUFFER_SIZE));
302 }
303
304 // We aren't yet at the location where we want to start reading. Read into
305 // our dummy buffer until then.
306 bytes_to_read = std::min(offs - read_start, buffer_.size());
307 bytes_read = loader_interface->ReadResponseBody(
308 loader, buffer_.data(), bytes_to_read, PP_BlockUntilComplete());
309
310 if (bytes_read < 0) {
311 errno = PPErrorToErrno(bytes_read);
312 return 0;
313 }
314
315 assert(bytes_read <= bytes_to_read);
316 read_start += bytes_read;
317 }
318
319 // At the read start, now we can read into the correct buffer.
320 char* out_buffer = static_cast<char*>(buf);
321 bytes_to_read = count;
322 while (bytes_to_read > 0) {
323 bytes_read = loader_interface->ReadResponseBody(
324 loader, out_buffer, bytes_to_read, PP_BlockUntilComplete());
325
326 if (bytes_read == 0) {
327 // This is not an error -- it may just be that we were trying to read
328 // more data than exists.
329 return count - bytes_to_read;
330 }
331
332 if (bytes_read < 0) {
333 errno = PPErrorToErrno(bytes_read);
334 return count - bytes_to_read;
335 }
336
337 assert(bytes_read <= bytes_to_read);
338 bytes_to_read -= bytes_read;
339 out_buffer += bytes_read;
340 }
341
342 return count;
343 }
344
345 int MountNodeHttp::Truncate(size_t size) {
346 errno = ENOSYS;
347 return -1;
348 }
349
350 int MountNodeHttp::Write(size_t offs, const void* buf, size_t count) {
351 // TODO(binji): support POST?
352 errno = ENOSYS;
353 return -1;
354 }
355
356 size_t MountNodeHttp::GetSize() {
357 // TODO(binji): This value should be cached properly; i.e. obey the caching
358 // headers returned by the server.
359 return stat_.st_size;
360 }
361
362 MountNodeHttp::MountNodeHttp(Mount* mount, const std::string& url)
363 : MountNode(mount),
364 url_(url) {
365 }
366
367 bool MountNodeHttp::OpenUrl(const char* method,
368 StringMap_t* request_headers,
369 PP_Resource* out_loader,
370 PP_Resource* out_request,
371 PP_Resource* out_response,
372 int32_t* out_statuscode,
373 StringMap_t* out_response_headers) {
374 // Assume lock_ is already held.
375
376 PepperInterface* ppapi = mount_->ppapi();
377
378 MountHttp* mount_http = static_cast<MountHttp*>(mount_);
379 ScopedResource request(ppapi,
380 mount_http->MakeUrlRequestInfo(url_, method,
381 request_headers));
382 if (!request.pp_resource()) {
383 errno = EINVAL;
384 return false;
385 }
386
387 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
388 URLResponseInfoInterface* response_interface =
389 ppapi->GetURLResponseInfoInterface();
390 VarInterface* var_interface = ppapi->GetVarInterface();
391
392 ScopedResource loader(ppapi, loader_interface->Create(ppapi->GetInstance()));
393 if (!loader.pp_resource()) {
394 errno = EINVAL;
395 return false;
396 }
397
398 int32_t result = loader_interface->Open(
399 loader.pp_resource(), request.pp_resource(), PP_BlockUntilComplete());
400 if (result != PP_OK) {
401 errno = PPErrorToErrno(result);
402 return false;
403 }
404
405 ScopedResource response(
406 ppapi,
407 loader_interface->GetResponseInfo(loader.pp_resource()));
408 if (!response.pp_resource()) {
409 errno = EINVAL;
410 return false;
411 }
412
413 // Get response statuscode.
414 PP_Var statuscode = response_interface->GetProperty(
415 response.pp_resource(),
416 PP_URLRESPONSEPROPERTY_STATUSCODE);
417
418 if (statuscode.type != PP_VARTYPE_INT32) {
419 errno = EINVAL;
420 return false;
421 }
422
423 *out_statuscode = statuscode.value.as_int;
424
425 // Only accept OK or Partial Content.
426 if (*out_statuscode != STATUSCODE_OK &&
427 *out_statuscode != STATUSCODE_PARTIAL_CONTENT) {
428 errno = EINVAL;
429 return false;
430 }
431
432 // Get response headers.
433 PP_Var response_headers_var = response_interface->GetProperty(
434 response.pp_resource(),
435 PP_URLRESPONSEPROPERTY_HEADERS);
436
437 uint32_t response_headers_length;
438 const char* response_headers_str = var_interface->VarToUtf8(
439 response_headers_var,
440 &response_headers_length);
441
442 *out_loader = loader.Release();
443 *out_request = request.Release();
444 *out_response = response.Release();
445 *out_response_headers = ParseHeaders(response_headers_str,
446 response_headers_length);
447
448 return true;
449 }
450
451 MountNode *MountHttp::Open(const Path& path, int mode) {
452 assert(url_root_.empty() || url_root_[url_root_.length() - 1] == '/');
453
454 NodeMap_t::iterator iter = node_cache_.find(path.Join());
455 if (iter != node_cache_.end()) {
456 return iter->second;
457 }
458
459 // If we can't find the node in the cache, create it
460 std::string url = url_root_ + (path.IsAbsolute() ?
461 path.Range(1, path.Size()) :
462 path.Join());
463
464 MountNodeHttp* node = new MountNodeHttp(this, url);
465 if (!node->Init(mode) || (0 != node->GetStat(NULL))) {
466 node->Release();
467 return NULL;
468 }
469
470 MountNodeDir* parent = FindOrCreateDir(path.Parent());
471 node_cache_[path.Join()] = node;
472 parent->AddChild(path.Basename(), node);
473 return node;
474 }
475
476 int MountHttp::Unlink(const Path& path) {
477 errno = ENOSYS;
478 return -1;
479 }
480
481 int MountHttp::Mkdir(const Path& path, int permissions) {
482 errno = ENOSYS;
483 return -1;
484 }
485
486 int MountHttp::Rmdir(const Path& path) {
487 errno = ENOSYS;
488 return -1;
489 }
490
491 int MountHttp::Remove(const Path& path) {
492 errno = ENOSYS;
493 return -1;
494 }
495
496 PP_Resource MountHttp::MakeUrlRequestInfo(
497 const std::string& url,
498 const char* method,
499 StringMap_t* additional_headers) {
500 URLRequestInfoInterface* interface = ppapi_->GetURLRequestInfoInterface();
501 VarInterface* var_interface = ppapi_->GetVarInterface();
502
503 PP_Resource request_info = interface->Create(ppapi_->GetInstance());
504 if (!request_info)
505 return 0;
506
507 interface->SetProperty(
508 request_info, PP_URLREQUESTPROPERTY_URL,
509 var_interface->VarFromUtf8(url.c_str(), url.length()));
510 interface->SetProperty(request_info, PP_URLREQUESTPROPERTY_METHOD,
511 var_interface->VarFromUtf8(method, strlen(method)));
512 interface->SetProperty(request_info,
513 PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS,
514 PP_MakeBool(allow_cors_ ? PP_TRUE : PP_FALSE));
515 interface->SetProperty(request_info, PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS,
516 PP_MakeBool(allow_credentials_ ? PP_TRUE : PP_FALSE));
517
518 // Merge the mount headers with the request headers. If the field is already
519 // set it |additional_headers|, don't use the one from headers_.
520 for (StringMap_t::iterator iter = headers_.begin(); iter != headers_.end();
521 ++iter) {
522 const std::string& key = NormalizeHeaderKey(iter->first);
523 if (additional_headers->find(key) == additional_headers->end()) {
524 additional_headers->insert(std::make_pair(key, iter->second));
525 }
526 }
527
528 // Join the headers into one string.
529 std::string headers;
530 for (StringMap_t::iterator iter = additional_headers->begin();
531 iter != additional_headers->end(); ++iter) {
532 headers += iter->first + ": " + iter->second + '\n';
533 }
534
535 interface->SetProperty(
536 request_info, PP_URLREQUESTPROPERTY_HEADERS,
537 var_interface->VarFromUtf8(headers.c_str(), headers.length()));
538
539 return request_info;
540 }
541
542 MountHttp::MountHttp()
543 : allow_cors_(false),
544 allow_credentials_(false),
545 allow_stat_cache_(true) {
546 }
547
548 bool MountHttp::Init(int dev, StringMap_t& args, PepperInterface* ppapi) {
549 if (!Mount::Init(dev, args, ppapi))
550 return false;
551
552 // Parse mount args.
553 for (StringMap_t::iterator iter = args.begin(); iter != args.end(); ++iter) {
554 if (iter->first == "SOURCE") {
555 url_root_ = iter->second;
556
557 // Make sure url_root_ ends with a slash.
558 if (!url_root_.empty() && url_root_[url_root_.length() - 1] != '/') {
559 url_root_ += '/';
560 }
561 } else if (iter->first == "manifest") {
562 char *text = LoadManifest(iter->second);
563 if (text != NULL) {
564 ParseManifest(text);
565 delete[] text;
566 }
567 } else if (iter->first == "allow_cross_origin_requests") {
568 allow_cors_ = iter->second == "true";
569 } else if (iter->first == "allow_credentials") {
570 allow_credentials_ = iter->second == "true";
571 } else if (iter->first == "allow_stat_cache") {
572 allow_stat_cache_ = iter->second == "true";
573 } else {
574 // Assume it is a header to pass to an HTTP request.
575 headers_[NormalizeHeaderKey(iter->first)] = iter->second;
576 }
577 }
578
579 return true;
580 }
581
582 void MountHttp::Destroy() {
583 }
584
585 MountNodeDir* MountHttp::FindOrCreateDir(const Path& path) {
586 std::string strpath = path.Join();
587 NodeMap_t::iterator iter = node_cache_.find(strpath);
588 if (iter != node_cache_.end()) {
589 return static_cast<MountNodeDir*>(iter->second);
590 }
591
592 // If the node does not exist, create it, and add it to the node cache
593 MountNodeDir* node = new MountNodeDir(this);
594 node->Init(S_IREAD);
595 node_cache_[strpath] = node;
596
597 // If not the root node, find the parent node and add it to the parent
598 if (!path.Top()) {
599 MountNodeDir* parent = FindOrCreateDir(path.Parent());
600 parent->AddChild(path.Basename(), node);
601 }
602
603 return node;
604 }
605
606 bool MountHttp::ParseManifest(char *text) {
607 StringList_t lines;
608 SplitString(text, "\n", &lines);
609
610 for (size_t i = 0; i < lines.size(); i++) {
611 StringList_t words;
612 SplitString(lines[i], " ", &words);
613
614 if (words.size() == 3) {
615 char* modestr = words[0];
616 char* lenstr = words[1];
617 char* name = words[2];
618
619 assert(modestr && strlen(modestr) == 4);
620 assert(name && name[0] == '/');
621 assert(lenstr);
622
623 // Only support regular and streams for now
624 // Ignore EXEC bit
625 int mode = S_IFREG;
626 switch (modestr[0]) {
627 case '-': mode = S_IFREG; break;
628 case 'c': mode = S_IFCHR; break;
629 default:
630 fprintf(stderr, "Unable to parse type %s for %s.\n", modestr, name);
631 return false;
632 }
633
634 switch (modestr[1]) {
635 case '-': break;
636 case 'r': mode |= S_IREAD; break;
637 default:
638 fprintf(stderr, "Unable to parse read %s for %s.\n", modestr, name);
639 return false;
640 }
641
642 switch (modestr[2]) {
643 case '-': break;
644 case 'w': mode |= S_IWRITE; break;
645 default:
646 fprintf(stderr, "Unable to parse write %s for %s.\n", modestr, name);
647 return false;
648 }
649
650 Path path(name);
651 std::string url = url_root_ + (path.IsAbsolute() ?
652 path.Range(1, path.Size()) :
653 path.Join());
654
655 MountNode* node = new MountNodeHttp(this, url);
656 node->Init(mode);
657 node->stat_.st_size = atoi(lenstr);
658
659 MountNodeDir* dir_node = FindOrCreateDir(path.Parent());
660 dir_node->AddChild(path.Basename(), node);
661
662 std::string pname = path.Join();
663 node_cache_[pname] = node;
664 }
665 }
666
667 return true;
668 }
669
670 char *MountHttp::LoadManifest(const std::string& manifestName) {
671 Path manifestPath(manifestName);
672 MountNode* manifiestNode = Open(manifestPath, O_RDONLY);
673
674 if (manifiestNode) {
675 char *text = new char[manifiestNode->GetSize() + 1];
676 off_t len = manifiestNode->Read(0, text, manifiestNode->GetSize());
677 manifiestNode->Release();
678
679 text[len] = 0;
680 return text;
681 }
682
683 fprintf(stderr, "Could not open manifest: %s\n", manifestName.c_str());
684 return NULL;
685 }
OLDNEW
« no previous file with comments | « native_client_sdk/src/libraries/nacl_mounts/mount_http.h ('k') | native_client_sdk/src/libraries/nacl_mounts/mount_mem.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698