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