OLD | NEW |
(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 } |
OLD | NEW |