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