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

Side by Side Diff: chrome/browser/download/chrome_download_manager_delegate.cc

Issue 8536041: This CL integrates the new SafeBrowsing download service class (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Fix uninitialized variable issue. Created 9 years, 1 month 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 "chrome/browser/download/chrome_download_manager_delegate.h" 5 #include "chrome/browser/download/chrome_download_manager_delegate.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/bind_helpers.h" 8 #include "base/bind_helpers.h"
9 #include "base/callback.h" 9 #include "base/callback.h"
10 #include "base/file_util.h" 10 #include "base/file_util.h"
11 #include "base/path_service.h" 11 #include "base/path_service.h"
12 #include "base/rand_util.h" 12 #include "base/rand_util.h"
13 #include "base/stringprintf.h" 13 #include "base/stringprintf.h"
14 #include "base/time.h"
14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/download/download_crx_util.h" 16 #include "chrome/browser/download/download_crx_util.h"
16 #include "chrome/browser/download/download_extensions.h" 17 #include "chrome/browser/download/download_extensions.h"
17 #include "chrome/browser/download/download_file_picker.h" 18 #include "chrome/browser/download/download_file_picker.h"
18 #include "chrome/browser/download/download_history.h" 19 #include "chrome/browser/download/download_history.h"
19 #include "chrome/browser/download/download_prefs.h" 20 #include "chrome/browser/download/download_prefs.h"
20 #include "chrome/browser/download/download_safe_browsing_client.h"
21 #include "chrome/browser/download/download_util.h" 21 #include "chrome/browser/download/download_util.h"
22 #include "chrome/browser/download/save_package_file_picker.h" 22 #include "chrome/browser/download/save_package_file_picker.h"
23 #include "chrome/browser/extensions/crx_installer.h" 23 #include "chrome/browser/extensions/crx_installer.h"
24 #include "chrome/browser/extensions/extension_service.h" 24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/prefs/pref_member.h" 25 #include "chrome/browser/prefs/pref_member.h"
26 #include "chrome/browser/prefs/pref_service.h" 26 #include "chrome/browser/prefs/pref_service.h"
27 #include "chrome/browser/profiles/profile.h" 27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/safe_browsing/safe_browsing_service.h" 28 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
29 #include "chrome/browser/ui/browser.h" 29 #include "chrome/browser/ui/browser.h"
30 #include "chrome/browser/ui/browser_list.h" 30 #include "chrome/browser/ui/browser_list.h"
31 #include "chrome/common/chrome_paths.h" 31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/extensions/user_script.h" 32 #include "chrome/common/extensions/user_script.h"
33 #include "chrome/common/pref_names.h" 33 #include "chrome/common/pref_names.h"
34 #include "content/browser/download/download_file.h" 34 #include "content/browser/download/download_file.h"
35 #include "content/browser/download/download_item.h" 35 #include "content/browser/download/download_item.h"
36 #include "content/browser/download/download_manager.h" 36 #include "content/browser/download/download_manager.h"
37 #include "content/browser/download/download_status_updater.h" 37 #include "content/browser/download/download_status_updater.h"
38 #include "content/browser/tab_contents/tab_contents.h" 38 #include "content/browser/tab_contents/tab_contents.h"
39 #include "content/public/browser/notification_source.h" 39 #include "content/public/browser/notification_source.h"
40 #include "grit/generated_resources.h" 40 #include "grit/generated_resources.h"
41 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/base/l10n/l10n_util.h"
42 42
43 using content::BrowserThread; 43 using content::BrowserThread;
44 using safe_browsing::DownloadProtectionService;
44 45
45 ChromeDownloadManagerDelegate::ChromeDownloadManagerDelegate(Profile* profile) 46 ChromeDownloadManagerDelegate::ChromeDownloadManagerDelegate(Profile* profile)
46 : profile_(profile), 47 : profile_(profile),
47 download_prefs_(new DownloadPrefs(profile->GetPrefs())) { 48 download_prefs_(new DownloadPrefs(profile->GetPrefs())) {
48 } 49 }
49 50
50 ChromeDownloadManagerDelegate::~ChromeDownloadManagerDelegate() { 51 ChromeDownloadManagerDelegate::~ChromeDownloadManagerDelegate() {
51 } 52 }
52 53
53 bool ChromeDownloadManagerDelegate::IsExtensionDownload( 54 bool ChromeDownloadManagerDelegate::IsExtensionDownload(
(...skipping 25 matching lines...) Expand all
79 // insert the new DownloadItem into 'history_downloads_' or inform our 80 // insert the new DownloadItem into 'history_downloads_' or inform our
80 // observers at this point. OnCreateDownloadEntryComplete() handles that 81 // observers at this point. OnCreateDownloadEntryComplete() handles that
81 // finalization of the the download creation as a callback from the history 82 // finalization of the the download creation as a callback from the history
82 // thread. 83 // thread.
83 DownloadItem* download = 84 DownloadItem* download =
84 download_manager_->GetActiveDownloadItem(download_id); 85 download_manager_->GetActiveDownloadItem(download_id);
85 if (!download) 86 if (!download)
86 return false; 87 return false;
87 88
88 #if defined(ENABLE_SAFE_BROWSING) 89 #if defined(ENABLE_SAFE_BROWSING)
89 // Create a client to verify download URL with safebrowsing. 90 SafeBrowsingService* sb_service =
90 // It deletes itself after the callback. 91 g_browser_process->safe_browsing_service();
91 scoped_refptr<DownloadSBClient> sb_client = new DownloadSBClient( 92 if (sb_service && sb_service->download_protection_service() &&
92 download_id, download->url_chain(), download->referrer_url(), 93 profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
93 profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)); 94 VLOG(2) << __FUNCTION__ << "() Start SB URL check for download = "
94 sb_client->CheckDownloadUrl( 95 << download->DebugString(false);
95 base::Bind(&ChromeDownloadManagerDelegate::CheckDownloadUrlDone, 96 sb_service->download_protection_service()->CheckDownloadUrl(
96 base::Unretained(this))); 97 DownloadProtectionService::DownloadInfo::FromDownloadItem(*download),
97 #else 98 base::Bind(
98 CheckDownloadUrlDone(download_id, false); 99 &ChromeDownloadManagerDelegate::CheckDownloadUrlDone,
100 this,
101 download->id()));
102 return false;
103 }
99 #endif 104 #endif
105 CheckDownloadUrlDone(download_id, DownloadProtectionService::SAFE);
100 return false; 106 return false;
101 } 107 }
102 108
103 void ChromeDownloadManagerDelegate::ChooseDownloadPath( 109 void ChromeDownloadManagerDelegate::ChooseDownloadPath(
104 TabContents* tab_contents, 110 TabContents* tab_contents,
105 const FilePath& suggested_path, 111 const FilePath& suggested_path,
106 void* data) { 112 void* data) {
107 // Deletes itself. 113 // Deletes itself.
108 new DownloadFilePicker( 114 new DownloadFilePicker(
109 download_manager_, tab_contents, suggested_path, data); 115 download_manager_, tab_contents, suggested_path, data);
(...skipping 30 matching lines...) Expand all
140 if (extension.empty()) 146 if (extension.empty())
141 return false; 147 return false;
142 if (Extension::IsExtension(path)) 148 if (Extension::IsExtension(path))
143 return false; 149 return false;
144 DCHECK(extension[0] == FilePath::kExtensionSeparator); 150 DCHECK(extension[0] == FilePath::kExtensionSeparator);
145 extension.erase(0, 1); 151 extension.erase(0, 1);
146 return download_prefs_->IsAutoOpenEnabledForExtension(extension); 152 return download_prefs_->IsAutoOpenEnabledForExtension(extension);
147 } 153 }
148 154
149 bool ChromeDownloadManagerDelegate::ShouldCompleteDownload(DownloadItem* item) { 155 bool ChromeDownloadManagerDelegate::ShouldCompleteDownload(DownloadItem* item) {
156 #if defined(ENABLE_SAFE_BROWSING)
157 // See if there is already a pending SafeBrowsing check for that download.
158 SafeBrowsingStateMap::iterator it = safe_browsing_state_.find(item->id());
159 if (it != safe_browsing_state_.end()) {
160 SafeBrowsingState state = it->second;
161 if (!state.pending) {
162 safe_browsing_state_.erase(it);
163 }
164 return !state.pending;
165 }
166 // Begin the safe browsing download protection check.
167 SafeBrowsingService* sb_service =
168 g_browser_process->safe_browsing_service();
169 if (sb_service && sb_service->download_protection_service() &&
170 profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
171 VLOG(2) << __FUNCTION__ << "() Start SB download check for download = "
172 << item->DebugString(false);
173 sb_service->download_protection_service()->CheckClientDownload(
174 DownloadProtectionService::DownloadInfo::FromDownloadItem(*item),
175 base::Bind(
176 &ChromeDownloadManagerDelegate::CheckClientDownloadDone,
177 this,
178 item->id()));
179 SafeBrowsingState state;
180 state.pending = true;
181 state.verdict = DownloadProtectionService::SAFE;
182 safe_browsing_state_[item->id()] = state;
183 return false;
184 }
185 #endif
150 return true; 186 return true;
151 } 187 }
152 188
153 bool ChromeDownloadManagerDelegate::ShouldOpenDownload(DownloadItem* item) { 189 bool ChromeDownloadManagerDelegate::ShouldOpenDownload(DownloadItem* item) {
154 if (!IsExtensionDownload(item)) { 190 if (!IsExtensionDownload(item)) {
155 #if defined(ENABLE_SAFE_BROWSING)
156 // Begin the safe browsing download protection check.
157 SafeBrowsingService* sb_service =
158 g_browser_process->safe_browsing_service();
159 if (sb_service && sb_service->download_protection_service() &&
160 profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
161 using safe_browsing::DownloadProtectionService;
162 sb_service->download_protection_service()->CheckClientDownload(
163 DownloadProtectionService::DownloadInfo::FromDownloadItem(*item),
164 base::Bind(
165 &ChromeDownloadManagerDelegate::CheckClientDownloadDone,
166 this, item->id()));
167 // For now, we won't delay the download for this.
168 }
169 #else
170 // Assume safe.
171 #endif
172
173 return true; 191 return true;
174 } 192 }
175 193
176 scoped_refptr<CrxInstaller> crx_installer = 194 scoped_refptr<CrxInstaller> crx_installer =
177 download_crx_util::OpenChromeExtension(profile_, *item); 195 download_crx_util::OpenChromeExtension(profile_, *item);
178 196
179 // CRX_INSTALLER_DONE will fire when the install completes. Observe() 197 // CRX_INSTALLER_DONE will fire when the install completes. Observe()
180 // will call DelayedDownloadOpened() on this item. If this DownloadItem is 198 // will call DelayedDownloadOpened() on this item. If this DownloadItem is
181 // not around when CRX_INSTALLER_DONE fires, Complete() will not be called. 199 // not around when CRX_INSTALLER_DONE fires, Complete() will not be called.
182 registrar_.Add(this, 200 registrar_.Add(this,
(...skipping 11 matching lines...) Expand all
194 bool ChromeDownloadManagerDelegate::GenerateFileHash() { 212 bool ChromeDownloadManagerDelegate::GenerateFileHash() {
195 #if defined(ENABLE_SAFE_BROWSING) 213 #if defined(ENABLE_SAFE_BROWSING)
196 return profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled) && 214 return profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled) &&
197 g_browser_process->safe_browsing_service()->DownloadBinHashNeeded(); 215 g_browser_process->safe_browsing_service()->DownloadBinHashNeeded();
198 #else 216 #else
199 return false; 217 return false;
200 #endif 218 #endif
201 } 219 }
202 220
203 void ChromeDownloadManagerDelegate::OnResponseCompleted(DownloadItem* item) { 221 void ChromeDownloadManagerDelegate::OnResponseCompleted(DownloadItem* item) {
204 #if defined(ENABLE_SAFE_BROWSING) 222 // TODO(noelutz): remove this method from the delegate API.
jam 2012/01/27 23:06:34 ping: can you send me a change to remove this? the
205 // When hash is not available, it means either it is not calculated
206 // or there is error while it is calculated. We will skip the download hash
207 // check in that case.
208 if (item->hash().empty())
209 return;
210
211 scoped_refptr<DownloadSBClient> sb_client =
212 new DownloadSBClient(item->id(),
213 item->url_chain(),
214 item->referrer_url(),
215 profile_->GetPrefs()->GetBoolean(
216 prefs::kSafeBrowsingEnabled));
217 sb_client->CheckDownloadHash(
218 item->hash(),
219 base::Bind(&ChromeDownloadManagerDelegate::CheckDownloadHashDone,
220 base::Unretained(this)));
221 #endif
222 } 223 }
223 224
224 void ChromeDownloadManagerDelegate::AddItemToPersistentStore( 225 void ChromeDownloadManagerDelegate::AddItemToPersistentStore(
225 DownloadItem* item) { 226 DownloadItem* item) {
226 download_history_->AddEntry(item, 227 download_history_->AddEntry(item,
227 base::Bind(&ChromeDownloadManagerDelegate::OnItemAddedToPersistentStore, 228 base::Bind(&ChromeDownloadManagerDelegate::OnItemAddedToPersistentStore,
228 base::Unretained(this))); 229 base::Unretained(this)));
229 } 230 }
230 231
231 void ChromeDownloadManagerDelegate::UpdateItemInPersistentStore( 232 void ChromeDownloadManagerDelegate::UpdateItemInPersistentStore(
232 DownloadItem* item) { 233 DownloadItem* item) {
233 download_history_->UpdateEntry(item); 234 download_history_->UpdateEntry(item);
234 } 235 }
235 236
236 void ChromeDownloadManagerDelegate::UpdatePathForItemInPersistentStore( 237 void ChromeDownloadManagerDelegate::UpdatePathForItemInPersistentStore(
237 DownloadItem* item, 238 DownloadItem* item,
238 const FilePath& new_path) { 239 const FilePath& new_path) {
239 download_history_->UpdateDownloadPath(item, new_path); 240 download_history_->UpdateDownloadPath(item, new_path);
240 } 241 }
241 242
242 void ChromeDownloadManagerDelegate::CheckClientDownloadDone(
243 int32 download_id,
244 safe_browsing::DownloadProtectionService::DownloadCheckResult result) {
245 // TODO(bryner): notify the user based on this result
246 }
247
248 void ChromeDownloadManagerDelegate::RemoveItemFromPersistentStore( 243 void ChromeDownloadManagerDelegate::RemoveItemFromPersistentStore(
249 DownloadItem* item) { 244 DownloadItem* item) {
250 download_history_->RemoveEntry(item); 245 download_history_->RemoveEntry(item);
251 } 246 }
252 247
253 void ChromeDownloadManagerDelegate::RemoveItemsFromPersistentStoreBetween( 248 void ChromeDownloadManagerDelegate::RemoveItemsFromPersistentStoreBetween(
254 const base::Time remove_begin, 249 const base::Time remove_begin,
255 const base::Time remove_end) { 250 const base::Time remove_end) {
256 download_history_->RemoveEntriesBetween(remove_begin, remove_end); 251 download_history_->RemoveEntriesBetween(remove_begin, remove_end);
257 } 252 }
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 int download_count = 0; 294 int download_count = 0;
300 bool progress_known = 295 bool progress_known =
301 g_browser_process->download_status_updater()->GetProgress( 296 g_browser_process->download_status_updater()->GetProgress(
302 &progress, &download_count); 297 &progress, &download_count);
303 download_util::UpdateAppIconDownloadProgress( 298 download_util::UpdateAppIconDownloadProgress(
304 download_count, progress_known, progress); 299 download_count, progress_known, progress);
305 } 300 }
306 301
307 void ChromeDownloadManagerDelegate::CheckDownloadUrlDone( 302 void ChromeDownloadManagerDelegate::CheckDownloadUrlDone(
308 int32 download_id, 303 int32 download_id,
309 bool is_dangerous_url) { 304 DownloadProtectionService::DownloadCheckResult result) {
310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311
312 DownloadItem* download = 306 DownloadItem* download =
313 download_manager_->GetActiveDownloadItem(download_id); 307 download_manager_->GetActiveDownloadItem(download_id);
314 if (!download) 308 if (!download)
315 return; 309 return;
316 310
317 if (is_dangerous_url) 311 VLOG(2) << __FUNCTION__ << "() download = " << download->DebugString(false)
312 << " verdict = " << result;
313 if (result == DownloadProtectionService::DANGEROUS)
318 download->MarkUrlDangerous(); 314 download->MarkUrlDangerous();
319 315
320 download_history_->CheckVisitedReferrerBefore( 316 download_history_->CheckVisitedReferrerBefore(
321 download_id, download->referrer_url(), 317 download_id, download->referrer_url(),
322 base::Bind(&ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone, 318 base::Bind(&ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone,
323 base::Unretained(this))); 319 base::Unretained(this)));
324 } 320 }
325 321
322 void ChromeDownloadManagerDelegate::CheckClientDownloadDone(
323 int32 download_id,
324 DownloadProtectionService::DownloadCheckResult result) {
325 DownloadItem* item = download_manager_->GetActiveDownloadItem(download_id);
326 if (!item) {
327 safe_browsing_state_.erase(download_id); // Just in case.
328 return;
329 }
330
331 VLOG(2) << __FUNCTION__ << "() download = " << item->DebugString(false)
332 << " verdict = " << result;
333 // TODO(noelutz):
334 // 1) display a warning if the result is DANGEROUS.
335 // 2) make sure we haven't already displayed a warning for the URL.
336 // 3) disable the existing dangerous file warning for executables.
337
338 SafeBrowsingStateMap::iterator it = safe_browsing_state_.find(item->id());
339 DCHECK(it != safe_browsing_state_.end() && it->second.pending);
340 if (it != safe_browsing_state_.end()) {
341 it->second.pending = false;
342 it->second.verdict = result;
343 }
344 download_manager_->MaybeCompleteDownload(item);
345 }
346
326 // content::NotificationObserver implementation. 347 // content::NotificationObserver implementation.
327 void ChromeDownloadManagerDelegate::Observe( 348 void ChromeDownloadManagerDelegate::Observe(
328 int type, 349 int type,
329 const content::NotificationSource& source, 350 const content::NotificationSource& source,
330 const content::NotificationDetails& details) { 351 const content::NotificationDetails& details) {
331 DCHECK(type == chrome::NOTIFICATION_CRX_INSTALLER_DONE); 352 DCHECK(type == chrome::NOTIFICATION_CRX_INSTALLER_DONE);
332 353
333 registrar_.Remove(this, 354 registrar_.Remove(this,
334 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 355 chrome::NOTIFICATION_CRX_INSTALLER_DONE,
335 source); 356 source);
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
513 bool ChromeDownloadManagerDelegate::IsDangerousFile( 534 bool ChromeDownloadManagerDelegate::IsDangerousFile(
514 const DownloadItem& download, 535 const DownloadItem& download,
515 const DownloadStateInfo& state, 536 const DownloadStateInfo& state,
516 bool visited_referrer_before) { 537 bool visited_referrer_before) {
517 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 538 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
518 539
519 // Anything loaded directly from the address bar is OK. 540 // Anything loaded directly from the address bar is OK.
520 if (state.transition_type & content::PAGE_TRANSITION_FROM_ADDRESS_BAR) 541 if (state.transition_type & content::PAGE_TRANSITION_FROM_ADDRESS_BAR)
521 return false; 542 return false;
522 543
544 // Return false if this type of files is handled by the enhanced SafeBrowsing
545 // download protection.
546 // TODO(noelutz): implement that check.
547
523 // Extensions that are not from the gallery are considered dangerous. 548 // Extensions that are not from the gallery are considered dangerous.
524 if (IsExtensionDownload(&download)) { 549 if (IsExtensionDownload(&download)) {
525 ExtensionService* service = profile_->GetExtensionService(); 550 ExtensionService* service = profile_->GetExtensionService();
526 if (!service || !service->IsDownloadFromGallery(download.GetURL(), 551 if (!service || !service->IsDownloadFromGallery(download.GetURL(),
527 download.referrer_url())) 552 download.referrer_url()))
528 return true; 553 return true;
529 } 554 }
530 555
531 // Anything the user has marked auto-open is OK if it's user-initiated. 556 // Anything the user has marked auto-open is OK if it's user-initiated.
532 if (ShouldOpenFileBasedOnExtension(state.suggested_path) && 557 if (ShouldOpenFileBasedOnExtension(state.suggested_path) &&
(...skipping 14 matching lines...) Expand all
547 int32 download_id, int64 db_handle) { 572 int32 download_id, int64 db_handle) {
548 // It's not immediately obvious, but HistoryBackend::CreateDownload() can 573 // It's not immediately obvious, but HistoryBackend::CreateDownload() can
549 // call this function with an invalid |db_handle|. For instance, this can 574 // call this function with an invalid |db_handle|. For instance, this can
550 // happen when the history database is offline. We cannot have multiple 575 // happen when the history database is offline. We cannot have multiple
551 // DownloadItems with the same invalid db_handle, so we need to assign a 576 // DownloadItems with the same invalid db_handle, so we need to assign a
552 // unique |db_handle| here. 577 // unique |db_handle| here.
553 if (db_handle == DownloadItem::kUninitializedHandle) 578 if (db_handle == DownloadItem::kUninitializedHandle)
554 db_handle = download_history_->GetNextFakeDbHandle(); 579 db_handle = download_history_->GetNextFakeDbHandle();
555 download_manager_->OnItemAddedToPersistentStore(download_id, db_handle); 580 download_manager_->OnItemAddedToPersistentStore(download_id, db_handle);
556 } 581 }
557
558 // TODO(noelutz): This function currently works as a callback place holder.
559 // Once we decide the hash check is reliable, we could move the
560 // MaybeCompleteDownload in OnAllDataSaved to this function.
561 void ChromeDownloadManagerDelegate::CheckDownloadHashDone(
562 int32 download_id,
563 bool is_dangerous_hash) {
564 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
565 DVLOG(1) << "CheckDownloadHashDone, download_id: " << download_id
566 << " is dangerous_hash: " << is_dangerous_hash;
567 }
OLDNEW
« no previous file with comments | « chrome/browser/download/chrome_download_manager_delegate.h ('k') | chrome/browser/download/download_safe_browsing_client.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698