fake_pepper_interface_url_loader.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2014 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 "fake_ppapi/fake_pepper_interface_url_loader.h" 6 7#include <string.h> 8#include <strings.h> 9 10#include <algorithm> 11#include <sstream> 12 13#include "gtest/gtest.h" 14 15#include "nacl_io/osinttypes.h" 16 17namespace { 18 19bool GetHeaderValue(const std::string& headers, 20 const std::string& key, 21 std::string* out_value) { 22 out_value->clear(); 23 24 size_t offset = 0; 25 while (offset != std::string::npos) { 26 // Find the next colon; this separates the key from the value. 27 size_t colon = headers.find(':', offset); 28 if (colon == std::string::npos) 29 return false; 30 31 // Find the newline; this separates the value from the next header. 32 size_t newline = headers.find('\n', offset); 33 if (strncasecmp(key.c_str(), &headers.data()[offset], key.size()) != 0) { 34 // Key doesn't match, skip to next header. 35 offset = newline; 36 continue; 37 } 38 39 // Key matches, extract value. First, skip leading spaces. 40 size_t nonspace = headers.find_first_not_of(' ', colon + 1); 41 if (nonspace == std::string::npos) 42 return false; 43 44 out_value->assign(headers, nonspace, newline - nonspace); 45 return true; 46 } 47 48 return false; 49} 50 51class FakeInstanceResource : public FakeResource { 52 public: 53 FakeInstanceResource() : server_template(NULL) {} 54 static const char* classname() { return "FakeInstanceResource"; } 55 56 FakeURLLoaderServer* server_template; // Weak reference. 57}; 58 59class FakeURLLoaderResource : public FakeResource { 60 public: 61 FakeURLLoaderResource() 62 : manager(NULL), 63 server(NULL), 64 entity(NULL), 65 response(0), 66 read_offset(0) {} 67 68 virtual void Destroy() { 69 EXPECT_TRUE(manager != NULL); 70 if (response != 0) 71 manager->Release(response); 72 delete server; 73 } 74 75 static const char* classname() { return "FakeURLLoaderResource"; } 76 77 FakeResourceManager* manager; // Weak reference. 78 FakeURLLoaderServer* server; 79 FakeURLLoaderEntity* entity; // Weak reference. 80 PP_Resource response; 81 off_t read_offset; 82 off_t read_end; 83}; 84 85class FakeURLRequestInfoResource : public FakeResource { 86 public: 87 FakeURLRequestInfoResource() {} 88 static const char* classname() { return "FakeURLRequestInfoResource"; } 89 90 std::string url; 91 std::string method; 92 std::string headers; 93}; 94 95class FakeURLResponseInfoResource : public FakeResource { 96 public: 97 FakeURLResponseInfoResource() : status_code(0) {} 98 static const char* classname() { return "FakeURLResponseInfoResource"; } 99 100 int status_code; 101 std::string url; 102 std::string headers; 103}; 104 105// Helper function to call the completion callback if it is defined (an 106// asynchronous call), or return the result directly if it isn't (a synchronous 107// call). 108// 109// Use like this: 110// if (<some error condition>) 111// return RunCompletionCallback(callback, PP_ERROR_FUBAR); 112// 113// /* Everything worked OK */ 114// return RunCompletionCallback(callback, PP_OK); 115int32_t RunCompletionCallback(PP_CompletionCallback* callback, int32_t result) { 116 if (callback->func) { 117 PP_RunCompletionCallback(callback, result); 118 return PP_OK_COMPLETIONPENDING; 119 } 120 return result; 121} 122 123void HandleContentLength(FakeURLLoaderResource* loader, 124 FakeURLResponseInfoResource* response, 125 FakeURLLoaderEntity* entity) { 126 off_t content_length = entity->size(); 127 if (!loader->server->send_content_length()) 128 return; 129 130 std::ostringstream ss; 131 ss << "Content-Length: " << content_length << "\n"; 132 response->headers += ss.str(); 133} 134 135void HandlePartial(FakeURLLoaderResource* loader, 136 FakeURLRequestInfoResource* request, 137 FakeURLResponseInfoResource* response, 138 FakeURLLoaderEntity* entity) { 139 if (!loader->server->allow_partial()) 140 return; 141 142 // Read the RFC on byte ranges for more info: 143 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 144 std::string range; 145 if (!GetHeaderValue(request->headers, "Range", &range)) 146 return; 147 148 // We don't support all range requests, just bytes=<num>-<num> 149 off_t lo; 150 off_t hi; 151 if (sscanf(range.c_str(), "bytes=%" SCNi64 "-%" SCNi64, &lo, &hi) != 2) { 152 // Couldn't parse the range value. 153 return; 154 } 155 156 off_t content_length = entity->size(); 157 if (lo > content_length) { 158 // Trying to start reading past the end of the entity is 159 // unsatisfiable. 160 response->status_code = 416; // Request range not satisfiable. 161 return; 162 } 163 164 // Clamp the hi value to the content length. 165 if (hi >= content_length) 166 hi = content_length - 1; 167 168 if (lo > hi) { 169 // Bad range, ignore it and return the full result. 170 return; 171 } 172 173 // The range is a closed interval; e.g. 0-10 is 11 bytes. We'll 174 // store it as a half-open interval instead--it's more natural 175 // in C that way. 176 loader->read_offset = lo; 177 loader->read_end = hi + 1; 178 179 // Also add a "Content-Range" response header. 180 std::ostringstream ss; 181 ss << "Content-Range: bytes " << lo << "-" << hi << "/" << content_length 182 << "\n"; 183 response->headers += ss.str(); 184 185 response->status_code = 206; // Partial content 186} 187 188} // namespace 189 190FakeURLLoaderEntity::FakeURLLoaderEntity(const std::string& body) 191 : body_(body), size_(body_.size()), repeat_(false) { 192} 193 194// Rather than specifying the entire file, specify a string to repeat, and the 195// full length. This lets us test extremely large files without having to store 196// them in memory. 197FakeURLLoaderEntity::FakeURLLoaderEntity(const std::string& to_repeat, 198 off_t size) 199 : body_(to_repeat), size_(size), repeat_(true) { 200} 201 202size_t FakeURLLoaderEntity::Read(void* buffer, size_t count, off_t offset) { 203 off_t max_read_count = 204 std::max<off_t>(std::min<off_t>(size_ - offset, 0xffffffff), 0); 205 size_t bytes_to_read = std::min(count, static_cast<size_t>(max_read_count)); 206 207 if (repeat_) { 208 size_t src_size = body_.size(); 209 char* dst = static_cast<char*>(buffer); 210 const char* src = body_.data(); 211 size_t bytes_left = bytes_to_read; 212 213 size_t src_offset = static_cast<size_t>(offset % src_size); 214 if (src_offset != 0) { 215 // Copy enough to align. 216 size_t bytes_to_copy = std::min(bytes_left, src_size - src_offset); 217 memcpy(dst, src + src_offset, bytes_to_copy); 218 dst += bytes_to_copy; 219 bytes_left -= bytes_to_copy; 220 } 221 222 // Copy the body N times. 223 for (size_t i = bytes_left / src_size; i > 0; --i) { 224 memcpy(dst, src, src_size); 225 dst += src_size; 226 bytes_left -= src_size; 227 } 228 229 // Copy the rest of the bytes, < src_size. 230 if (bytes_left > 0) { 231 assert(bytes_left < src_size); 232 memcpy(dst, src, bytes_left); 233 } 234 } else { 235 memcpy(buffer, &body_.data()[offset], bytes_to_read); 236 } 237 238 return bytes_to_read; 239} 240 241FakeURLLoaderServer::FakeURLLoaderServer() 242 : max_read_size_(0), send_content_length_(false), allow_partial_(false) { 243} 244 245void FakeURLLoaderServer::Clear() { 246 entity_map_.clear(); 247} 248 249bool FakeURLLoaderServer::AddEntity(const std::string& url, 250 const std::string& body, 251 FakeURLLoaderEntity** out_entity) { 252 EntityMap::iterator iter = entity_map_.find(url); 253 if (iter != entity_map_.end()) { 254 if (out_entity) 255 *out_entity = NULL; 256 return false; 257 } 258 259 FakeURLLoaderEntity entity(body); 260 std::pair<EntityMap::iterator, bool> result = 261 entity_map_.insert(EntityMap::value_type(url, entity)); 262 263 EXPECT_EQ(true, result.second); 264 if (out_entity) 265 *out_entity = &result.first->second; 266 return true; 267} 268 269bool FakeURLLoaderServer::AddEntity(const std::string& url, 270 const std::string& body, 271 off_t size, 272 FakeURLLoaderEntity** out_entity) { 273 EntityMap::iterator iter = entity_map_.find(url); 274 if (iter != entity_map_.end()) { 275 if (out_entity) 276 *out_entity = NULL; 277 return false; 278 } 279 280 FakeURLLoaderEntity entity(body, size); 281 std::pair<EntityMap::iterator, bool> result = 282 entity_map_.insert(EntityMap::value_type(url, entity)); 283 284 EXPECT_EQ(true, result.second); 285 if (out_entity) 286 *out_entity = &result.first->second; 287 return true; 288} 289 290bool FakeURLLoaderServer::AddError(const std::string& url, 291 int http_status_code) { 292 ErrorMap::iterator iter = error_map_.find(url); 293 if (iter != error_map_.end()) 294 return false; 295 296 error_map_[url] = http_status_code; 297 return true; 298} 299 300FakeURLLoaderEntity* FakeURLLoaderServer::GetEntity(const std::string& url) { 301 EntityMap::iterator iter = entity_map_.find(url); 302 if (iter == entity_map_.end()) 303 return NULL; 304 return &iter->second; 305} 306 307int FakeURLLoaderServer::GetError(const std::string& url) { 308 ErrorMap::iterator iter = error_map_.find(url); 309 if (iter == error_map_.end()) 310 return 0; 311 return iter->second; 312} 313 314FakeURLLoaderInterface::FakeURLLoaderInterface( 315 FakeCoreInterface* core_interface) 316 : core_interface_(core_interface) { 317} 318 319PP_Resource FakeURLLoaderInterface::Create(PP_Instance instance) { 320 FakeInstanceResource* instance_resource = 321 core_interface_->resource_manager()->Get<FakeInstanceResource>(instance); 322 if (instance_resource == NULL) 323 return PP_ERROR_BADRESOURCE; 324 325 FakeURLLoaderResource* loader_resource = new FakeURLLoaderResource; 326 loader_resource->manager = core_interface_->resource_manager(); 327 loader_resource->server = 328 new FakeURLLoaderServer(*instance_resource->server_template); 329 330 return CREATE_RESOURCE(core_interface_->resource_manager(), 331 FakeURLLoaderResource, 332 loader_resource); 333} 334 335int32_t FakeURLLoaderInterface::Open(PP_Resource loader, 336 PP_Resource request, 337 PP_CompletionCallback callback) { 338 FakeURLLoaderResource* loader_resource = 339 core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader); 340 if (loader_resource == NULL) 341 return PP_ERROR_BADRESOURCE; 342 343 FakeURLRequestInfoResource* request_resource = 344 core_interface_->resource_manager()->Get<FakeURLRequestInfoResource>( 345 request); 346 if (request_resource == NULL) 347 return PP_ERROR_BADRESOURCE; 348 349 // Create a response resource. 350 FakeURLResponseInfoResource* response_resource = 351 new FakeURLResponseInfoResource; 352 loader_resource->response = 353 CREATE_RESOURCE(core_interface_->resource_manager(), 354 FakeURLResponseInfoResource, 355 response_resource); 356 357 loader_resource->entity = NULL; 358 loader_resource->read_offset = 0; 359 loader_resource->read_end = 0; 360 361 // Get the URL from the request info. 362 std::string url = request_resource->url; 363 std::string method = request_resource->method; 364 365 response_resource->url = url; 366 // TODO(binji): allow this to be set? 367 response_resource->headers.clear(); 368 369 // Check the error map first, to see if this URL should produce an error. 370 EXPECT_TRUE(NULL != loader_resource->server); 371 int http_status_code = loader_resource->server->GetError(url); 372 if (http_status_code != 0) { 373 // Got an error, return that in the response. 374 response_resource->status_code = http_status_code; 375 return RunCompletionCallback(&callback, PP_OK); 376 } 377 378 // Look up the URL in the loader resource entity map. 379 FakeURLLoaderEntity* entity = loader_resource->server->GetEntity(url); 380 response_resource->status_code = entity ? 200 : 404; 381 382 if (method == "GET") { 383 loader_resource->entity = entity; 384 } else if (method != "HEAD") { 385 response_resource->status_code = 405; // Method not allowed. 386 return RunCompletionCallback(&callback, PP_OK); 387 } 388 389 if (entity != NULL) { 390 off_t content_length = entity->size(); 391 loader_resource->read_end = content_length; 392 HandleContentLength(loader_resource, response_resource, entity); 393 HandlePartial(loader_resource, request_resource, response_resource, entity); 394 } 395 396 // Call the callback. 397 return RunCompletionCallback(&callback, PP_OK); 398} 399 400PP_Resource FakeURLLoaderInterface::GetResponseInfo(PP_Resource loader) { 401 FakeURLLoaderResource* loader_resource = 402 core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader); 403 if (loader_resource == NULL) 404 return 0; 405 406 // Returned resources have an implicit AddRef. 407 core_interface_->resource_manager()->AddRef(loader_resource->response); 408 return loader_resource->response; 409} 410 411int32_t FakeURLLoaderInterface::ReadResponseBody( 412 PP_Resource loader, 413 void* buffer, 414 int32_t bytes_to_read, 415 PP_CompletionCallback callback) { 416 FakeURLLoaderResource* loader_resource = 417 core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader); 418 if (loader_resource == NULL) 419 return PP_ERROR_BADRESOURCE; 420 421 if (loader_resource->entity == NULL) 422 // TODO(binji): figure out the correct error here. 423 return PP_ERROR_FAILED; 424 425 // Allow the test to specify how much the "server" should send in each call 426 // to ReadResponseBody. A max_read_size of 0 means read as much as the 427 // buffer will allow. 428 size_t server_max_read_size = loader_resource->server->max_read_size(); 429 if (server_max_read_size != 0) 430 bytes_to_read = std::min<int32_t>(bytes_to_read, server_max_read_size); 431 432 size_t bytes_read = loader_resource->entity->Read( 433 buffer, bytes_to_read, loader_resource->read_offset); 434 loader_resource->read_offset += bytes_read; 435 436 return RunCompletionCallback(&callback, bytes_read); 437} 438 439void FakeURLLoaderInterface::Close(PP_Resource loader) { 440 FakeURLLoaderResource* loader_resource = 441 core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader); 442 if (loader_resource == NULL) 443 return; 444 445 core_interface_->resource_manager()->Release(loader_resource->response); 446 447 loader_resource->server = NULL; 448 loader_resource->entity = NULL; 449 loader_resource->response = 0; 450 loader_resource->read_offset = 0; 451} 452 453FakeURLRequestInfoInterface::FakeURLRequestInfoInterface( 454 FakeCoreInterface* core_interface, 455 FakeVarInterface* var_interface) 456 : core_interface_(core_interface), var_interface_(var_interface) { 457} 458 459PP_Resource FakeURLRequestInfoInterface::Create(PP_Instance instance) { 460 FakeInstanceResource* instance_resource = 461 core_interface_->resource_manager()->Get<FakeInstanceResource>(instance); 462 if (instance_resource == NULL) 463 return PP_ERROR_BADRESOURCE; 464 465 return CREATE_RESOURCE(core_interface_->resource_manager(), 466 FakeURLRequestInfoResource, 467 new FakeURLRequestInfoResource); 468} 469 470PP_Bool FakeURLRequestInfoInterface::SetProperty(PP_Resource request, 471 PP_URLRequestProperty property, 472 PP_Var value) { 473 FakeURLRequestInfoResource* request_resource = 474 core_interface_->resource_manager()->Get<FakeURLRequestInfoResource>( 475 request); 476 if (request_resource == NULL) 477 return PP_FALSE; 478 479 switch (property) { 480 case PP_URLREQUESTPROPERTY_URL: { 481 if (value.type != PP_VARTYPE_STRING) 482 return PP_FALSE; 483 484 uint32_t len; 485 const char* url = var_interface_->VarToUtf8(value, &len); 486 if (url == NULL) 487 return PP_FALSE; 488 489 request_resource->url = url; 490 var_interface_->Release(value); 491 return PP_TRUE; 492 } 493 case PP_URLREQUESTPROPERTY_METHOD: { 494 if (value.type != PP_VARTYPE_STRING) 495 return PP_FALSE; 496 497 uint32_t len; 498 const char* url = var_interface_->VarToUtf8(value, &len); 499 if (url == NULL) 500 return PP_FALSE; 501 502 request_resource->method = url; 503 var_interface_->Release(value); 504 return PP_TRUE; 505 } 506 case PP_URLREQUESTPROPERTY_HEADERS: { 507 if (value.type != PP_VARTYPE_STRING) 508 return PP_FALSE; 509 510 uint32_t len; 511 const char* url = var_interface_->VarToUtf8(value, &len); 512 if (url == NULL) 513 return PP_FALSE; 514 515 request_resource->headers = url; 516 var_interface_->Release(value); 517 return PP_TRUE; 518 } 519 case PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS: { 520 if (value.type != PP_VARTYPE_BOOL) 521 return PP_FALSE; 522 // Throw the value away for now. TODO(binji): add tests for this. 523 return PP_TRUE; 524 } 525 case PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS: { 526 if (value.type != PP_VARTYPE_BOOL) 527 return PP_FALSE; 528 // Throw the value away for now. TODO(binji): add tests for this. 529 return PP_TRUE; 530 } 531 default: 532 EXPECT_TRUE(false) << "Unimplemented property " << property 533 << " in " 534 "FakeURLRequestInfoInterface::SetProperty"; 535 return PP_FALSE; 536 } 537} 538 539FakeURLResponseInfoInterface::FakeURLResponseInfoInterface( 540 FakeCoreInterface* core_interface, 541 FakeVarInterface* var_interface) 542 : core_interface_(core_interface), var_interface_(var_interface) { 543} 544 545PP_Var FakeURLResponseInfoInterface::GetProperty( 546 PP_Resource response, 547 PP_URLResponseProperty property) { 548 FakeURLResponseInfoResource* response_resource = 549 core_interface_->resource_manager()->Get<FakeURLResponseInfoResource>( 550 response); 551 if (response_resource == NULL) 552 return PP_Var(); 553 554 switch (property) { 555 case PP_URLRESPONSEPROPERTY_URL: 556 return var_interface_->VarFromUtf8(response_resource->url.data(), 557 response_resource->url.size()); 558 559 case PP_URLRESPONSEPROPERTY_STATUSCODE: 560 return PP_MakeInt32(response_resource->status_code); 561 562 case PP_URLRESPONSEPROPERTY_HEADERS: 563 return var_interface_->VarFromUtf8(response_resource->headers.data(), 564 response_resource->headers.size()); 565 default: 566 EXPECT_TRUE(false) << "Unimplemented property " << property 567 << " in " 568 "FakeURLResponseInfoInterface::GetProperty"; 569 return PP_Var(); 570 } 571} 572 573FakePepperInterfaceURLLoader::FakePepperInterfaceURLLoader() 574 : core_interface_(&resource_manager_), 575 var_interface_(&var_manager_), 576 url_loader_interface_(&core_interface_), 577 url_request_info_interface_(&core_interface_, &var_interface_), 578 url_response_info_interface_(&core_interface_, &var_interface_) { 579 FakeInstanceResource* instance_resource = new FakeInstanceResource; 580 instance_resource->server_template = &server_template_; 581 instance_ = CREATE_RESOURCE(core_interface_.resource_manager(), 582 FakeInstanceResource, 583 instance_resource); 584} 585 586FakePepperInterfaceURLLoader::~FakePepperInterfaceURLLoader() { 587 core_interface_.ReleaseResource(instance_); 588} 589 590nacl_io::CoreInterface* FakePepperInterfaceURLLoader::GetCoreInterface() { 591 return &core_interface_; 592} 593 594nacl_io::VarInterface* FakePepperInterfaceURLLoader::GetVarInterface() { 595 return &var_interface_; 596} 597 598nacl_io::URLLoaderInterface* 599FakePepperInterfaceURLLoader::GetURLLoaderInterface() { 600 return &url_loader_interface_; 601} 602 603nacl_io::URLRequestInfoInterface* 604FakePepperInterfaceURLLoader::GetURLRequestInfoInterface() { 605 return &url_request_info_interface_; 606} 607 608nacl_io::URLResponseInfoInterface* 609FakePepperInterfaceURLLoader::GetURLResponseInfoInterface() { 610 return &url_response_info_interface_; 611} 612