1// Copyright 2013 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 "components/component_updater/test/url_request_post_interceptor.h"
6
7#include "base/files/file_util.h"
8#include "base/macros.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/strings/stringprintf.h"
11#include "components/component_updater/test/test_configurator.h"
12#include "net/base/upload_bytes_element_reader.h"
13#include "net/url_request/url_request.h"
14#include "net/url_request/url_request_filter.h"
15#include "net/url_request/url_request_interceptor.h"
16#include "net/url_request/url_request_simple_job.h"
17#include "net/url_request/url_request_test_util.h"
18
19namespace component_updater {
20
21// Returns a canned response.
22class URLRequestMockJob : public net::URLRequestSimpleJob {
23 public:
24  URLRequestMockJob(net::URLRequest* request,
25                    net::NetworkDelegate* network_delegate,
26                    int response_code,
27                    const std::string& response_body)
28      : net::URLRequestSimpleJob(request, network_delegate),
29        response_code_(response_code),
30        response_body_(response_body) {}
31
32 protected:
33  virtual int GetResponseCode() const OVERRIDE { return response_code_; }
34
35  virtual int GetData(std::string* mime_type,
36                      std::string* charset,
37                      std::string* data,
38                      const net::CompletionCallback& callback) const OVERRIDE {
39    mime_type->assign("text/plain");
40    charset->assign("US-ASCII");
41    data->assign(response_body_);
42    return net::OK;
43  }
44
45 private:
46  virtual ~URLRequestMockJob() {}
47
48  int response_code_;
49  std::string response_body_;
50  DISALLOW_COPY_AND_ASSIGN(URLRequestMockJob);
51};
52
53URLRequestPostInterceptor::URLRequestPostInterceptor(
54    const GURL& url,
55    const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
56    : url_(url), io_task_runner_(io_task_runner), hit_count_(0) {
57}
58
59URLRequestPostInterceptor::~URLRequestPostInterceptor() {
60  DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
61  ClearExpectations();
62}
63
64void URLRequestPostInterceptor::ClearExpectations() {
65  while (!expectations_.empty()) {
66    Expectation expectation(expectations_.front());
67    delete expectation.first;
68    expectations_.pop();
69  }
70}
71
72GURL URLRequestPostInterceptor::GetUrl() const {
73  return url_;
74}
75
76bool URLRequestPostInterceptor::ExpectRequest(
77    class RequestMatcher* request_matcher) {
78  expectations_.push(std::make_pair(request_matcher,
79                                    ExpectationResponse(kResponseCode200, "")));
80  return true;
81}
82
83bool URLRequestPostInterceptor::ExpectRequest(
84    class RequestMatcher* request_matcher,
85    int response_code) {
86  expectations_.push(
87      std::make_pair(request_matcher, ExpectationResponse(response_code, "")));
88  return true;
89}
90
91bool URLRequestPostInterceptor::ExpectRequest(
92    class RequestMatcher* request_matcher,
93    const base::FilePath& filepath) {
94  std::string response;
95  if (filepath.empty() || !base::ReadFileToString(filepath, &response))
96    return false;
97
98  expectations_.push(std::make_pair(
99      request_matcher, ExpectationResponse(kResponseCode200, response)));
100  return true;
101}
102
103int URLRequestPostInterceptor::GetHitCount() const {
104  base::AutoLock auto_lock(interceptor_lock_);
105  return hit_count_;
106}
107
108int URLRequestPostInterceptor::GetCount() const {
109  base::AutoLock auto_lock(interceptor_lock_);
110  return static_cast<int>(requests_.size());
111}
112
113std::vector<std::string> URLRequestPostInterceptor::GetRequests() const {
114  base::AutoLock auto_lock(interceptor_lock_);
115  return requests_;
116}
117
118std::string URLRequestPostInterceptor::GetRequestsAsString() const {
119  std::vector<std::string> requests(GetRequests());
120
121  std::string s = "Requests are:";
122
123  int i = 0;
124  for (std::vector<std::string>::const_iterator it = requests.begin();
125       it != requests.end();
126       ++it) {
127    s.append(base::StringPrintf("\n  (%d): %s", ++i, it->c_str()));
128  }
129
130  return s;
131}
132
133void URLRequestPostInterceptor::Reset() {
134  base::AutoLock auto_lock(interceptor_lock_);
135  hit_count_ = 0;
136  requests_.clear();
137  ClearExpectations();
138}
139
140class URLRequestPostInterceptor::Delegate : public net::URLRequestInterceptor {
141 public:
142  Delegate(const std::string& scheme,
143           const std::string& hostname,
144           const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
145      : scheme_(scheme), hostname_(hostname), io_task_runner_(io_task_runner) {}
146
147  void Register() {
148    DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
149    net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
150        scheme_, hostname_, scoped_ptr<net::URLRequestInterceptor>(this));
151  }
152
153  void Unregister() {
154    DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
155    for (InterceptorMap::iterator it = interceptors_.begin();
156         it != interceptors_.end();
157         ++it)
158      delete (*it).second;
159    net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(scheme_,
160                                                                hostname_);
161  }
162
163  void OnCreateInterceptor(URLRequestPostInterceptor* interceptor) {
164    DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
165    DCHECK(interceptors_.find(interceptor->GetUrl()) == interceptors_.end());
166
167    interceptors_.insert(std::make_pair(interceptor->GetUrl(), interceptor));
168  }
169
170 private:
171  virtual ~Delegate() {}
172
173  virtual net::URLRequestJob* MaybeInterceptRequest(
174      net::URLRequest* request,
175      net::NetworkDelegate* network_delegate) const OVERRIDE {
176    DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
177
178    // Only intercepts POST.
179    if (!request->has_upload())
180      return NULL;
181
182    GURL url = request->url();
183    if (url.has_query()) {
184      GURL::Replacements replacements;
185      replacements.ClearQuery();
186      url = url.ReplaceComponents(replacements);
187    }
188
189    InterceptorMap::const_iterator it(interceptors_.find(url));
190    if (it == interceptors_.end())
191      return NULL;
192
193    // There is an interceptor hooked up for this url. Read the request body,
194    // check the existing expectations, and handle the matching case by
195    // popping the expectation off the queue, counting the match, and
196    // returning a mock object to serve the canned response.
197    URLRequestPostInterceptor* interceptor(it->second);
198
199    const net::UploadDataStream* stream = request->get_upload();
200    const net::UploadBytesElementReader* reader =
201        stream->element_readers()[0]->AsBytesReader();
202    const int size = reader->length();
203    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(size));
204    const std::string request_body(reader->bytes());
205
206    {
207      base::AutoLock auto_lock(interceptor->interceptor_lock_);
208      interceptor->requests_.push_back(request_body);
209      if (interceptor->expectations_.empty())
210        return NULL;
211      const URLRequestPostInterceptor::Expectation& expectation(
212          interceptor->expectations_.front());
213      if (expectation.first->Match(request_body)) {
214        const int response_code(expectation.second.response_code);
215        const std::string response_body(expectation.second.response_body);
216        delete expectation.first;
217        interceptor->expectations_.pop();
218        ++interceptor->hit_count_;
219
220        return new URLRequestMockJob(
221            request, network_delegate, response_code, response_body);
222      }
223    }
224
225    return NULL;
226  }
227
228  typedef std::map<GURL, URLRequestPostInterceptor*> InterceptorMap;
229  InterceptorMap interceptors_;
230
231  const std::string scheme_;
232  const std::string hostname_;
233  scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
234
235  DISALLOW_COPY_AND_ASSIGN(Delegate);
236};
237
238URLRequestPostInterceptorFactory::URLRequestPostInterceptorFactory(
239    const std::string& scheme,
240    const std::string& hostname,
241    const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
242    : scheme_(scheme),
243      hostname_(hostname),
244      io_task_runner_(io_task_runner),
245      delegate_(new URLRequestPostInterceptor::Delegate(scheme,
246                                                        hostname,
247                                                        io_task_runner)) {
248  io_task_runner_->PostTask(
249      FROM_HERE,
250      base::Bind(&URLRequestPostInterceptor::Delegate::Register,
251                 base::Unretained(delegate_)));
252}
253
254URLRequestPostInterceptorFactory::~URLRequestPostInterceptorFactory() {
255  io_task_runner_->PostTask(
256      FROM_HERE,
257      base::Bind(&URLRequestPostInterceptor::Delegate::Unregister,
258                 base::Unretained(delegate_)));
259}
260
261URLRequestPostInterceptor* URLRequestPostInterceptorFactory::CreateInterceptor(
262    const base::FilePath& filepath) {
263  const GURL base_url(
264      base::StringPrintf("%s://%s", scheme_.c_str(), hostname_.c_str()));
265  GURL absolute_url(base_url.Resolve(filepath.MaybeAsASCII()));
266  URLRequestPostInterceptor* interceptor(
267      new URLRequestPostInterceptor(absolute_url, io_task_runner_));
268  bool res = io_task_runner_->PostTask(
269      FROM_HERE,
270      base::Bind(&URLRequestPostInterceptor::Delegate::OnCreateInterceptor,
271                 base::Unretained(delegate_),
272                 base::Unretained(interceptor)));
273  if (!res) {
274    delete interceptor;
275    return NULL;
276  }
277
278  return interceptor;
279}
280
281bool PartialMatch::Match(const std::string& actual) const {
282  return actual.find(expected_) != std::string::npos;
283}
284
285InterceptorFactory::InterceptorFactory(
286    const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
287    : URLRequestPostInterceptorFactory(POST_INTERCEPT_SCHEME,
288                                       POST_INTERCEPT_HOSTNAME,
289                                       io_task_runner) {
290}
291
292InterceptorFactory::~InterceptorFactory() {
293}
294
295URLRequestPostInterceptor* InterceptorFactory::CreateInterceptor() {
296  return CreateInterceptorForPath(POST_INTERCEPT_PATH);
297}
298
299URLRequestPostInterceptor* InterceptorFactory::CreateInterceptorForPath(
300    const char* url_path) {
301  return URLRequestPostInterceptorFactory::CreateInterceptor(
302      base::FilePath::FromUTF8Unsafe(url_path));
303}
304
305}  // namespace component_updater
306