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),
243      send_content_length_(false),
244      allow_partial_(false),
245      allow_head_(true) {
246}
247
248void FakeURLLoaderServer::Clear() {
249  entity_map_.clear();
250}
251
252bool FakeURLLoaderServer::AddEntity(const std::string& url,
253                                    const std::string& body,
254                                    FakeURLLoaderEntity** out_entity) {
255  EntityMap::iterator iter = entity_map_.find(url);
256  if (iter != entity_map_.end()) {
257    if (out_entity)
258      *out_entity = NULL;
259    return false;
260  }
261
262  FakeURLLoaderEntity entity(body);
263  std::pair<EntityMap::iterator, bool> result =
264      entity_map_.insert(EntityMap::value_type(url, entity));
265
266  EXPECT_EQ(true, result.second);
267  if (out_entity)
268    *out_entity = &result.first->second;
269  return true;
270}
271
272bool FakeURLLoaderServer::AddEntity(const std::string& url,
273                                    const std::string& body,
274                                    off_t size,
275                                    FakeURLLoaderEntity** out_entity) {
276  EntityMap::iterator iter = entity_map_.find(url);
277  if (iter != entity_map_.end()) {
278    if (out_entity)
279      *out_entity = NULL;
280    return false;
281  }
282
283  FakeURLLoaderEntity entity(body, size);
284  std::pair<EntityMap::iterator, bool> result =
285      entity_map_.insert(EntityMap::value_type(url, entity));
286
287  EXPECT_EQ(true, result.second);
288  if (out_entity)
289    *out_entity = &result.first->second;
290  return true;
291}
292
293bool FakeURLLoaderServer::SetBlobEntity(const std::string& url,
294                                        const std::string& body,
295                                        FakeURLLoaderEntity** out_entity) {
296  set_allow_partial(true);
297  set_allow_head(false);
298  return AddEntity(url, body, out_entity);
299}
300
301bool FakeURLLoaderServer::AddError(const std::string& url,
302                                   int http_status_code) {
303  ErrorMap::iterator iter = error_map_.find(url);
304  if (iter != error_map_.end())
305    return false;
306
307  error_map_[url] = http_status_code;
308  return true;
309}
310
311FakeURLLoaderEntity* FakeURLLoaderServer::GetEntity(const std::string& url) {
312  EntityMap::iterator iter = entity_map_.find(url);
313  if (iter == entity_map_.end())
314    return NULL;
315  return &iter->second;
316}
317
318int FakeURLLoaderServer::GetError(const std::string& url) {
319  ErrorMap::iterator iter = error_map_.find(url);
320  if (iter == error_map_.end())
321    return 0;
322  return iter->second;
323}
324
325FakeURLLoaderInterface::FakeURLLoaderInterface(
326    FakeCoreInterface* core_interface)
327    : core_interface_(core_interface) {
328}
329
330PP_Resource FakeURLLoaderInterface::Create(PP_Instance instance) {
331  FakeInstanceResource* instance_resource =
332      core_interface_->resource_manager()->Get<FakeInstanceResource>(instance);
333  if (instance_resource == NULL)
334    return PP_ERROR_BADRESOURCE;
335
336  FakeURLLoaderResource* loader_resource = new FakeURLLoaderResource;
337  loader_resource->manager = core_interface_->resource_manager();
338  loader_resource->server =
339      new FakeURLLoaderServer(*instance_resource->server_template);
340
341  return CREATE_RESOURCE(core_interface_->resource_manager(),
342                         FakeURLLoaderResource,
343                         loader_resource);
344}
345
346int32_t FakeURLLoaderInterface::Open(PP_Resource loader,
347                                     PP_Resource request,
348                                     PP_CompletionCallback callback) {
349  FakeURLLoaderResource* loader_resource =
350      core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader);
351  if (loader_resource == NULL)
352    return PP_ERROR_BADRESOURCE;
353
354  FakeURLRequestInfoResource* request_resource =
355      core_interface_->resource_manager()->Get<FakeURLRequestInfoResource>(
356          request);
357  if (request_resource == NULL)
358    return PP_ERROR_BADRESOURCE;
359
360  // Create a response resource.
361  FakeURLResponseInfoResource* response_resource =
362      new FakeURLResponseInfoResource;
363  loader_resource->response =
364      CREATE_RESOURCE(core_interface_->resource_manager(),
365                      FakeURLResponseInfoResource,
366                      response_resource);
367
368  loader_resource->entity = NULL;
369  loader_resource->read_offset = 0;
370  loader_resource->read_end = 0;
371
372  // Get the URL from the request info.
373  std::string url = request_resource->url;
374  std::string method = request_resource->method;
375
376  response_resource->url = url;
377  // TODO(binji): allow this to be set?
378  response_resource->headers.clear();
379
380  // Check the error map first, to see if this URL should produce an error.
381  EXPECT_TRUE(NULL != loader_resource->server);
382  int http_status_code = loader_resource->server->GetError(url);
383  if (http_status_code != 0) {
384    // Got an error, return that in the response.
385    response_resource->status_code = http_status_code;
386    return RunCompletionCallback(&callback, PP_OK);
387  }
388
389  // Look up the URL in the loader resource entity map.
390  FakeURLLoaderEntity* entity = loader_resource->server->GetEntity(url);
391  response_resource->status_code = entity ? 200 : 404;
392
393  if (method == "GET") {
394    loader_resource->entity = entity;
395  } else if (method != "HEAD" || !loader_resource->server->allow_head()) {
396    response_resource->status_code = 405;  // Method not allowed.
397    return RunCompletionCallback(&callback, PP_OK);
398  }
399
400  if (entity != NULL) {
401    off_t content_length = entity->size();
402    loader_resource->read_end = content_length;
403    HandleContentLength(loader_resource, response_resource, entity);
404    HandlePartial(loader_resource, request_resource, response_resource, entity);
405  }
406
407  // Call the callback.
408  return RunCompletionCallback(&callback, PP_OK);
409}
410
411PP_Resource FakeURLLoaderInterface::GetResponseInfo(PP_Resource loader) {
412  FakeURLLoaderResource* loader_resource =
413      core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader);
414  if (loader_resource == NULL)
415    return 0;
416
417  // Returned resources have an implicit AddRef.
418  core_interface_->resource_manager()->AddRef(loader_resource->response);
419  return loader_resource->response;
420}
421
422int32_t FakeURLLoaderInterface::ReadResponseBody(
423    PP_Resource loader,
424    void* buffer,
425    int32_t bytes_to_read,
426    PP_CompletionCallback callback) {
427  FakeURLLoaderResource* loader_resource =
428      core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader);
429  if (loader_resource == NULL)
430    return PP_ERROR_BADRESOURCE;
431
432  if (loader_resource->entity == NULL)
433    // TODO(binji): figure out the correct error here.
434    return PP_ERROR_FAILED;
435
436  // Allow the test to specify how much the "server" should send in each call
437  // to ReadResponseBody. A max_read_size of 0 means read as much as the
438  // buffer will allow.
439  size_t server_max_read_size = loader_resource->server->max_read_size();
440  if (server_max_read_size != 0)
441    bytes_to_read = std::min<int32_t>(bytes_to_read, server_max_read_size);
442
443  size_t bytes_read = loader_resource->entity->Read(
444      buffer, bytes_to_read, loader_resource->read_offset);
445  loader_resource->read_offset += bytes_read;
446
447  return RunCompletionCallback(&callback, bytes_read);
448}
449
450void FakeURLLoaderInterface::Close(PP_Resource loader) {
451  FakeURLLoaderResource* loader_resource =
452      core_interface_->resource_manager()->Get<FakeURLLoaderResource>(loader);
453  if (loader_resource == NULL)
454    return;
455
456  core_interface_->resource_manager()->Release(loader_resource->response);
457
458  loader_resource->server = NULL;
459  loader_resource->entity = NULL;
460  loader_resource->response = 0;
461  loader_resource->read_offset = 0;
462}
463
464FakeURLRequestInfoInterface::FakeURLRequestInfoInterface(
465    FakeCoreInterface* core_interface,
466    FakeVarInterface* var_interface)
467    : core_interface_(core_interface), var_interface_(var_interface) {
468}
469
470PP_Resource FakeURLRequestInfoInterface::Create(PP_Instance instance) {
471  FakeInstanceResource* instance_resource =
472      core_interface_->resource_manager()->Get<FakeInstanceResource>(instance);
473  if (instance_resource == NULL)
474    return PP_ERROR_BADRESOURCE;
475
476  return CREATE_RESOURCE(core_interface_->resource_manager(),
477                         FakeURLRequestInfoResource,
478                         new FakeURLRequestInfoResource);
479}
480
481PP_Bool FakeURLRequestInfoInterface::SetProperty(PP_Resource request,
482                                                 PP_URLRequestProperty property,
483                                                 PP_Var value) {
484  FakeURLRequestInfoResource* request_resource =
485      core_interface_->resource_manager()->Get<FakeURLRequestInfoResource>(
486          request);
487  if (request_resource == NULL)
488    return PP_FALSE;
489
490  switch (property) {
491    case PP_URLREQUESTPROPERTY_URL: {
492      if (value.type != PP_VARTYPE_STRING)
493        return PP_FALSE;
494
495      uint32_t len;
496      const char* url = var_interface_->VarToUtf8(value, &len);
497      if (url == NULL)
498        return PP_FALSE;
499
500      request_resource->url = url;
501      var_interface_->Release(value);
502      return PP_TRUE;
503    }
504    case PP_URLREQUESTPROPERTY_METHOD: {
505      if (value.type != PP_VARTYPE_STRING)
506        return PP_FALSE;
507
508      uint32_t len;
509      const char* url = var_interface_->VarToUtf8(value, &len);
510      if (url == NULL)
511        return PP_FALSE;
512
513      request_resource->method = url;
514      var_interface_->Release(value);
515      return PP_TRUE;
516    }
517    case PP_URLREQUESTPROPERTY_HEADERS: {
518      if (value.type != PP_VARTYPE_STRING)
519        return PP_FALSE;
520
521      uint32_t len;
522      const char* url = var_interface_->VarToUtf8(value, &len);
523      if (url == NULL)
524        return PP_FALSE;
525
526      request_resource->headers = url;
527      var_interface_->Release(value);
528      return PP_TRUE;
529    }
530    case PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS: {
531      if (value.type != PP_VARTYPE_BOOL)
532        return PP_FALSE;
533      // Throw the value away for now. TODO(binji): add tests for this.
534      return PP_TRUE;
535    }
536    case PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS: {
537      if (value.type != PP_VARTYPE_BOOL)
538        return PP_FALSE;
539      // Throw the value away for now. TODO(binji): add tests for this.
540      return PP_TRUE;
541    }
542    default:
543      EXPECT_TRUE(false) << "Unimplemented property " << property
544                         << " in "
545                            "FakeURLRequestInfoInterface::SetProperty";
546      return PP_FALSE;
547  }
548}
549
550FakeURLResponseInfoInterface::FakeURLResponseInfoInterface(
551    FakeCoreInterface* core_interface,
552    FakeVarInterface* var_interface)
553    : core_interface_(core_interface), var_interface_(var_interface) {
554}
555
556PP_Var FakeURLResponseInfoInterface::GetProperty(
557    PP_Resource response,
558    PP_URLResponseProperty property) {
559  FakeURLResponseInfoResource* response_resource =
560      core_interface_->resource_manager()->Get<FakeURLResponseInfoResource>(
561          response);
562  if (response_resource == NULL)
563    return PP_Var();
564
565  switch (property) {
566    case PP_URLRESPONSEPROPERTY_URL:
567      return var_interface_->VarFromUtf8(response_resource->url.data(),
568                                         response_resource->url.size());
569
570    case PP_URLRESPONSEPROPERTY_STATUSCODE:
571      return PP_MakeInt32(response_resource->status_code);
572
573    case PP_URLRESPONSEPROPERTY_HEADERS:
574      return var_interface_->VarFromUtf8(response_resource->headers.data(),
575                                         response_resource->headers.size());
576    default:
577      EXPECT_TRUE(false) << "Unimplemented property " << property
578                         << " in "
579                            "FakeURLResponseInfoInterface::GetProperty";
580      return PP_Var();
581  }
582}
583
584FakePepperInterfaceURLLoader::FakePepperInterfaceURLLoader()
585    : core_interface_(&resource_manager_),
586      var_interface_(&var_manager_),
587      url_loader_interface_(&core_interface_),
588      url_request_info_interface_(&core_interface_, &var_interface_),
589      url_response_info_interface_(&core_interface_, &var_interface_) {
590  FakeInstanceResource* instance_resource = new FakeInstanceResource;
591  instance_resource->server_template = &server_template_;
592  instance_ = CREATE_RESOURCE(core_interface_.resource_manager(),
593                              FakeInstanceResource,
594                              instance_resource);
595}
596
597FakePepperInterfaceURLLoader::~FakePepperInterfaceURLLoader() {
598  core_interface_.ReleaseResource(instance_);
599}
600
601nacl_io::CoreInterface* FakePepperInterfaceURLLoader::GetCoreInterface() {
602  return &core_interface_;
603}
604
605nacl_io::VarInterface* FakePepperInterfaceURLLoader::GetVarInterface() {
606  return &var_interface_;
607}
608
609nacl_io::URLLoaderInterface*
610FakePepperInterfaceURLLoader::GetURLLoaderInterface() {
611  return &url_loader_interface_;
612}
613
614nacl_io::URLRequestInfoInterface*
615FakePepperInterfaceURLLoader::GetURLRequestInfoInterface() {
616  return &url_request_info_interface_;
617}
618
619nacl_io::URLResponseInfoInterface*
620FakePepperInterfaceURLLoader::GetURLResponseInfoInterface() {
621  return &url_response_info_interface_;
622}
623