Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Side by Side Diff: remoting/protocol/validating_authenticator_unittest.cc

Issue 2724223003: Disconnect all users if too many connection requests are received for It2Me (Closed)
Patch Set: Addressing CR Feedback Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « remoting/protocol/validating_authenticator.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <memory> 5 #include <memory>
6 #include <string> 6 #include <string>
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/callback.h" 10 #include "base/callback.h"
(...skipping 11 matching lines...) Expand all
22 namespace remoting { 22 namespace remoting {
23 namespace protocol { 23 namespace protocol {
24 24
25 namespace { 25 namespace {
26 26
27 using testing::_; 27 using testing::_;
28 using testing::Return; 28 using testing::Return;
29 29
30 typedef ValidatingAuthenticator::Result ValidationResult; 30 typedef ValidatingAuthenticator::Result ValidationResult;
31 31
32 const char kRemoteTestJid[] = "ficticious_jid_for_testing"; 32 constexpr char kRemoteTestJid[] = "ficticious_jid_for_testing";
33 33
34 // testing::InvokeArgument<N> does not work with base::Callback, fortunately 34 // testing::InvokeArgument<N> does not work with base::Callback, fortunately
35 // gmock makes it simple to create action templates that do for the various 35 // gmock makes it simple to create action templates that do for the various
36 // possible numbers of arguments. 36 // possible numbers of arguments.
37 ACTION_TEMPLATE(InvokeCallbackArgument, 37 ACTION_TEMPLATE(InvokeCallbackArgument,
38 HAS_1_TEMPLATE_PARAMS(int, k), 38 HAS_1_TEMPLATE_PARAMS(int, k),
39 AND_0_VALUE_PARAMS()) { 39 AND_0_VALUE_PARAMS()) {
40 ::std::tr1::get<k>(args).Run(); 40 ::std::tr1::get<k>(args).Run();
41 } 41 }
42 42
(...skipping 13 matching lines...) Expand all
56 void SetUp() override; 56 void SetUp() override;
57 57
58 // Calls ProcessMessage() on |validating_authenticator_| and blocks until 58 // Calls ProcessMessage() on |validating_authenticator_| and blocks until
59 // the result callback is called. 59 // the result callback is called.
60 void SendMessageAndWaitForCallback(); 60 void SendMessageAndWaitForCallback();
61 61
62 // Used to set up our mock behaviors on the MockAuthenticator object passed 62 // Used to set up our mock behaviors on the MockAuthenticator object passed
63 // to |validating_authenticator_|. Lifetime of the object is controlled by 63 // to |validating_authenticator_|. Lifetime of the object is controlled by
64 // |validating_authenticator_| so this pointer is no longer valid once 64 // |validating_authenticator_| so this pointer is no longer valid once
65 // the owner is destroyed. 65 // the owner is destroyed.
66 MockAuthenticator* mock_authenticator_ = nullptr; 66 testing::NiceMock<MockAuthenticator>* mock_authenticator_ = nullptr;
67 67
68 // This member is used to drive behavior in |validating_authenticator_| when 68 // This member is used to drive behavior in |validating_authenticator_| when
69 // it's validation complete callback is run. 69 // its validation complete callback is run.
70 ValidationResult validation_result_ = ValidationResult::SUCCESS; 70 ValidationResult validation_result_ = ValidationResult::SUCCESS;
71 71
72 // Tracks whether our ValidateCallback has been called or not. 72 // Tracks whether our validation callback has been called or not.
73 bool validate_complete_called_ = false; 73 bool validate_complete_called_ = false;
74 74
75 // The object under test. 75 // The object under test.
76 std::unique_ptr<ValidatingAuthenticator> validating_authenticator_; 76 std::unique_ptr<ValidatingAuthenticator> validating_authenticator_;
77 77
78 private: 78 private:
79 base::MessageLoop message_loop_; 79 base::MessageLoop message_loop_;
80 80
81 DISALLOW_COPY_AND_ASSIGN(ValidatingAuthenticatorTest); 81 DISALLOW_COPY_AND_ASSIGN(ValidatingAuthenticatorTest);
82 }; 82 };
83 83
84 ValidatingAuthenticatorTest::ValidatingAuthenticatorTest() {} 84 ValidatingAuthenticatorTest::ValidatingAuthenticatorTest() {}
85 85
86 ValidatingAuthenticatorTest::~ValidatingAuthenticatorTest() {} 86 ValidatingAuthenticatorTest::~ValidatingAuthenticatorTest() {}
87 87
88 void ValidatingAuthenticatorTest::ValidateCallback( 88 void ValidatingAuthenticatorTest::ValidateCallback(
89 const std::string& remote_jid, 89 const std::string& remote_jid,
90 const ValidatingAuthenticator::ResultCallback& callback) { 90 const ValidatingAuthenticator::ResultCallback& callback) {
91 validate_complete_called_ = true; 91 validate_complete_called_ = true;
92 callback.Run(validation_result_); 92 callback.Run(validation_result_);
93 } 93 }
94 94
95 void ValidatingAuthenticatorTest::SetUp() { 95 void ValidatingAuthenticatorTest::SetUp() {
96 mock_authenticator_ = new MockAuthenticator(); 96 mock_authenticator_ = new testing::NiceMock<MockAuthenticator>();
97 std::unique_ptr<Authenticator> authenticator(mock_authenticator_); 97 std::unique_ptr<Authenticator> authenticator(mock_authenticator_);
98 98
99 validating_authenticator_.reset(new ValidatingAuthenticator( 99 validating_authenticator_.reset(new ValidatingAuthenticator(
100 kRemoteTestJid, base::Bind(&ValidatingAuthenticatorTest::ValidateCallback, 100 kRemoteTestJid, base::Bind(&ValidatingAuthenticatorTest::ValidateCallback,
101 base::Unretained(this)), 101 base::Unretained(this)),
102 std::move(authenticator))); 102 std::move(authenticator)));
103 } 103 }
104 104
105 void ValidatingAuthenticatorTest::SendMessageAndWaitForCallback() { 105 void ValidatingAuthenticatorTest::SendMessageAndWaitForCallback() {
106 base::RunLoop run_loop; 106 base::RunLoop run_loop;
107 std::unique_ptr<buzz::XmlElement> first_message( 107 std::unique_ptr<buzz::XmlElement> first_message(
108 Authenticator::CreateEmptyAuthenticatorMessage()); 108 Authenticator::CreateEmptyAuthenticatorMessage());
109 validating_authenticator_->ProcessMessage(first_message.get(), 109 validating_authenticator_->ProcessMessage(first_message.get(),
110 run_loop.QuitClosure()); 110 run_loop.QuitClosure());
111 run_loop.Run(); 111 run_loop.Run();
112 } 112 }
113 113
114 TEST_F(ValidatingAuthenticatorTest, ValidConnection_SingleMessage) { 114 TEST_F(ValidatingAuthenticatorTest, ValidConnection_SingleMessage) {
115 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)) 115 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
116 .Times(1) 116 .Times(1)
117 .WillOnce(InvokeCallbackArgument<1>()); 117 .WillOnce(InvokeCallbackArgument<1>());
118 118
119 ON_CALL(*mock_authenticator_, state()) 119 ON_CALL(*mock_authenticator_, state())
120 .WillByDefault(Return(Authenticator::ACCEPTED)); 120 .WillByDefault(Return(Authenticator::ACCEPTED));
121 121
122 SendMessageAndWaitForCallback(); 122 SendMessageAndWaitForCallback();
123 ASSERT_TRUE(validate_complete_called_); 123 ASSERT_TRUE(validate_complete_called_);
124 ASSERT_EQ(validating_authenticator_->state(), Authenticator::ACCEPTED); 124 ASSERT_EQ(Authenticator::ACCEPTED, validating_authenticator_->state());
125 } 125 }
126 126
127 TEST_F(ValidatingAuthenticatorTest, ValidConnection_TwoMessages) { 127 TEST_F(ValidatingAuthenticatorTest, ValidConnection_TwoMessages) {
128 // Send the first message to the authenticator, set the mock up to act 128 // Send the first message to the authenticator, set the mock up to act
129 // like it is waiting for a second message. 129 // like it is waiting for a second message.
130 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)) 130 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
131 .Times(2) 131 .Times(2)
132 .WillRepeatedly(InvokeCallbackArgument<1>()); 132 .WillRepeatedly(InvokeCallbackArgument<1>());
133 133
134 EXPECT_CALL(*mock_authenticator_, state()) 134 EXPECT_CALL(*mock_authenticator_, state())
135 .WillRepeatedly(Return(Authenticator::MESSAGE_READY)); 135 .WillRepeatedly(Return(Authenticator::MESSAGE_READY));
136 136
137 SendMessageAndWaitForCallback(); 137 SendMessageAndWaitForCallback();
138 ASSERT_TRUE(validate_complete_called_); 138 ASSERT_FALSE(validate_complete_called_);
139 ASSERT_EQ(validating_authenticator_->state(), Authenticator::MESSAGE_READY); 139 ASSERT_EQ(Authenticator::MESSAGE_READY, validating_authenticator_->state());
140 140
141 // Now 'retrieve' the message for the client which resets the state. 141 // Now 'retrieve' the message for the client which resets the state.
142 EXPECT_CALL(*mock_authenticator_, state()) 142 EXPECT_CALL(*mock_authenticator_, state())
143 .WillRepeatedly(Return(Authenticator::WAITING_MESSAGE)); 143 .WillRepeatedly(Return(Authenticator::WAITING_MESSAGE));
144 144
145 // This dance is needed because GMock doesn't handle unique_ptrs very well. 145 // This dance is needed because GMock doesn't handle unique_ptrs very well.
146 // The mock method receives a raw pointer which it wraps and returns when 146 // The mock method receives a raw pointer which it wraps and returns when
147 // GetNextMessage() is called. 147 // GetNextMessage() is called.
148 std::unique_ptr<buzz::XmlElement> next_message( 148 std::unique_ptr<buzz::XmlElement> next_message(
149 Authenticator::CreateEmptyAuthenticatorMessage()); 149 Authenticator::CreateEmptyAuthenticatorMessage());
150 EXPECT_CALL(*mock_authenticator_, GetNextMessagePtr()) 150 EXPECT_CALL(*mock_authenticator_, GetNextMessagePtr())
151 .Times(1) 151 .Times(1)
152 .WillOnce(Return(next_message.release())); 152 .WillOnce(Return(next_message.release()));
153 153
154 validating_authenticator_->GetNextMessage(); 154 validating_authenticator_->GetNextMessage();
155 ASSERT_EQ(validating_authenticator_->state(), Authenticator::WAITING_MESSAGE); 155 ASSERT_EQ(Authenticator::WAITING_MESSAGE, validating_authenticator_->state());
156 156
157 // Now send the second message for processing. 157 // Now send the second message for processing.
158 EXPECT_CALL(*mock_authenticator_, state()) 158 EXPECT_CALL(*mock_authenticator_, state())
159 .WillRepeatedly(Return(Authenticator::ACCEPTED)); 159 .WillRepeatedly(Return(Authenticator::ACCEPTED));
160 160
161 // Reset the callback state, we don't expect the validate function to be
162 // called for the second message.
163 validate_complete_called_ = false;
164 SendMessageAndWaitForCallback(); 161 SendMessageAndWaitForCallback();
165 ASSERT_FALSE(validate_complete_called_); 162 ASSERT_TRUE(validate_complete_called_);
166 ASSERT_EQ(validating_authenticator_->state(), Authenticator::ACCEPTED); 163 ASSERT_EQ(Authenticator::ACCEPTED, validating_authenticator_->state());
167 } 164 }
168 165
169 TEST_F(ValidatingAuthenticatorTest, InvalidConnection_RejectedByUser) { 166 TEST_F(ValidatingAuthenticatorTest, ValidConnection_SendBeforeAccept) {
170 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)).Times(0); 167 // This test simulates an authenticator which needs to send a message before
171 EXPECT_CALL(*mock_authenticator_, state()).Times(0); 168 // transitioning to the ACCEPTED state.
172 EXPECT_CALL(*mock_authenticator_, rejection_reason()).Times(0); 169 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
170 .Times(1)
171 .WillRepeatedly(InvokeCallbackArgument<1>());
172
173 EXPECT_CALL(*mock_authenticator_, state())
174 .WillOnce(Return(Authenticator::MESSAGE_READY))
175 .WillOnce(Return(Authenticator::ACCEPTED));
176
177 // This dance is needed because GMock doesn't handle unique_ptrs very well.
178 // The mock method receives a raw pointer which it wraps and returns when
179 // GetNextMessage() is called.
180 std::unique_ptr<buzz::XmlElement> next_message(
181 Authenticator::CreateEmptyAuthenticatorMessage());
182 EXPECT_CALL(*mock_authenticator_, GetNextMessagePtr())
183 .Times(1)
184 .WillOnce(Return(next_message.release()));
185
186 SendMessageAndWaitForCallback();
187 ASSERT_TRUE(validate_complete_called_);
188 ASSERT_EQ(Authenticator::MESSAGE_READY, validating_authenticator_->state());
189
190 // Now 'retrieve' the message for the client which resets the state.
191 validating_authenticator_->GetNextMessage();
192 ASSERT_EQ(Authenticator::ACCEPTED, validating_authenticator_->state());
193 }
194
195 TEST_F(ValidatingAuthenticatorTest, ValidConnection_ErrorInvalidCredentials) {
196 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
197 .Times(1)
198 .WillOnce(InvokeCallbackArgument<1>());
199
200 ON_CALL(*mock_authenticator_, state())
201 .WillByDefault(Return(Authenticator::ACCEPTED));
202
203 validation_result_ = ValidationResult::ERROR_INVALID_CREDENTIALS;
204
205 SendMessageAndWaitForCallback();
206 ASSERT_TRUE(validate_complete_called_);
207 ASSERT_EQ(Authenticator::REJECTED, validating_authenticator_->state());
208 ASSERT_EQ(Authenticator::INVALID_CREDENTIALS,
209 validating_authenticator_->rejection_reason());
210 }
211
212 TEST_F(ValidatingAuthenticatorTest, ValidConnection_ErrorRejectedByUser) {
213 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
214 .Times(1)
215 .WillOnce(InvokeCallbackArgument<1>());
216
217 ON_CALL(*mock_authenticator_, state())
218 .WillByDefault(Return(Authenticator::ACCEPTED));
173 219
174 validation_result_ = ValidationResult::ERROR_REJECTED_BY_USER; 220 validation_result_ = ValidationResult::ERROR_REJECTED_BY_USER;
175 221
176 SendMessageAndWaitForCallback(); 222 SendMessageAndWaitForCallback();
177 ASSERT_TRUE(validate_complete_called_); 223 ASSERT_TRUE(validate_complete_called_);
178 ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED); 224 ASSERT_EQ(Authenticator::REJECTED, validating_authenticator_->state());
179 ASSERT_EQ(validating_authenticator_->rejection_reason(), 225 ASSERT_EQ(Authenticator::REJECTED_BY_USER,
180 Authenticator::REJECTED_BY_USER); 226 validating_authenticator_->rejection_reason());
181 } 227 }
182 228
183 TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidCredentials) { 229 TEST_F(ValidatingAuthenticatorTest, ValidConnection_ErrorTooManyConnections) {
184 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)).Times(0);
185 EXPECT_CALL(*mock_authenticator_, state()).Times(0);
186 EXPECT_CALL(*mock_authenticator_, rejection_reason()).Times(0);
187
188 validation_result_ = ValidationResult::ERROR_INVALID_CREDENTIALS;
189
190 SendMessageAndWaitForCallback();
191 ASSERT_TRUE(validate_complete_called_);
192 ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
193 ASSERT_EQ(validating_authenticator_->rejection_reason(),
194 Authenticator::INVALID_CREDENTIALS);
195 }
196
197 TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidAccount) {
198 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)).Times(0);
199 EXPECT_CALL(*mock_authenticator_, state()).Times(0);
200 EXPECT_CALL(*mock_authenticator_, rejection_reason()).Times(0);
201
202 validation_result_ = ValidationResult::ERROR_INVALID_ACCOUNT;
203
204 SendMessageAndWaitForCallback();
205 ASSERT_TRUE(validate_complete_called_);
206 ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
207 ASSERT_EQ(validating_authenticator_->rejection_reason(),
208 Authenticator::INVALID_ACCOUNT);
209 }
210
211 TEST_F(ValidatingAuthenticatorTest,
212 WrappedAuthenticatorRejectsConnection_InvalidCredentials) {
213 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)) 230 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
214 .Times(1) 231 .Times(1)
215 .WillOnce(InvokeCallbackArgument<1>()); 232 .WillOnce(InvokeCallbackArgument<1>());
216 233
217 ON_CALL(*mock_authenticator_, state()) 234 ON_CALL(*mock_authenticator_, state())
218 .WillByDefault(Return(Authenticator::REJECTED)); 235 .WillByDefault(Return(Authenticator::ACCEPTED));
219 236
220 ON_CALL(*mock_authenticator_, rejection_reason()) 237 validation_result_ = ValidationResult::ERROR_TOO_MANY_CONNECTIONS;
221 .WillByDefault(Return(Authenticator::REJECTED_BY_USER));
222 238
223 SendMessageAndWaitForCallback(); 239 SendMessageAndWaitForCallback();
224 ASSERT_TRUE(validate_complete_called_); 240 ASSERT_TRUE(validate_complete_called_);
225 ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED); 241 ASSERT_EQ(Authenticator::REJECTED, validating_authenticator_->state());
226 ASSERT_EQ(validating_authenticator_->rejection_reason(), 242 ASSERT_EQ(Authenticator::TOO_MANY_CONNECTIONS,
227 Authenticator::REJECTED_BY_USER); 243 validating_authenticator_->rejection_reason());
228 } 244 }
229 245
230 TEST_F(ValidatingAuthenticatorTest, 246 TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidCredentials) {
231 WrappedAuthenticatorRejectsConnection_InvalidAccount) {
232 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)) 247 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
233 .Times(1) 248 .Times(1)
234 .WillOnce(InvokeCallbackArgument<1>()); 249 .WillOnce(InvokeCallbackArgument<1>());
235 250
236 ON_CALL(*mock_authenticator_, state()) 251 ON_CALL(*mock_authenticator_, state())
237 .WillByDefault(Return(Authenticator::REJECTED)); 252 .WillByDefault(Return(Authenticator::REJECTED));
238 253
239 ON_CALL(*mock_authenticator_, rejection_reason()) 254 ON_CALL(*mock_authenticator_, rejection_reason())
240 .WillByDefault(Return(Authenticator::INVALID_CREDENTIALS)); 255 .WillByDefault(Return(Authenticator::INVALID_CREDENTIALS));
241 256
257 // Verify validation callback is not called for invalid connections.
242 SendMessageAndWaitForCallback(); 258 SendMessageAndWaitForCallback();
243 ASSERT_TRUE(validate_complete_called_); 259 ASSERT_FALSE(validate_complete_called_);
244 ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED); 260 ASSERT_EQ(Authenticator::REJECTED, validating_authenticator_->state());
245 ASSERT_EQ(validating_authenticator_->rejection_reason(), 261 ASSERT_EQ(Authenticator::INVALID_CREDENTIALS,
246 Authenticator::INVALID_CREDENTIALS); 262 validating_authenticator_->rejection_reason());
247 } 263 }
248 264
249 TEST_F(ValidatingAuthenticatorTest, 265 TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidAccount) {
250 WrappedAuthenticatorRejectsConnection_PROTOCOL_ERROR) {
251 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _)) 266 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
252 .Times(1) 267 .Times(1)
253 .WillOnce(InvokeCallbackArgument<1>()); 268 .WillOnce(InvokeCallbackArgument<1>());
269
270 ON_CALL(*mock_authenticator_, state())
271 .WillByDefault(Return(Authenticator::REJECTED));
272
273 ON_CALL(*mock_authenticator_, rejection_reason())
274 .WillByDefault(Return(Authenticator::INVALID_ACCOUNT));
275
276 // Verify validation callback is not called for invalid connections.
277 SendMessageAndWaitForCallback();
278 ASSERT_FALSE(validate_complete_called_);
279 ASSERT_EQ(Authenticator::REJECTED, validating_authenticator_->state());
280 ASSERT_EQ(Authenticator::INVALID_ACCOUNT,
281 validating_authenticator_->rejection_reason());
282 }
283
284 TEST_F(ValidatingAuthenticatorTest, InvalidConnection_ProtocolError) {
285 EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
286 .Times(1)
287 .WillOnce(InvokeCallbackArgument<1>());
254 288
255 ON_CALL(*mock_authenticator_, state()) 289 ON_CALL(*mock_authenticator_, state())
256 .WillByDefault(Return(Authenticator::REJECTED)); 290 .WillByDefault(Return(Authenticator::REJECTED));
257 291
258 ON_CALL(*mock_authenticator_, rejection_reason()) 292 ON_CALL(*mock_authenticator_, rejection_reason())
259 .WillByDefault(Return(Authenticator::PROTOCOL_ERROR)); 293 .WillByDefault(Return(Authenticator::PROTOCOL_ERROR));
260 294
295 // Verify validation callback is not called for invalid connections.
261 SendMessageAndWaitForCallback(); 296 SendMessageAndWaitForCallback();
262 ASSERT_TRUE(validate_complete_called_); 297 ASSERT_FALSE(validate_complete_called_);
263 ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED); 298 ASSERT_EQ(Authenticator::REJECTED, validating_authenticator_->state());
264 ASSERT_EQ(validating_authenticator_->rejection_reason(), 299 ASSERT_EQ(Authenticator::PROTOCOL_ERROR,
265 Authenticator::PROTOCOL_ERROR); 300 validating_authenticator_->rejection_reason());
266 } 301 }
267 302
268 } // namespace protocol 303 } // namespace protocol
269 } // namespace remoting 304 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/protocol/validating_authenticator.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698