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

Side by Side Diff: sync/internal_api/attachments/attachment_downloader_impl_unittest.cc

Issue 2130453004: [Sync] Move //sync to //components/sync. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 4 years, 4 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
OLDNEW
(Empty)
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 "sync/internal_api/public/attachments/attachment_downloader_impl.h"
6
7 #include <stdint.h>
8
9 #include <map>
10
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/run_loop.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/test/histogram_tester.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "google_apis/gaia/fake_oauth2_token_service.h"
20 #include "google_apis/gaia/gaia_constants.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/url_request/test_url_fetcher_factory.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "sync/api/attachments/attachment.h"
25 #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
26 #include "sync/internal_api/public/attachments/attachment_util.h"
27 #include "sync/internal_api/public/base/model_type.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "third_party/leveldatabase/src/util/crc32c.h"
30
31 namespace syncer {
32
33 namespace {
34
35 const char kAccountId[] = "attachments@gmail.com";
36 const char kAccessToken[] = "access.token";
37 const char kAttachmentServerUrl[] = "http://attachments.com/";
38 const char kAttachmentContent[] = "attachment.content";
39 const char kStoreBirthday[] = "z00000000-0000-007b-0000-0000000004d2";
40 const syncer::ModelType kModelType = syncer::ModelType::ARTICLES;
41
42 // MockOAuth2TokenService remembers last request for access token and verifies
43 // that only one request is active at a time.
44 // Call RespondToAccessTokenRequest to respond to it.
45 class MockOAuth2TokenService : public FakeOAuth2TokenService {
46 public:
47 MockOAuth2TokenService() : num_invalidate_token_(0) {}
48
49 ~MockOAuth2TokenService() override {}
50
51 void RespondToAccessTokenRequest(GoogleServiceAuthError error);
52
53 int num_invalidate_token() const { return num_invalidate_token_; }
54
55 protected:
56 void FetchOAuth2Token(RequestImpl* request,
57 const std::string& account_id,
58 net::URLRequestContextGetter* getter,
59 const std::string& client_id,
60 const std::string& client_secret,
61 const ScopeSet& scopes) override;
62
63 void InvalidateAccessTokenImpl(const std::string& account_id,
64 const std::string& client_id,
65 const ScopeSet& scopes,
66 const std::string& access_token) override;
67
68 private:
69 base::WeakPtr<RequestImpl> last_request_;
70 int num_invalidate_token_;
71 };
72
73 void MockOAuth2TokenService::RespondToAccessTokenRequest(
74 GoogleServiceAuthError error) {
75 EXPECT_TRUE(last_request_);
76 std::string access_token;
77 base::Time expiration_time;
78 if (error == GoogleServiceAuthError::AuthErrorNone()) {
79 access_token = kAccessToken;
80 expiration_time = base::Time::Max();
81 }
82 base::ThreadTaskRunnerHandle::Get()->PostTask(
83 FROM_HERE,
84 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
85 last_request_, error, access_token, expiration_time));
86 }
87
88 void MockOAuth2TokenService::FetchOAuth2Token(
89 RequestImpl* request,
90 const std::string& account_id,
91 net::URLRequestContextGetter* getter,
92 const std::string& client_id,
93 const std::string& client_secret,
94 const ScopeSet& scopes) {
95 // Only one request at a time is allowed.
96 EXPECT_FALSE(last_request_);
97 last_request_ = request->AsWeakPtr();
98 }
99
100 void MockOAuth2TokenService::InvalidateAccessTokenImpl(
101 const std::string& account_id,
102 const std::string& client_id,
103 const ScopeSet& scopes,
104 const std::string& access_token) {
105 ++num_invalidate_token_;
106 }
107
108 class TokenServiceProvider
109 : public OAuth2TokenServiceRequest::TokenServiceProvider,
110 base::NonThreadSafe {
111 public:
112 explicit TokenServiceProvider(OAuth2TokenService* token_service);
113
114 // OAuth2TokenService::TokenServiceProvider implementation.
115 scoped_refptr<base::SingleThreadTaskRunner> GetTokenServiceTaskRunner()
116 override;
117 OAuth2TokenService* GetTokenService() override;
118
119 private:
120 ~TokenServiceProvider() override;
121
122 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
123 OAuth2TokenService* token_service_;
124 };
125
126 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
127 : task_runner_(base::ThreadTaskRunnerHandle::Get()),
128 token_service_(token_service) {
129 DCHECK(token_service_);
130 }
131
132 TokenServiceProvider::~TokenServiceProvider() {
133 }
134
135 scoped_refptr<base::SingleThreadTaskRunner>
136 TokenServiceProvider::GetTokenServiceTaskRunner() {
137 return task_runner_;
138 }
139
140 OAuth2TokenService* TokenServiceProvider::GetTokenService() {
141 DCHECK(task_runner_->BelongsToCurrentThread());
142 return token_service_;
143 }
144
145 } // namespace
146
147 class AttachmentDownloaderImplTest : public testing::Test {
148 protected:
149 typedef std::map<AttachmentId, AttachmentDownloader::DownloadResult>
150 ResultsMap;
151
152 enum HashHeaderType {
153 HASH_HEADER_NONE,
154 HASH_HEADER_VALID,
155 HASH_HEADER_INVALID
156 };
157
158 AttachmentDownloaderImplTest()
159 : num_completed_downloads_(0),
160 attachment_id_(
161 Attachment::Create(new base::RefCountedStaticMemory(
162 kAttachmentContent,
163 strlen(kAttachmentContent))).GetId()) {}
164
165 void SetUp() override;
166 void TearDown() override;
167
168 AttachmentDownloader* downloader() { return attachment_downloader_.get(); }
169
170 MockOAuth2TokenService* token_service() { return token_service_.get(); }
171
172 int num_completed_downloads() { return num_completed_downloads_; }
173
174 const AttachmentId attachment_id() const { return attachment_id_; }
175
176 AttachmentDownloader::DownloadCallback download_callback(
177 const AttachmentId& id) {
178 return base::Bind(&AttachmentDownloaderImplTest::DownloadDone,
179 base::Unretained(this),
180 id);
181 }
182
183 // Respond with |response_code| and hash header of type |hash_header_type|.
184 void CompleteDownload(int response_code, HashHeaderType hash_header_type);
185
186 void DownloadDone(const AttachmentId& attachment_id,
187 const AttachmentDownloader::DownloadResult& result,
188 std::unique_ptr<Attachment> attachment);
189
190 void VerifyDownloadResult(const AttachmentId& attachment_id,
191 const AttachmentDownloader::DownloadResult& result);
192
193 void RunMessageLoop();
194
195 private:
196 static void AddHashHeader(HashHeaderType hash_header_type,
197 net::TestURLFetcher* fetcher);
198
199 base::MessageLoopForIO message_loop_;
200 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
201 net::TestURLFetcherFactory url_fetcher_factory_;
202 std::unique_ptr<MockOAuth2TokenService> token_service_;
203 std::unique_ptr<AttachmentDownloader> attachment_downloader_;
204 ResultsMap download_results_;
205 int num_completed_downloads_;
206 const AttachmentId attachment_id_;
207 };
208
209 void AttachmentDownloaderImplTest::SetUp() {
210 url_request_context_getter_ =
211 new net::TestURLRequestContextGetter(message_loop_.task_runner());
212 url_fetcher_factory_.set_remove_fetcher_on_delete(true);
213 token_service_.reset(new MockOAuth2TokenService());
214 token_service_->AddAccount(kAccountId);
215 scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>
216 token_service_provider(new TokenServiceProvider(token_service_.get()));
217
218 OAuth2TokenService::ScopeSet scopes;
219 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
220 attachment_downloader_ = AttachmentDownloader::Create(
221 GURL(kAttachmentServerUrl), url_request_context_getter_, kAccountId,
222 scopes, token_service_provider, std::string(kStoreBirthday), kModelType);
223 }
224
225 void AttachmentDownloaderImplTest::TearDown() {
226 RunMessageLoop();
227 }
228
229 void AttachmentDownloaderImplTest::CompleteDownload(
230 int response_code,
231 HashHeaderType hash_header_type) {
232 // TestURLFetcherFactory remembers last active URLFetcher.
233 net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
234 // There should be outstanding url fetch request.
235 EXPECT_TRUE(fetcher);
236 fetcher->set_status(net::URLRequestStatus());
237 fetcher->set_response_code(response_code);
238 if (response_code == net::HTTP_OK) {
239 fetcher->SetResponseString(kAttachmentContent);
240 }
241 AddHashHeader(hash_header_type, fetcher);
242
243 // Call URLFetcherDelegate.
244 net::URLFetcherDelegate* delegate = fetcher->delegate();
245 delegate->OnURLFetchComplete(fetcher);
246 RunMessageLoop();
247 // Once result is processed URLFetcher should be deleted.
248 fetcher = url_fetcher_factory_.GetFetcherByID(0);
249 EXPECT_FALSE(fetcher);
250 }
251
252 void AttachmentDownloaderImplTest::DownloadDone(
253 const AttachmentId& attachment_id,
254 const AttachmentDownloader::DownloadResult& result,
255 std::unique_ptr<Attachment> attachment) {
256 download_results_.insert(std::make_pair(attachment_id, result));
257 if (result == AttachmentDownloader::DOWNLOAD_SUCCESS) {
258 // Successful download should be accompanied by valid attachment with
259 // matching id and valid data.
260 EXPECT_TRUE(attachment);
261 EXPECT_EQ(attachment_id, attachment->GetId());
262
263 scoped_refptr<base::RefCountedMemory> data = attachment->GetData();
264 std::string data_as_string(data->front_as<char>(), data->size());
265 EXPECT_EQ(data_as_string, kAttachmentContent);
266 } else {
267 EXPECT_FALSE(attachment);
268 }
269 ++num_completed_downloads_;
270 }
271
272 void AttachmentDownloaderImplTest::VerifyDownloadResult(
273 const AttachmentId& attachment_id,
274 const AttachmentDownloader::DownloadResult& result) {
275 ResultsMap::const_iterator iter = download_results_.find(attachment_id);
276 EXPECT_TRUE(iter != download_results_.end());
277 EXPECT_EQ(iter->second, result);
278 }
279
280 void AttachmentDownloaderImplTest::RunMessageLoop() {
281 base::RunLoop run_loop;
282 run_loop.RunUntilIdle();
283 }
284
285 void AttachmentDownloaderImplTest::AddHashHeader(
286 HashHeaderType hash_header_type,
287 net::TestURLFetcher* fetcher) {
288 std::string header = "X-Goog-Hash: crc32c=";
289 scoped_refptr<net::HttpResponseHeaders> headers(
290 new net::HttpResponseHeaders(""));
291 switch (hash_header_type) {
292 case HASH_HEADER_NONE:
293 break;
294 case HASH_HEADER_VALID:
295 header += AttachmentUploaderImpl::FormatCrc32cHash(leveldb::crc32c::Value(
296 kAttachmentContent, strlen(kAttachmentContent)));
297 headers->AddHeader(header);
298 break;
299 case HASH_HEADER_INVALID:
300 header += "BOGUS1==";
301 headers->AddHeader(header);
302 break;
303 }
304 fetcher->set_response_headers(headers);
305 }
306
307 TEST_F(AttachmentDownloaderImplTest, HappyCase) {
308 AttachmentId id1 = attachment_id();
309 // DownloadAttachment should trigger RequestAccessToken.
310 downloader()->DownloadAttachment(id1, download_callback(id1));
311 RunMessageLoop();
312 // Return valid access token.
313 token_service()->RespondToAccessTokenRequest(
314 GoogleServiceAuthError::AuthErrorNone());
315 RunMessageLoop();
316 // Catch histogram entries.
317 base::HistogramTester histogram_tester;
318 // Check that there is outstanding URLFetcher request and complete it.
319 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
320 // Verify that the response code was logged properly.
321 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
322 net::HTTP_OK, 1);
323 // Verify that callback was called for the right id with the right result.
324 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
325 }
326
327 TEST_F(AttachmentDownloaderImplTest, SameIdMultipleDownloads) {
328 AttachmentId id1 = attachment_id();
329 base::HistogramTester histogram_tester;
330 // Call DownloadAttachment two times for the same id.
331 downloader()->DownloadAttachment(id1, download_callback(id1));
332 downloader()->DownloadAttachment(id1, download_callback(id1));
333 RunMessageLoop();
334 // Return valid access token.
335 token_service()->RespondToAccessTokenRequest(
336 GoogleServiceAuthError::AuthErrorNone());
337 RunMessageLoop();
338 // Start one more download after access token is received.
339 downloader()->DownloadAttachment(id1, download_callback(id1));
340 // Complete URLFetcher request.
341 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
342 // Verify that all download requests completed.
343 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
344 EXPECT_EQ(3, num_completed_downloads());
345
346 // Let's download the same attachment again.
347 downloader()->DownloadAttachment(id1, download_callback(id1));
348 RunMessageLoop();
349 // Verify that it didn't finish prematurely.
350 EXPECT_EQ(3, num_completed_downloads());
351 // Return valid access token.
352 token_service()->RespondToAccessTokenRequest(
353 GoogleServiceAuthError::AuthErrorNone());
354 RunMessageLoop();
355 // Complete URLFetcher request.
356 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
357 // Verify that all download requests completed.
358 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
359 EXPECT_EQ(4, num_completed_downloads());
360 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
361 net::HTTP_OK, 2);
362 }
363
364 TEST_F(AttachmentDownloaderImplTest, RequestAccessTokenFails) {
365 AttachmentId id1 = attachment_id();
366 AttachmentId id2 = AttachmentId::Create(id1.GetSize(), id1.GetCrc32c());
367 // Trigger first RequestAccessToken.
368 downloader()->DownloadAttachment(id1, download_callback(id1));
369 RunMessageLoop();
370 // Return valid access token.
371 token_service()->RespondToAccessTokenRequest(
372 GoogleServiceAuthError::AuthErrorNone());
373 RunMessageLoop();
374 // Trigger second RequestAccessToken.
375 downloader()->DownloadAttachment(id2, download_callback(id2));
376 RunMessageLoop();
377 // Fail RequestAccessToken.
378 token_service()->RespondToAccessTokenRequest(
379 GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
380 RunMessageLoop();
381 // Only id2 should fail.
382 VerifyDownloadResult(id2, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
383 // Complete request for id1.
384 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
385 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
386 }
387
388 TEST_F(AttachmentDownloaderImplTest, URLFetcher_BadToken) {
389 AttachmentId id1 = attachment_id();
390 downloader()->DownloadAttachment(id1, download_callback(id1));
391 RunMessageLoop();
392 // Return valid access token.
393 token_service()->RespondToAccessTokenRequest(
394 GoogleServiceAuthError::AuthErrorNone());
395 RunMessageLoop();
396 // Fail URLFetcher. This should trigger download failure and access token
397 // invalidation.
398 base::HistogramTester histogram_tester;
399 CompleteDownload(net::HTTP_UNAUTHORIZED, HASH_HEADER_VALID);
400 EXPECT_EQ(1, token_service()->num_invalidate_token());
401 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
402 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
403 net::HTTP_UNAUTHORIZED, 1);
404 }
405
406 TEST_F(AttachmentDownloaderImplTest, URLFetcher_ServiceUnavailable) {
407 AttachmentId id1 = attachment_id();
408 downloader()->DownloadAttachment(id1, download_callback(id1));
409 RunMessageLoop();
410 // Return valid access token.
411 token_service()->RespondToAccessTokenRequest(
412 GoogleServiceAuthError::AuthErrorNone());
413 RunMessageLoop();
414 // Fail URLFetcher. This should trigger download failure. Access token
415 // shouldn't be invalidated.
416 base::HistogramTester histogram_tester;
417 CompleteDownload(net::HTTP_SERVICE_UNAVAILABLE, HASH_HEADER_VALID);
418 EXPECT_EQ(0, token_service()->num_invalidate_token());
419 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
420 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
421 net::HTTP_SERVICE_UNAVAILABLE, 1);
422 }
423
424 // Verify that if no hash is present on the response the downloader accepts the
425 // received attachment.
426 TEST_F(AttachmentDownloaderImplTest, NoHash) {
427 AttachmentId id1 = attachment_id();
428 downloader()->DownloadAttachment(id1, download_callback(id1));
429 RunMessageLoop();
430 token_service()->RespondToAccessTokenRequest(
431 GoogleServiceAuthError::AuthErrorNone());
432 RunMessageLoop();
433 CompleteDownload(net::HTTP_OK, HASH_HEADER_NONE);
434 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
435 }
436
437 // Verify that if an invalid hash is present on the response the downloader
438 // treats it as a transient error.
439 TEST_F(AttachmentDownloaderImplTest, InvalidHash) {
440 AttachmentId id1 = attachment_id();
441 downloader()->DownloadAttachment(id1, download_callback(id1));
442 RunMessageLoop();
443 token_service()->RespondToAccessTokenRequest(
444 GoogleServiceAuthError::AuthErrorNone());
445 RunMessageLoop();
446 CompleteDownload(net::HTTP_OK, HASH_HEADER_INVALID);
447 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
448 }
449
450 // Verify that when the hash from the attachment id does not match the one on
451 // the response the result is an unspecified error.
452 TEST_F(AttachmentDownloaderImplTest, IdHashDoesNotMatch) {
453 // id1 has the wrong crc32c.
454 AttachmentId id1 = AttachmentId::Create(attachment_id().GetSize(), 12345);
455 downloader()->DownloadAttachment(id1, download_callback(id1));
456 RunMessageLoop();
457 token_service()->RespondToAccessTokenRequest(
458 GoogleServiceAuthError::AuthErrorNone());
459 RunMessageLoop();
460 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
461 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR);
462 }
463
464
465 // Verify that extract fails when there is no headers object.
466 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_NoHeaders) {
467 uint32_t extracted;
468 ASSERT_FALSE(AttachmentDownloaderImpl::ExtractCrc32c(nullptr, &extracted));
469 }
470
471 // Verify that extract fails when there is no crc32c value.
472 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_Empty) {
473 std::string raw;
474 raw += "HTTP/1.1 200 OK\n";
475 raw += "Foo: bar\n";
476 raw += "X-Goog-HASH: crc32c=\n";
477 raw += "\n";
478 std::replace(raw.begin(), raw.end(), '\n', '\0');
479 scoped_refptr<net::HttpResponseHeaders> headers(
480 new net::HttpResponseHeaders(raw));
481 uint32_t extracted;
482 ASSERT_FALSE(
483 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
484 }
485
486 // Verify that extract finds the first crc32c and ignores others.
487 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_First) {
488 const std::string expected_encoded = "z8SuHQ==";
489 const uint32_t expected = 3485773341;
490 std::string raw;
491 raw += "HTTP/1.1 200 OK\n";
492 raw += "Foo: bar\n";
493 // Ignored because it's the wrong header.
494 raw += "X-Goog-Hashes: crc32c=AAAAAA==\n";
495 // Header name matches. The md5 item is ignored.
496 raw += "X-Goog-HASH: md5=rL0Y20zC+Fzt72VPzMSk2A==,crc32c=" +
497 expected_encoded + "\n";
498 // Ignored because we already found a crc32c in the one above.
499 raw += "X-Goog-HASH: crc32c=AAAAAA==\n";
500 raw += "\n";
501 std::replace(raw.begin(), raw.end(), '\n', '\0');
502 scoped_refptr<net::HttpResponseHeaders> headers(
503 new net::HttpResponseHeaders(raw));
504 uint32_t extracted;
505 ASSERT_TRUE(
506 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
507 ASSERT_EQ(expected, extracted);
508 }
509
510 // Verify that extract fails when encoded value is too long.
511 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_TooLong) {
512 std::string raw;
513 raw += "HTTP/1.1 200 OK\n";
514 raw += "Foo: bar\n";
515 raw += "X-Goog-HASH: crc32c=AAAAAAAA\n";
516 raw += "\n";
517 std::replace(raw.begin(), raw.end(), '\n', '\0');
518 scoped_refptr<net::HttpResponseHeaders> headers(
519 new net::HttpResponseHeaders(raw));
520 uint32_t extracted;
521 ASSERT_FALSE(
522 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
523 }
524
525 // Verify that extract fails if there is no crc32c.
526 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_None) {
527 std::string raw;
528 raw += "HTTP/1.1 200 OK\n";
529 raw += "Foo: bar\n";
530 raw += "X-Goog-Hash: md5=rL0Y20zC+Fzt72VPzMSk2A==\n";
531 raw += "\n";
532 std::replace(raw.begin(), raw.end(), '\n', '\0');
533 scoped_refptr<net::HttpResponseHeaders> headers(
534 new net::HttpResponseHeaders(raw));
535 uint32_t extracted;
536 ASSERT_FALSE(
537 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
538 }
539
540 } // namespace syncer
OLDNEW
« no previous file with comments | « sync/internal_api/attachments/attachment_downloader_impl.cc ('k') | sync/internal_api/attachments/attachment_service.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698