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

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

Issue 11887021: [NaCl SDK] Add HTTP mount. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge Created 7 years, 11 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 <ppapi/c/pp_errors.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <vector>
14 #include "nacl_mounts/osinttypes.h"
15 #include "utils/auto_lock.h"
16
17 #if defined(WIN32)
18 #define snprintf _snprintf
19 #endif
20
21
22 namespace {
23
24 // If we're attempting to read a partial request, but the server returns a full
25 // request, we need to read all of the data up to the start of our partial
26 // request into a dummy buffer. This is the maximum size of that buffer.
27 static const size_t MAX_READ_BUFFER_SIZE = 64 * 1024;
28 static const int32_t STATUSCODE_OK = 200;
29 static const int32_t STATUSCODE_PARTIAL_CONTENT = 206;
30
31 std::string NormalizeHeaderKey(const std::string& s) {
32 // Capitalize the first letter and any letter following a hyphen:
33 // e.g. ACCEPT-ENCODING -> Accept-Encoding
34 std::string result;
35 bool upper = true;
36 for (size_t i = 0; i < s.length(); ++i) {
37 char c = s[i];
38 result += upper ? toupper(c) : tolower(c);
39 upper = c == '-';
40 }
41
42 return result;
43 }
44
45 StringMap_t ParseHeaders(const char* headers, int32_t headers_length) {
46 enum State {
47 FINDING_KEY,
48 SKIPPING_WHITESPACE,
49 FINDING_VALUE,
50 };
51
52 StringMap_t result;
53 std::string key;
54 std::string value;
55
56 State state = FINDING_KEY;
57 const char* start = headers;
58 for (int i = 0; i < headers_length; ++i) {
59 switch (state) {
60 case FINDING_KEY:
61 if (headers[i] == ':') {
62 // Found key.
63 key.assign(start, &headers[i] - start);
64 key = NormalizeHeaderKey(key);
65 state = SKIPPING_WHITESPACE;
66 }
67 break;
68
69 case SKIPPING_WHITESPACE:
70 if (headers[i] == ' ') {
71 // Found whitespace, keep going...
72 break;
73 }
74
75 // Found a non-whitespace, mark this as the start of the value.
76 start = &headers[i + 1];
77 state = FINDING_VALUE;
78 // Fallthrough to start processing value without incrementing i.
79
80 case FINDING_VALUE:
81 if (headers[i] == '\n') {
82 // Found value.
83 value.assign(start, &headers[i] - start);
84 result[key] = value;
85
86 start = &headers[i + 1];
87 state = FINDING_KEY;
88 }
89 break;
90 }
91 }
92
93 return result;
94 }
95
96 bool ParseContentLength(const StringMap_t& headers, size_t* content_length) {
97 StringMap_t::const_iterator iter = headers.find("Content-Length");
98 if (iter == headers.end())
99 return false;
100
101 *content_length = strtoul(iter->second.c_str(), NULL, 10);
102 return true;
103 }
104
105 bool ParseContentRange(const StringMap_t& headers, size_t* read_start,
106 size_t* read_end, size_t* entity_length) {
107 StringMap_t::const_iterator iter = headers.find("Content-Range");
108 if (iter == headers.end())
109 return false;
110
111 // The key should look like "bytes ##-##/##" or "bytes ##-##/*". The last
112 // value is the entity length, which can potentially be * (i.e. unknown).
113 int read_start_int;
114 int read_end_int;
115 int entity_length_int;
116 int result = sscanf(iter->second.c_str(), "bytes %"SCNuS"-%"SCNuS"/%"SCNuS,
117 &read_start_int, &read_end_int, &entity_length_int);
118
119 if (result == 2) {
120 *read_start = read_start_int;
121 *read_end = read_end_int;
122 *entity_length = 0;
123 return true;
124 } else if (result == 3) {
125 *read_start = read_start_int;
126 *read_end = read_end_int;
127 *entity_length = entity_length_int;
128 return true;
129 }
130
131 return false;
132 }
133
134 class MountNodeHttp : public MountNode {
135 public:
136 virtual int FSync();
137 virtual int GetDents(size_t offs, struct dirent* pdir, size_t count);
138 virtual int GetStat(struct stat* stat);
139 virtual int Read(size_t offs, void* buf, size_t count);
140 virtual int Truncate(size_t size);
141 virtual int Write(size_t offs, const void* buf, size_t count);
142 virtual size_t GetSize();
143
144 protected:
145 MountNodeHttp(Mount* mount, int ino, int dev, const std::string& url);
146 virtual bool Init(int mode, short uid, short gid);
147 virtual int Close();
148
149 private:
150 bool OpenUrl(const char* method,
151 StringMap_t* request_headers,
152 PP_Resource* out_loader,
153 PP_Resource* out_request,
154 PP_Resource* out_response,
155 int32_t* out_statuscode,
156 StringMap_t* out_response_headers);
157
158 std::string url_;
159 std::vector<char> buffer_;
160
161 friend class ::MountHttp;
162 };
163
164 int MountNodeHttp::FSync() {
165 errno = ENOSYS;
166 return -1;
167 }
168
169 int MountNodeHttp::GetDents(size_t offs, struct dirent* pdir, size_t count) {
170 errno = ENOSYS;
171 return -1;
172 }
173
174 int MountNodeHttp::GetStat(struct stat* stat) {
175 AutoLock lock(&lock_);
176
177 StringMap_t headers;
178 PP_Resource loader;
179 PP_Resource request;
180 PP_Resource response;
181 int32_t statuscode;
182 StringMap_t response_headers;
183 if (!OpenUrl("HEAD", &headers, &loader, &request, &response, &statuscode,
184 &response_headers)) {
185 // errno is already set by OpenUrl.
186 return -1;
187 }
188
189 ScopedResource scoped_loader(mount_->ppapi(), loader);
190 ScopedResource scoped_request(mount_->ppapi(), request);
191 ScopedResource scoped_response(mount_->ppapi(), response);
192
193 // Fill in known info here.
194 memcpy(stat, &stat_, sizeof(stat_));
195
196 size_t entity_length;
197 if (ParseContentLength(response_headers, &entity_length))
198 stat->st_size = static_cast<off_t>(entity_length);
199 else
200 stat->st_size = 0;
201
202 stat->st_atime = 0; // TODO(binji): Use "Last-Modified".
203 stat->st_mtime = 0;
204 stat->st_ctime = 0;
205
206 return 0;
207 }
208
209 int MountNodeHttp::Read(size_t offs, void* buf, size_t count) {
210 AutoLock lock(&lock_);
211 StringMap_t headers;
212
213 char buffer[100];
214 // Range request is inclusive: 0-99 returns 100 bytes.
215 snprintf(&buffer[0], sizeof(buffer), "bytes=%"PRIuS"-%"PRIuS,
216 offs, offs + count - 1);
217 headers["Range"] = buffer;
218
219 PP_Resource loader;
220 PP_Resource request;
221 PP_Resource response;
222 int32_t statuscode;
223 StringMap_t response_headers;
224 if (!OpenUrl("GET", &headers, &loader, &request, &response, &statuscode,
225 &response_headers)) {
226 // errno is already set by OpenUrl.
227 return 0;
228 }
229
230 PepperInterface* ppapi = mount_->ppapi();
231 ScopedResource scoped_loader(ppapi, loader);
232 ScopedResource scoped_request(ppapi, request);
233 ScopedResource scoped_response(ppapi, response);
234
235 size_t read_start = 0;
236 if (statuscode == STATUSCODE_OK) {
237 // No partial result, read everything starting from the part we care about.
238 size_t content_length;
239 if (ParseContentLength(response_headers, &content_length)) {
240 if (offs >= content_length) {
241 errno = EINVAL;
242 return 0;
243 }
244
245 // Clamp count, if trying to read past the end of the file.
246 if (offs + count > content_length) {
247 count = content_length - offs;
248 }
249 }
250 } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) {
251 // Determine from the headers where we are reading.
252 size_t read_end;
253 size_t entity_length;
254 if (ParseContentRange(response_headers, &read_start, &read_end,
255 &entity_length)) {
256 if (read_start > offs || read_start > read_end) {
257 // Shouldn't happen.
258 errno = EINVAL;
259 return 0;
260 }
261
262 // Clamp count, if trying to read past the end of the file.
263 count = std::min(read_end - read_start, count);
264 } else {
265 // Partial Content without Content-Range. Assume that the server gave us
266 // exactly what we asked for. This can happen even when the server
267 // returns 200 -- the cache may return 206 in this case, but not modify
268 // the headers.
269 read_start = offs;
270 }
271 }
272
273 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
274
275 size_t bytes_to_read;
276 int32_t bytes_read;
277 while (read_start < offs) {
278 if (!buffer_.size()) {
279 buffer_.resize(std::min(offs - read_start, MAX_READ_BUFFER_SIZE));
280 }
281
282 // We aren't yet at the location where we want to start reading. Read into
283 // our dummy buffer until then.
284 bytes_to_read = std::min(offs - read_start, buffer_.size());
285 bytes_read = loader_interface->ReadResponseBody(
286 loader, buffer_.data(), bytes_to_read, PP_BlockUntilComplete());
287
288 if (bytes_read < 0) {
289 errno = PPErrorToErrno(bytes_read);
290 return 0;
291 }
292
293 assert(bytes_read <= bytes_to_read);
294 read_start += bytes_read;
295 }
296
297 // At the read start, now we can read into the correct buffer.
298 char* out_buffer = static_cast<char*>(buf);
299 bytes_to_read = count;
300 while (bytes_to_read > 0) {
301 bytes_read = loader_interface->ReadResponseBody(
302 loader, out_buffer, bytes_to_read, PP_BlockUntilComplete());
303
304 if (bytes_read == 0) {
305 // This is not an error -- it may just be that we were trying to read
306 // more data than exists.
307 return count - bytes_to_read;
308 }
309
310 if (bytes_read < 0) {
311 errno = PPErrorToErrno(bytes_read);
312 return count - bytes_to_read;
313 }
314
315 assert(bytes_read <= bytes_to_read);
316 bytes_to_read -= bytes_read;
317 out_buffer += bytes_read;
318 }
319
320 return count;
321 }
322
323 int MountNodeHttp::Truncate(size_t size) {
324 errno = ENOSYS;
325 return -1;
326 }
327
328 int MountNodeHttp::Write(size_t offs, const void* buf, size_t count) {
329 // TODO(binji): supprt POST?
330 errno = ENOSYS;
331 return -1;
332 }
333
334 size_t MountNodeHttp::GetSize() {
335 struct stat stat;
336 if (GetStat(&stat) == -1) {
337 // errno is already set by GetStat.
338 return -1;
339 }
340
341 return stat.st_size;
342 }
343
344 MountNodeHttp::MountNodeHttp(Mount* mount, int ino, int dev,
345 const std::string& url)
346 : MountNode(mount, ino, dev),
347 url_(url) {
348 }
349
350 bool MountNodeHttp::Init(int mode, short uid, short gid) {
351 if (!MountNode::Init(mode, uid, gid)) {
352 return false;
353 }
354
355 struct stat stat;
356 if (GetStat(&stat) == -1) {
357 return false;
358 }
359
360 memcpy(&stat_, &stat, sizeof(stat_));
361
362 return true;
363 }
364
365 int MountNodeHttp::Close() {
366 return 0;
367 }
368
369 bool MountNodeHttp::OpenUrl(const char* method,
370 StringMap_t* request_headers,
371 PP_Resource* out_loader,
372 PP_Resource* out_request,
373 PP_Resource* out_response,
374 int32_t* out_statuscode,
375 StringMap_t* out_response_headers) {
376 // Assume lock_ is already held.
377
378 PepperInterface* ppapi = mount_->ppapi();
379
380 MountHttp* mount_http = static_cast<MountHttp*>(mount_);
381 ScopedResource request(ppapi,
382 mount_http->MakeUrlRequestInfo(url_, method,
383 request_headers));
384 if (!request.pp_resource()) {
385 errno = EINVAL;
386 return false;
387 }
388
389 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
390 URLResponseInfoInterface* response_interface =
391 ppapi->GetURLResponseInfoInterface();
392 VarInterface* var_interface = ppapi->GetVarInterface();
393
394 ScopedResource loader(ppapi, loader_interface->Create(ppapi->GetInstance()));
395 if (!loader.pp_resource()) {
396 errno = EINVAL;
397 return false;
398 }
399
400 int32_t result = loader_interface->Open(
401 loader.pp_resource(), request.pp_resource(), PP_BlockUntilComplete());
402 if (result != PP_OK) {
403 errno = PPErrorToErrno(result);
404 return false;
405 }
406
407 ScopedResource response(
408 ppapi,
409 loader_interface->GetResponseInfo(loader.pp_resource()));
410 if (!response.pp_resource()) {
411 errno = EINVAL;
412 return false;
413 }
414
415 // Get response statuscode.
416 PP_Var statuscode = response_interface->GetProperty(
417 response.pp_resource(),
418 PP_URLRESPONSEPROPERTY_STATUSCODE);
419
420 if (statuscode.type != PP_VARTYPE_INT32) {
421 errno = EINVAL;
422 return false;
423 }
424
425 *out_statuscode = statuscode.value.as_int;
426
427 // Only accept OK or Partial Content.
428 if (*out_statuscode != STATUSCODE_OK &&
429 *out_statuscode != STATUSCODE_PARTIAL_CONTENT) {
430 errno = EINVAL;
431 return false;
432 }
433
434 // Get response headers.
435 PP_Var response_headers_var = response_interface->GetProperty(
436 response.pp_resource(),
437 PP_URLRESPONSEPROPERTY_HEADERS);
438
439 uint32_t response_headers_length;
440 const char* response_headers_str = var_interface->VarToUtf8(
441 response_headers_var,
442 &response_headers_length);
443
444 *out_loader = loader.Release();
445 *out_request = request.Release();
446 *out_response = response.Release();
447 *out_response_headers = ParseHeaders(response_headers_str,
448 response_headers_length);
449
450 return true;
451 }
452
453
454 } // namespace
455
456 MountNode *MountHttp::Open(const Path& path, int mode) {
457 assert(url_root_.empty() || url_root_[url_root_.length() - 1] == '/');
458
459 std::string url = url_root_ + (path.IsAbsolute() ?
460 path.Range(1, path.Size()) :
461 path.Join());
462
463 const int ino = 1;
464 const int USR_ID = 1001;
465 const int GRP_ID = 1002;
466 MountNodeHttp* node = new MountNodeHttp(this, ino, dev_, url);
467 if (!node->Init(mode, USR_ID, GRP_ID)) {
468 node->Release();
469 return NULL;
470 }
471
472 return node;
473 }
474
475 int MountHttp::Close(MountNode* node) {
476 AutoLock lock(&lock_);
477 node->Close();
478 node->Release();
479 return 0;
480 }
481
482 int MountHttp::Unlink(const Path& path) {
483 errno = ENOSYS;
484 return -1;
485 }
486
487 int MountHttp::Mkdir(const Path& path, int permissions) {
488 errno = ENOSYS;
489 return -1;
490 }
491
492 int MountHttp::Rmdir(const Path& path) {
493 errno = ENOSYS;
494 return -1;
495 }
496
497 int MountHttp::Remove(const Path& path) {
498 errno = ENOSYS;
499 return -1;
500 }
501
502 PP_Resource MountHttp::MakeUrlRequestInfo(
503 const std::string& url,
504 const char* method,
505 StringMap_t* additional_headers) {
506 URLRequestInfoInterface* interface = ppapi_->GetURLRequestInfoInterface();
507 VarInterface* var_interface = ppapi_->GetVarInterface();
508
509 PP_Resource request_info = interface->Create(ppapi_->GetInstance());
510 if (!request_info)
511 return 0;
512
513 interface->SetProperty(
514 request_info, PP_URLREQUESTPROPERTY_URL,
515 var_interface->VarFromUtf8(url.c_str(), url.length()));
516 interface->SetProperty(request_info, PP_URLREQUESTPROPERTY_METHOD,
517 var_interface->VarFromUtf8(method, strlen(method)));
518 interface->SetProperty(request_info,
519 PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS,
520 PP_MakeBool(allow_cors_ ? PP_TRUE : PP_FALSE));
521 interface->SetProperty(request_info, PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS,
522 PP_MakeBool(allow_credentials_ ? PP_TRUE : PP_FALSE));
523
524 // Merge the mount headers with the request headers. If the field is already
525 // set it |additional_headers|, don't use the one from headers_.
526 for (StringMap_t::iterator iter = headers_.begin(); iter != headers_.end();
527 ++iter) {
528 const std::string& key = NormalizeHeaderKey(iter->first);
529 if (additional_headers->find(key) == additional_headers->end()) {
530 additional_headers->insert(std::make_pair(key, iter->second));
531 }
532 }
533
534 // Join the headers into one string.
535 std::string headers;
536 for (StringMap_t::iterator iter = additional_headers->begin();
537 iter != additional_headers->end(); ++iter) {
538 headers += iter->first + ": " + iter->second + '\n';
539 }
540
541 interface->SetProperty(
542 request_info, PP_URLREQUESTPROPERTY_HEADERS,
543 var_interface->VarFromUtf8(headers.c_str(), headers.length()));
544
545 return request_info;
546 }
547
548 MountHttp::MountHttp()
549 : allow_cors_(false),
550 allow_credentials_(false) {
551 }
552
553 bool MountHttp::Init(int dev, StringMap_t& args, PepperInterface* ppapi) {
554 if (!Mount::Init(dev, args, ppapi))
555 return false;
556
557 // Parse mount args.
558 for (StringMap_t::iterator iter = args.begin(); iter != args.end(); ++iter) {
559 if (iter->first == "SOURCE") {
560 url_root_ = iter->second;
561
562 // Make sure url_root_ ends with a slash.
563 if (!url_root_.empty() && url_root_[url_root_.length() - 1] != '/') {
564 url_root_ += '/';
565 }
566 } else if (iter->first == "allow_cross_origin_requests") {
567 allow_cors_ = iter->second == "true";
568 } else if (iter->first == "allow_credentials") {
569 allow_credentials_ = iter->second == "true";
570 } else {
571 // Assume it is a header to pass to an HTTP request.
572 headers_[NormalizeHeaderKey(iter->first)] = iter->second;
573 }
574 }
575
576 return true;
577 }
578
579 void MountHttp::Destroy() {
580 }
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_node.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698