Chromium Code Reviews| 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 "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 } | |
| OLD | NEW |