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