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

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