OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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/certificate_transparency/log_proof_fetcher.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "components/safe_json/testing_json_parser.h" | |
10 #include "net/base/net_errors.h" | |
11 #include "net/base/network_delegate.h" | |
12 #include "net/cert/signed_tree_head.h" | |
13 #include "net/http/http_status_code.h" | |
14 #include "net/test/ct_test_util.h" | |
15 #include "net/url_request/url_request_context.h" | |
16 #include "net/url_request/url_request_filter.h" | |
17 #include "net/url_request/url_request_interceptor.h" | |
18 #include "net/url_request/url_request_job.h" | |
19 #include "net/url_request/url_request_test_job.h" | |
20 #include "net/url_request/url_request_test_util.h" | |
21 #include "testing/gtest/include/gtest/gtest.h" | |
22 | |
23 namespace certificate_transparency { | |
24 | |
25 namespace { | |
26 | |
27 const char kGetSTHHeaders[] = | |
28 "HTTP/1.1 200 OK\0" | |
29 "Content-Type: application/json; charset=ISO-8859-1\0" | |
30 "\0"; | |
31 | |
32 const char kLogSchema[] = "https"; | |
33 const char kLogURL[] = "ct.log.example.com"; | |
34 const char kLogID[] = "some_id"; | |
35 | |
36 class FetchSTHTestJob : public net::URLRequestTestJob { | |
37 public: | |
38 FetchSTHTestJob(const std::string& get_sth_data, | |
39 net::URLRequest* request, | |
40 net::NetworkDelegate* network_delegate) | |
41 : URLRequestTestJob( | |
42 request, | |
43 network_delegate, | |
44 std::string(kGetSTHHeaders, arraysize(kGetSTHHeaders)), | |
45 get_sth_data, | |
46 true), | |
47 async_io_(false), | |
48 response_code_(net::HTTP_OK) {} | |
49 | |
50 void SetAsyncIO(bool async_io) { async_io_ = async_io; } | |
mmenke
2015/08/04 19:54:30
set_asnyc_io
Eran Messeri
2015/08/05 13:10:52
Done.
| |
51 | |
52 void SetResponseCode(int response_code) { response_code_ = response_code; } | |
53 | |
54 int GetResponseCode() const override { return response_code_; } | |
mmenke
2015/08/04 19:54:29
By default, this will just be grabbed from the hea
Eran Messeri
2015/08/05 13:10:52
Done.
| |
55 | |
56 protected: | |
57 bool NextReadAsync() override { | |
mmenke
2015/08/04 19:54:29
This can be private (parent classes can access pri
Eran Messeri
2015/08/05 13:10:52
Done.
| |
58 // Response with indication of async IO only once, otherwise the final | |
59 // Read would (incorrectly) be classified as async, causing the | |
60 // URLRequestJob to try reading another time and failing on a CHECK | |
61 // that the raw_read_buffer_ is not null. | |
mmenke
2015/08/04 19:54:30
Why is raw read buffer NULL? The final read of a
Eran Messeri
2015/08/05 13:10:52
I'll follow up on that over email - still trying t
| |
62 if (async_io_) { | |
63 async_io_ = false; | |
64 return true; | |
65 } | |
66 return false; | |
67 } | |
68 | |
69 private: | |
70 bool async_io_; | |
71 int response_code_; | |
mmenke
2015/08/04 19:54:30
member variables go at the end, after all methods,
Eran Messeri
2015/08/05 13:10:52
Done.
| |
72 | |
73 ~FetchSTHTestJob() override {} | |
74 DISALLOW_COPY_AND_ASSIGN(FetchSTHTestJob); | |
mmenke
2015/08/04 19:54:30
nit: Blank line before DISALLOW_COPY_AND_ASSIGN
Eran Messeri
2015/08/05 13:10:52
Done.
| |
75 }; | |
76 | |
77 class GetSTHResponseHandler : public net::URLRequestInterceptor { | |
78 public: | |
79 GetSTHResponseHandler() | |
80 : async_io_(false), response_data_(""), response_code_(net::HTTP_OK) {} | |
81 ~GetSTHResponseHandler() override {} | |
82 | |
83 // URLRequestInterceptor implementation: | |
84 net::URLRequestJob* MaybeInterceptRequest( | |
85 net::URLRequest* request, | |
86 net::NetworkDelegate* network_delegate) const override { | |
87 FetchSTHTestJob* job = | |
88 new FetchSTHTestJob(response_data_, request, network_delegate); | |
89 job->SetAsyncIO(async_io_); | |
90 job->SetResponseCode(response_code_); | |
91 return job; | |
92 } | |
93 | |
94 void SetResponseData(std::string response_data) { | |
mmenke
2015/08/04 19:54:30
set_response_data
Eran Messeri
2015/08/05 13:10:52
Done.
| |
95 response_data_ = response_data; | |
mmenke
2015/08/04 19:54:30
Suggest response_data -> response_body everywhere.
Eran Messeri
2015/08/05 13:10:52
Done.
| |
96 } | |
97 | |
98 void SetAsyncIO(bool async_io) { async_io_ = async_io; } | |
mmenke
2015/08/04 19:54:30
set_asnyc_io
Eran Messeri
2015/08/05 13:10:52
Done.
| |
99 | |
100 void SetResponseCode(int response_code) { response_code_ = response_code; } | |
mmenke
2015/08/04 19:54:30
set_response_code (Though, per earlier comment, I
Eran Messeri
2015/08/05 13:10:53
Done.
| |
101 | |
102 private: | |
103 bool async_io_; | |
104 std::string response_data_; | |
105 int response_code_; | |
106 | |
107 DISALLOW_COPY_AND_ASSIGN(GetSTHResponseHandler); | |
108 }; | |
109 | |
110 class RecordFetchCallbackInvocations { | |
111 public: | |
112 RecordFetchCallbackInvocations() : expect_success_(false), invoked_(false) {} | |
113 | |
114 virtual void STHFetched(const std::string& log_id, | |
mmenke
2015/08/04 19:54:30
virtual no longer needed
Eran Messeri
2015/08/05 13:10:52
Done.
| |
115 const net::ct::SignedTreeHead& sth) { | |
mmenke
2015/08/04 19:54:30
ASSERT_FALSE(invoked_);
Eran Messeri
2015/08/05 13:10:52
Done.
| |
116 ASSERT_TRUE(expect_success_); | |
117 invoked_ = true; | |
118 // If expected to succeed, expecting the known_good STH. | |
119 net::ct::SignedTreeHead known_sth; | |
120 net::ct::GetSampleSignedTreeHead(&known_sth); | |
mmenke
2015/08/04 19:54:30
suggest known -> expected (More common for tests,
Eran Messeri
2015/08/05 13:10:52
Done.
| |
121 | |
122 ASSERT_EQ(kLogID, log_id); | |
123 ASSERT_EQ(sth.version, known_sth.version); | |
124 ASSERT_EQ(sth.timestamp, known_sth.timestamp); | |
125 ASSERT_EQ(sth.tree_size, known_sth.tree_size); | |
126 ASSERT_STREQ(sth.sha256_root_hash, known_sth.sha256_root_hash); | |
127 ASSERT_EQ(sth.signature.hash_algorithm, known_sth.signature.hash_algorithm); | |
128 ASSERT_EQ(sth.signature.signature_algorithm, | |
129 known_sth.signature.signature_algorithm); | |
130 ASSERT_EQ(sth.signature.signature_data, known_sth.signature.signature_data); | |
mmenke
2015/08/04 19:54:30
For all of these, expected should be first.
Eran Messeri
2015/08/05 13:10:52
Done.
| |
131 } | |
132 | |
133 void FetchingFailed(const std::string& log_id, | |
134 int net_error, | |
135 int http_response_code) { | |
mmenke
2015/08/04 19:54:30
ASSERT_FALSE(invoked_);
Eran Messeri
2015/08/05 13:10:52
Done.
| |
136 ASSERT_FALSE(expect_success_); | |
137 invoked_ = true; | |
138 net_error_ = net_error; | |
139 http_response_code_ = http_response_code; | |
140 if (net_error_ == net::OK) { | |
141 ASSERT_NE(net::HTTP_OK, http_response_code_); | |
142 } | |
143 } | |
144 | |
145 bool invoked() { return invoked_; } | |
mmenke
2015/08/04 19:54:30
bool invoked() const ...
Eran Messeri
2015/08/05 13:10:52
Done.
| |
146 | |
147 int net_error() { return net_error_; } | |
mmenke
2015/08/04 19:54:30
bool net_error() const ...
Eran Messeri
2015/08/05 13:10:52
Done.
| |
148 | |
149 int http_response_code() { return http_response_code_; } | |
mmenke
2015/08/04 19:54:30
const
Eran Messeri
2015/08/05 13:10:52
Done.
| |
150 | |
151 void set_expect_success(bool expect_success) { | |
152 expect_success_ = expect_success; | |
153 } | |
154 | |
155 private: | |
156 bool expect_success_; | |
mmenke
2015/08/04 19:54:30
optional: Suggest making this a constructor param
Eran Messeri
2015/08/05 13:10:52
Done.
| |
157 | |
158 bool invoked_; | |
159 int net_error_; | |
160 int http_response_code_; | |
mmenke
2015/08/04 19:54:30
All of these should be initialized in the construc
Eran Messeri
2015/08/05 13:10:52
Done.
| |
161 }; | |
162 | |
163 class LogProofFetcherTest : public ::testing::Test { | |
164 public: | |
165 LogProofFetcherTest() | |
166 : log_url_(std::string(kLogSchema) + "://" + std::string(kLogURL) + "/") { | |
167 scoped_ptr<GetSTHResponseHandler> handler(new GetSTHResponseHandler()); | |
168 handler_ = handler.get(); | |
169 | |
170 net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | |
171 kLogSchema, kLogURL, handler.Pass()); | |
172 | |
173 fetcher_.reset(new LogProofFetcher(&context_)); | |
174 } | |
175 | |
176 ~LogProofFetcherTest() override { | |
177 net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(kLogSchema, | |
178 kLogURL); | |
179 } | |
180 | |
181 protected: | |
182 void SetValidSTHJSONResponse() { | |
183 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
184 handler_->SetResponseData(sth_json_reply_data); | |
185 } | |
186 | |
187 void RunFetcherWithCallback(RecordFetchCallbackInvocations* callback) { | |
188 fetcher_->FetchSignedTreeHead( | |
189 log_url_, kLogID, | |
190 base::Bind(&RecordFetchCallbackInvocations::STHFetched, | |
191 base::Unretained(callback)), | |
192 base::Bind(&RecordFetchCallbackInvocations::FetchingFailed, | |
193 base::Unretained(callback))); | |
194 message_loop_.RunUntilIdle(); | |
195 } | |
196 | |
197 base::MessageLoopForIO message_loop_; | |
198 net::TestURLRequestContext context_; | |
199 safe_json::TestingJsonParser::ScopedFactoryOverride factory_override_; | |
200 scoped_ptr<LogProofFetcher> fetcher_; | |
201 const GURL log_url_; | |
202 GetSTHResponseHandler* handler_; | |
203 }; | |
204 | |
205 TEST_F(LogProofFetcherTest, TestValidGetReply) { | |
206 SetValidSTHJSONResponse(); | |
207 | |
208 RecordFetchCallbackInvocations callback; | |
209 callback.set_expect_success(true); | |
210 | |
211 RunFetcherWithCallback(&callback); | |
212 | |
213 ASSERT_TRUE(callback.invoked()); | |
214 } | |
215 | |
216 TEST_F(LogProofFetcherTest, TestValidGetReplyAsyncIO) { | |
217 SetValidSTHJSONResponse(); | |
218 handler_->SetAsyncIO(true); | |
219 | |
220 RecordFetchCallbackInvocations callback; | |
221 callback.set_expect_success(true); | |
222 RunFetcherWithCallback(&callback); | |
223 | |
224 ASSERT_TRUE(callback.invoked()); | |
225 } | |
226 | |
227 TEST_F(LogProofFetcherTest, TestInvalidGetReplyIncompleteJSON) { | |
228 std::string sth_json_reply_data = net::ct::CreateSignedTreeHeadJsonString( | |
229 21 /* tree_size */, 123456u /* timestamp */, std::string(""), | |
230 std::string("")); | |
mmenke
2015/08/04 19:54:30
nit: std::string() (x2)
Eran Messeri
2015/08/05 13:10:52
Done.
| |
231 handler_->SetResponseData(sth_json_reply_data); | |
232 | |
233 RecordFetchCallbackInvocations callback; | |
234 callback.set_expect_success(false); | |
235 RunFetcherWithCallback(&callback); | |
236 | |
237 ASSERT_TRUE(callback.invoked()); | |
238 ASSERT_EQ(net::ERR_CT_STH_INCOMPLETE, callback.net_error()); | |
239 } | |
240 | |
241 TEST_F(LogProofFetcherTest, TestInvalidGetReplyInvalidJSON) { | |
242 std::string sth_json_reply_data = "{\"tree_size\":21,\"timestamp\":}"; | |
243 handler_->SetResponseData(sth_json_reply_data); | |
244 | |
245 RecordFetchCallbackInvocations callback; | |
246 callback.set_expect_success(false); | |
247 RunFetcherWithCallback(&callback); | |
248 | |
249 ASSERT_TRUE(callback.invoked()); | |
250 ASSERT_EQ(net::ERR_CT_STH_PARSING_FAILED, callback.net_error()); | |
251 } | |
252 | |
253 TEST_F(LogProofFetcherTest, TestLogReplyIsTooLong) { | |
254 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
255 // Add kMaxLogResponseSizeInBytes to make sure the response is too big. | |
256 sth_json_reply_data.append(std::string(kMaxLogResponseSizeInBytes, ' ')); | |
257 handler_->SetResponseData(sth_json_reply_data); | |
258 | |
259 RecordFetchCallbackInvocations callback; | |
260 callback.set_expect_success(false); | |
261 RunFetcherWithCallback(&callback); | |
262 | |
263 ASSERT_TRUE(callback.invoked()); | |
264 ASSERT_EQ(net::ERR_FILE_TOO_BIG, callback.net_error()); | |
265 ASSERT_EQ(net::HTTP_OK, callback.http_response_code()); | |
266 } | |
267 | |
268 TEST_F(LogProofFetcherTest, TestLogReplyIsExactlyMaxSize) { | |
269 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
270 // Extend the reply to be exactly kMaxLogResponseSizeInBytes. | |
271 sth_json_reply_data.append(std::string( | |
272 kMaxLogResponseSizeInBytes - sth_json_reply_data.size(), ' ')); | |
273 handler_->SetResponseData(sth_json_reply_data); | |
274 | |
275 RecordFetchCallbackInvocations callback; | |
276 callback.set_expect_success(true); | |
277 RunFetcherWithCallback(&callback); | |
278 | |
279 ASSERT_TRUE(callback.invoked()); | |
280 } | |
281 | |
282 TEST_F(LogProofFetcherTest, TestLogRepliesWithHttpError) { | |
283 handler_->SetResponseCode(net::HTTP_NOT_FOUND); | |
284 | |
285 RecordFetchCallbackInvocations callback; | |
286 callback.set_expect_success(false); | |
287 RunFetcherWithCallback(&callback); | |
288 | |
289 ASSERT_TRUE(callback.invoked()); | |
290 ASSERT_EQ(net::OK, callback.net_error()); | |
291 ASSERT_EQ(net::HTTP_NOT_FOUND, callback.http_response_code()); | |
292 } | |
293 | |
294 } // namespace | |
295 | |
296 } // namespace certificate_transparency | |
OLD | NEW |