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

Side by Side Diff: native_client_sdk/src/libraries/nacl_io/mount_node_http.cc

Issue 16232016: [NaCl SDK] nacl_io: big refactor to return error value (errno). (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge master, fix windows Created 7 years, 6 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) 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
6 #include "nacl_io/mount_node_http.h"
7
8 #include <assert.h>
9 #include <errno.h>
10 #include <stdio.h>
11 #include <string.h>
12
13 #include <ppapi/c/pp_errors.h>
14
15 #include "nacl_io/mount_http.h"
16 #include "nacl_io/osinttypes.h"
17
18 #if defined(WIN32)
19 #define snprintf _snprintf
20 #endif
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 const size_t MAX_READ_BUFFER_SIZE = 64 * 1024;
28 const int32_t STATUSCODE_OK = 200;
29 const int32_t STATUSCODE_PARTIAL_CONTENT = 206;
30
31 StringMap_t ParseHeaders(const char* headers, int32_t headers_length) {
32 enum State {
33 FINDING_KEY,
34 SKIPPING_WHITESPACE,
35 FINDING_VALUE,
36 };
37
38 StringMap_t result;
39 std::string key;
40 std::string value;
41
42 State state = FINDING_KEY;
43 const char* start = headers;
44 for (int i = 0; i < headers_length; ++i) {
45 switch (state) {
46 case FINDING_KEY:
47 if (headers[i] == ':') {
48 // Found key.
49 key.assign(start, &headers[i] - start);
50 key = NormalizeHeaderKey(key);
51 state = SKIPPING_WHITESPACE;
52 }
53 break;
54
55 case SKIPPING_WHITESPACE:
56 if (headers[i] == ' ') {
57 // Found whitespace, keep going...
58 break;
59 }
60
61 // Found a non-whitespace, mark this as the start of the value.
62 start = &headers[i];
63 state = FINDING_VALUE;
64 // Fallthrough to start processing value without incrementing i.
65
66 case FINDING_VALUE:
67 if (headers[i] == '\n') {
68 // Found value.
69 value.assign(start, &headers[i] - start);
70 result[key] = value;
71 start = &headers[i + 1];
72 state = FINDING_KEY;
73 }
74 break;
75 }
76 }
77
78 return result;
79 }
80
81 bool ParseContentLength(const StringMap_t& headers, size_t* content_length) {
82 StringMap_t::const_iterator iter = headers.find("Content-Length");
83 if (iter == headers.end())
84 return false;
85
86 *content_length = strtoul(iter->second.c_str(), NULL, 10);
87 return true;
88 }
89
90 bool ParseContentRange(const StringMap_t& headers,
91 size_t* read_start,
92 size_t* read_end,
93 size_t* entity_length) {
94 StringMap_t::const_iterator iter = headers.find("Content-Range");
95 if (iter == headers.end())
96 return false;
97
98 // The key should look like "bytes ##-##/##" or "bytes ##-##/*". The last
99 // value is the entity length, which can potentially be * (i.e. unknown).
100 int read_start_int;
101 int read_end_int;
102 int entity_length_int;
103 int result = sscanf(iter->second.c_str(),
104 "bytes %" SCNuS "-%" SCNuS "/%" SCNuS,
105 &read_start_int,
106 &read_end_int,
107 &entity_length_int);
108
109 // The Content-Range header specifies an inclusive range: e.g. the first ten
110 // bytes is "bytes 0-9/*". Convert it to a half-open range by incrementing
111 // read_end.
112 if (result == 2) {
113 *read_start = read_start_int;
114 *read_end = read_end_int + 1;
115 *entity_length = 0;
116 return true;
117 } else if (result == 3) {
118 *read_start = read_start_int;
119 *read_end = read_end_int + 1;
120 *entity_length = entity_length_int;
121 return true;
122 }
123
124 return false;
125 }
126
127 } // namespace
128
129 void MountNodeHttp::SetCachedSize(off_t size) {
130 has_cached_size_ = true;
131 stat_.st_size = size;
132 }
133
134 Error MountNodeHttp::FSync() { return ENOSYS; }
135
136 Error MountNodeHttp::GetDents(size_t offs,
137 struct dirent* pdir,
138 size_t count,
139 int* out_bytes) {
140 *out_bytes = 0;
141 return ENOSYS;
142 }
143
144 Error MountNodeHttp::GetStat(struct stat* stat) {
145 AutoLock lock(&lock_);
146
147 // Assume we need to 'HEAD' if we do not know the size, otherwise, assume
148 // that the information is constant. We can add a timeout if needed.
149 MountHttp* mount = static_cast<MountHttp*>(mount_);
150 if (stat_.st_size == 0 || !mount->cache_stat_) {
151 StringMap_t headers;
152 PP_Resource loader;
153 PP_Resource request;
154 PP_Resource response;
155 int32_t statuscode;
156 StringMap_t response_headers;
157 Error error = OpenUrl("HEAD",
158 &headers,
159 &loader,
160 &request,
161 &response,
162 &statuscode,
163 &response_headers);
164 if (error)
165 return error;
166
167 ScopedResource scoped_loader(mount_->ppapi(), loader);
168 ScopedResource scoped_request(mount_->ppapi(), request);
169 ScopedResource scoped_response(mount_->ppapi(), response);
170
171 size_t entity_length;
172 if (ParseContentLength(response_headers, &entity_length)) {
173 SetCachedSize(static_cast<off_t>(entity_length));
174 } else if (cache_content_ && !has_cached_size_) {
175 error = DownloadToCache();
176 // TODO(binji): this error should not be dropped, but it requires a bit
177 // of a refactor of the tests. See crbug.com/245431
178 // if (error)
179 // return error;
180 } else {
181 // Don't use SetCachedSize here -- it is actually unknown.
182 stat_.st_size = 0;
183 }
184
185 stat_.st_atime = 0; // TODO(binji): Use "Last-Modified".
186 stat_.st_mtime = 0;
187 stat_.st_ctime = 0;
188 }
189
190 // Fill the stat structure if provided
191 if (stat)
192 memcpy(stat, &stat_, sizeof(stat_));
193
194 return 0;
195 }
196
197 Error MountNodeHttp::Read(size_t offs,
198 void* buf,
199 size_t count,
200 int* out_bytes) {
201 *out_bytes = 0;
202
203 AutoLock lock(&lock_);
204 if (cache_content_) {
205 if (cached_data_.empty()) {
206 Error error = DownloadToCache();
207 if (error)
208 return error;
209 }
210
211 return ReadPartialFromCache(offs, buf, count, out_bytes);
212 }
213
214 return DownloadPartial(offs, buf, count, out_bytes);
215 }
216
217 Error MountNodeHttp::FTruncate(off_t size) { return ENOSYS; }
218
219 Error MountNodeHttp::Write(size_t offs,
220 const void* buf,
221 size_t count,
222 int* out_bytes) {
223 // TODO(binji): support POST?
224 *out_bytes = 0;
225 return ENOSYS;
226 }
227
228 Error MountNodeHttp::GetSize(size_t* out_size) {
229 *out_size = 0;
230
231 // TODO(binji): This value should be cached properly; i.e. obey the caching
232 // headers returned by the server.
233 AutoLock lock(&lock_);
234 if (!has_cached_size_) {
235 // Even if DownloadToCache fails, the best result we can return is what
236 // was written to stat_.st_size.
237 if (cache_content_) {
238 Error error = DownloadToCache();
239 if (error)
240 return error;
241 }
242 }
243
244 *out_size = stat_.st_size;
245 return 0;
246 }
247
248 MountNodeHttp::MountNodeHttp(Mount* mount,
249 const std::string& url,
250 bool cache_content)
251 : MountNode(mount),
252 url_(url),
253 cache_content_(cache_content),
254 has_cached_size_(false) {}
255
256 Error MountNodeHttp::OpenUrl(const char* method,
257 StringMap_t* request_headers,
258 PP_Resource* out_loader,
259 PP_Resource* out_request,
260 PP_Resource* out_response,
261 int32_t* out_statuscode,
262 StringMap_t* out_response_headers) {
263 // Assume lock_ is already held.
264 PepperInterface* ppapi = mount_->ppapi();
265
266 MountHttp* mount_http = static_cast<MountHttp*>(mount_);
267 ScopedResource request(
268 ppapi, mount_http->MakeUrlRequestInfo(url_, method, request_headers));
269 if (!request.pp_resource())
270 return EINVAL;
271
272 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
273 URLResponseInfoInterface* response_interface =
274 ppapi->GetURLResponseInfoInterface();
275 VarInterface* var_interface = ppapi->GetVarInterface();
276
277 ScopedResource loader(ppapi, loader_interface->Create(ppapi->GetInstance()));
278 if (!loader.pp_resource())
279 return EINVAL;
280
281 int32_t result = loader_interface->Open(
282 loader.pp_resource(), request.pp_resource(), PP_BlockUntilComplete());
283 if (result != PP_OK)
284 return PPErrorToErrno(result);
285
286 ScopedResource response(
287 ppapi, loader_interface->GetResponseInfo(loader.pp_resource()));
288 if (!response.pp_resource())
289 return EINVAL;
290
291 // Get response statuscode.
292 PP_Var statuscode = response_interface->GetProperty(
293 response.pp_resource(), PP_URLRESPONSEPROPERTY_STATUSCODE);
294
295 if (statuscode.type != PP_VARTYPE_INT32)
296 return EINVAL;
297
298 *out_statuscode = statuscode.value.as_int;
299
300 // Only accept OK or Partial Content.
301 if (*out_statuscode != STATUSCODE_OK &&
302 *out_statuscode != STATUSCODE_PARTIAL_CONTENT) {
303 return EINVAL;
304 }
305
306 // Get response headers.
307 PP_Var response_headers_var = response_interface->GetProperty(
308 response.pp_resource(), PP_URLRESPONSEPROPERTY_HEADERS);
309
310 uint32_t response_headers_length;
311 const char* response_headers_str =
312 var_interface->VarToUtf8(response_headers_var, &response_headers_length);
313
314 *out_loader = loader.Release();
315 *out_request = request.Release();
316 *out_response = response.Release();
317 *out_response_headers =
318 ParseHeaders(response_headers_str, response_headers_length);
319
320 return 0;
321 }
322
323 Error MountNodeHttp::DownloadToCache() {
324 StringMap_t headers;
325 PP_Resource loader;
326 PP_Resource request;
327 PP_Resource response;
328 int32_t statuscode;
329 StringMap_t response_headers;
330 Error error = OpenUrl("GET",
331 &headers,
332 &loader,
333 &request,
334 &response,
335 &statuscode,
336 &response_headers);
337 if (error)
338 return error;
339
340 PepperInterface* ppapi = mount_->ppapi();
341 ScopedResource scoped_loader(ppapi, loader);
342 ScopedResource scoped_request(ppapi, request);
343 ScopedResource scoped_response(ppapi, response);
344
345 size_t content_length = 0;
346 if (ParseContentLength(response_headers, &content_length)) {
347 cached_data_.resize(content_length);
348 int real_size;
349 error = DownloadToBuffer(
350 loader, cached_data_.data(), content_length, &real_size);
351 if (error)
352 return error;
353
354 SetCachedSize(real_size);
355 cached_data_.resize(real_size);
356 return 0;
357 }
358
359 // We don't know how big the file is. Read in chunks.
360 cached_data_.resize(MAX_READ_BUFFER_SIZE);
361 size_t total_bytes_read = 0;
362 size_t bytes_to_read = MAX_READ_BUFFER_SIZE;
363 while (true) {
364 char* buf = cached_data_.data() + total_bytes_read;
365 int bytes_read;
366 error = DownloadToBuffer(loader, buf, bytes_to_read, &bytes_read);
367 if (error)
368 return error;
369
370 total_bytes_read += bytes_read;
371
372 if (bytes_read < bytes_to_read) {
373 SetCachedSize(total_bytes_read);
374 cached_data_.resize(total_bytes_read);
375 return 0;
376 }
377
378 cached_data_.resize(total_bytes_read + bytes_to_read);
379 }
380 }
381
382 Error MountNodeHttp::ReadPartialFromCache(size_t offs,
383 void* buf,
384 size_t count,
385 int* out_bytes) {
386 *out_bytes = 0;
387
388 if (offs > cached_data_.size())
389 return EINVAL;
390
391 count = std::min(count, cached_data_.size() - offs);
392 memcpy(buf, &cached_data_.data()[offs], count);
393
394 *out_bytes = count;
395 return 0;
396 }
397
398 Error MountNodeHttp::DownloadPartial(size_t offs,
399 void* buf,
400 size_t count,
401 int* out_bytes) {
402 *out_bytes = 0;
403
404 StringMap_t headers;
405
406 char buffer[100];
407 // Range request is inclusive: 0-99 returns 100 bytes.
408 snprintf(&buffer[0],
409 sizeof(buffer),
410 "bytes=%" PRIuS "-%" PRIuS,
411 offs,
412 offs + count - 1);
413 headers["Range"] = buffer;
414
415 PP_Resource loader;
416 PP_Resource request;
417 PP_Resource response;
418 int32_t statuscode;
419 StringMap_t response_headers;
420 Error error = OpenUrl("GET",
421 &headers,
422 &loader,
423 &request,
424 &response,
425 &statuscode,
426 &response_headers);
427 if (error)
428 return error;
429
430 PepperInterface* ppapi = mount_->ppapi();
431 ScopedResource scoped_loader(ppapi, loader);
432 ScopedResource scoped_request(ppapi, request);
433 ScopedResource scoped_response(ppapi, response);
434
435 size_t read_start = 0;
436 if (statuscode == STATUSCODE_OK) {
437 // No partial result, read everything starting from the part we care about.
438 size_t content_length;
439 if (ParseContentLength(response_headers, &content_length)) {
440 if (offs >= content_length)
441 return EINVAL;
442
443 // Clamp count, if trying to read past the end of the file.
444 if (offs + count > content_length) {
445 count = content_length - offs;
446 }
447 }
448 } else if (statuscode == STATUSCODE_PARTIAL_CONTENT) {
449 // Determine from the headers where we are reading.
450 size_t read_end;
451 size_t entity_length;
452 if (ParseContentRange(
453 response_headers, &read_start, &read_end, &entity_length)) {
454 if (read_start > offs || read_start > read_end) {
455 // If this error occurs, the server is returning bogus values.
456 return EINVAL;
457 }
458
459 // Clamp count, if trying to read past the end of the file.
460 count = std::min(read_end - read_start, count);
461 } else {
462 // Partial Content without Content-Range. Assume that the server gave us
463 // exactly what we asked for. This can happen even when the server
464 // returns 200 -- the cache may return 206 in this case, but not modify
465 // the headers.
466 read_start = offs;
467 }
468 }
469
470 if (read_start < offs) {
471 // We aren't yet at the location where we want to start reading. Read into
472 // our dummy buffer until then.
473 size_t bytes_to_read = offs - read_start;
474 if (buffer_.size() < bytes_to_read)
475 buffer_.resize(std::min(bytes_to_read, MAX_READ_BUFFER_SIZE));
476
477 while (bytes_to_read > 0) {
478 int32_t bytes_read;
479 Error error =
480 DownloadToBuffer(loader, buffer_.data(), buffer_.size(), &bytes_read);
481 if (error)
482 return error;
483
484 bytes_to_read -= bytes_read;
485 }
486 }
487
488 return DownloadToBuffer(loader, buf, count, out_bytes);
489 }
490
491 Error MountNodeHttp::DownloadToBuffer(PP_Resource loader,
492 void* buf,
493 size_t count,
494 int* out_bytes) {
495 *out_bytes = 0;
496
497 PepperInterface* ppapi = mount_->ppapi();
498 URLLoaderInterface* loader_interface = ppapi->GetURLLoaderInterface();
499
500 char* out_buffer = static_cast<char*>(buf);
501 size_t bytes_to_read = count;
502 while (bytes_to_read > 0) {
503 int32_t bytes_read = loader_interface->ReadResponseBody(
504 loader, out_buffer, bytes_to_read, PP_BlockUntilComplete());
505
506 if (bytes_read == 0) {
507 // This is not an error -- it may just be that we were trying to read
508 // more data than exists.
509 *out_bytes = count - bytes_to_read;
510 return 0;
511 }
512
513 if (bytes_read < 0)
514 return PPErrorToErrno(bytes_read);
515
516 assert(bytes_read <= bytes_to_read);
517 bytes_to_read -= bytes_read;
518 out_buffer += bytes_read;
519 }
520
521 *out_bytes = count;
522 return 0;
523 }
OLDNEW
« no previous file with comments | « native_client_sdk/src/libraries/nacl_io/mount_node_http.h ('k') | native_client_sdk/src/libraries/nacl_io/mount_node_mem.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698