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