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

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

Issue 9617010: Move chrome.downloads out of experimental to dev (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: comments Created 8 years, 6 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "chrome/browser/download/download_extension_api.h"
6
7 #include <algorithm>
8 #include <cctype>
9 #include <iterator>
10 #include <set>
11 #include <string>
12
13 #include "base/basictypes.h"
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/callback.h"
17 #include "base/json/json_writer.h"
18 #include "base/lazy_instance.h"
19 #include "base/logging.h"
20 #include "base/metrics/histogram.h"
21 #include "base/stl_util.h"
22 #include "base/string16.h"
23 #include "base/string_split.h"
24 #include "base/string_util.h"
25 #include "base/stringprintf.h"
26 #include "base/values.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/browser/download/download_file_icon_extractor.h"
29 #include "chrome/browser/download/download_query.h"
30 #include "chrome/browser/download/download_service.h"
31 #include "chrome/browser/download/download_service_factory.h"
32 #include "chrome/browser/download/download_util.h"
33 #include "chrome/browser/extensions/extension_event_names.h"
34 #include "chrome/browser/extensions/extension_event_router.h"
35 #include "chrome/browser/icon_loader.h"
36 #include "chrome/browser/icon_manager.h"
37 #include "chrome/browser/renderer_host/chrome_render_message_filter.h"
38 #include "chrome/browser/ui/browser_list.h"
39 #include "chrome/browser/ui/webui/web_ui_util.h"
40 #include "content/public/browser/download_interrupt_reasons.h"
41 #include "content/public/browser/download_item.h"
42 #include "content/public/browser/download_save_info.h"
43 #include "content/public/browser/render_process_host.h"
44 #include "content/public/browser/render_view_host.h"
45 #include "content/public/browser/resource_dispatcher_host.h"
46 #include "net/base/load_flags.h"
47 #include "net/http/http_util.h"
48 #include "net/url_request/url_request.h"
49
50 using content::BrowserThread;
51 using content::DownloadId;
52 using content::DownloadItem;
53 using content::DownloadManager;
54
55 namespace download_extension_errors {
56
57 // Error messages
58 const char kGenericError[] = "I'm afraid I can't do that.";
59 const char kIconNotFoundError[] = "Icon not found.";
60 const char kInvalidDangerTypeError[] = "Invalid danger type";
61 const char kInvalidFilterError[] = "Invalid query filter";
62 const char kInvalidOperationError[] = "Invalid operation.";
63 const char kInvalidOrderByError[] = "Invalid orderBy field";
64 const char kInvalidQueryLimit[] = "Invalid query limit";
65 const char kInvalidStateError[] = "Invalid state";
66 const char kInvalidURLError[] = "Invalid URL.";
67 const char kNotImplementedError[] = "NotImplemented.";
68
69 } // namespace download_extension_errors
70
71 namespace {
72
73 // Default icon size for getFileIcon() in pixels.
74 const int kDefaultIconSize = 32;
75
76 // Parameter keys
77 const char kBodyKey[] = "body";
78 const char kBytesReceivedKey[] = "bytesReceived";
79 const char kDangerAcceptedKey[] = "dangerAccepted";
80 const char kDangerContent[] = "content";
81 const char kDangerFile[] = "file";
82 const char kDangerKey[] = "danger";
83 const char kDangerSafe[] = "safe";
84 const char kDangerUncommon[] = "uncommon";
85 const char kDangerUrl[] = "url";
86 const char kEndTimeKey[] = "endTime";
87 const char kErrorKey[] = "error";
88 const char kFileSizeKey[] = "fileSize";
89 const char kFilenameKey[] = "filename";
90 const char kFilenameRegexKey[] = "filenameRegex";
91 const char kHeaderNameKey[] = "name";
92 const char kHeaderValueKey[] = "value";
93 const char kHeaderBinaryValueKey[] = "binaryValue";
94 const char kHeadersKey[] = "headers";
95 const char kIdKey[] = "id";
96 const char kIncognito[] = "incognito";
97 const char kLimitKey[] = "limit";
98 const char kMethodKey[] = "method";
99 const char kMimeKey[] = "mime";
100 const char kOrderByKey[] = "orderBy";
101 const char kPausedKey[] = "paused";
102 const char kQueryKey[] = "query";
103 const char kSaveAsKey[] = "saveAs";
104 const char kSizeKey[] = "size";
105 const char kStartTimeKey[] = "startTime";
106 const char kStartedAfterKey[] = "startedAfter";
107 const char kStartedBeforeKey[] = "startedBefore";
108 const char kStateComplete[] = "complete";
109 const char kStateInProgress[] = "in_progress";
110 const char kStateInterrupted[] = "interrupted";
111 const char kStateKey[] = "state";
112 const char kTotalBytesKey[] = "totalBytes";
113 const char kTotalBytesGreaterKey[] = "totalBytesGreater";
114 const char kTotalBytesLessKey[] = "totalBytesLess";
115 const char kUrlKey[] = "url";
116 const char kUrlRegexKey[] = "urlRegex";
117
118 // Note: Any change to the danger type strings, should be accompanied by a
119 // corresponding change to {experimental.}downloads.json.
120 const char* kDangerStrings[] = {
121 kDangerSafe,
122 kDangerFile,
123 kDangerUrl,
124 kDangerContent,
125 kDangerSafe,
126 kDangerUncommon,
127 };
128 COMPILE_ASSERT(arraysize(kDangerStrings) == content::DOWNLOAD_DANGER_TYPE_MAX,
129 download_danger_type_enum_changed);
130
131 // Note: Any change to the state strings, should be accompanied by a
132 // corresponding change to {experimental.}downloads.json.
133 const char* kStateStrings[] = {
134 kStateInProgress,
135 kStateComplete,
136 kStateInterrupted,
137 NULL,
138 kStateInterrupted,
139 };
140 COMPILE_ASSERT(arraysize(kStateStrings) == DownloadItem::MAX_DOWNLOAD_STATE,
141 download_item_state_enum_changed);
142
143 const char* DangerString(content::DownloadDangerType danger) {
144 DCHECK(danger >= 0);
145 DCHECK(danger < static_cast<content::DownloadDangerType>(
146 arraysize(kDangerStrings)));
147 return kDangerStrings[danger];
148 }
149
150 content::DownloadDangerType DangerEnumFromString(const std::string& danger) {
151 for (size_t i = 0; i < arraysize(kDangerStrings); ++i) {
152 if (danger == kDangerStrings[i])
153 return static_cast<content::DownloadDangerType>(i);
154 }
155 return content::DOWNLOAD_DANGER_TYPE_MAX;
156 }
157
158 const char* StateString(DownloadItem::DownloadState state) {
159 DCHECK(state >= 0);
160 DCHECK(state < static_cast<DownloadItem::DownloadState>(
161 arraysize(kStateStrings)));
162 DCHECK(state != DownloadItem::REMOVING);
163 return kStateStrings[state];
164 }
165
166 DownloadItem::DownloadState StateEnumFromString(const std::string& state) {
167 for (size_t i = 0; i < arraysize(kStateStrings); ++i) {
168 if ((kStateStrings[i] != NULL) && (state == kStateStrings[i]))
169 return static_cast<DownloadItem::DownloadState>(i);
170 }
171 return DownloadItem::MAX_DOWNLOAD_STATE;
172 }
173
174 bool ValidateFilename(const string16& filename) {
175 // TODO(benjhayden): More robust validation of filename.
176 if ((filename.find('/') != string16::npos) ||
177 (filename.find('\\') != string16::npos))
178 return false;
179 if (filename.size() >= 2u && filename[0] == L'.' && filename[1] == L'.')
180 return false;
181 return true;
182 }
183
184 scoped_ptr<base::DictionaryValue> DownloadItemToJSON(DownloadItem* item) {
185 base::DictionaryValue* json = new base::DictionaryValue();
186 json->SetInteger(kIdKey, item->GetId());
187 json->SetString(kUrlKey, item->GetOriginalUrl().spec());
188 json->SetString(kFilenameKey, item->GetFullPath().LossyDisplayName());
189 json->SetString(kDangerKey, DangerString(item->GetDangerType()));
190 if (item->GetDangerType() != content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
191 json->SetBoolean(kDangerAcceptedKey,
192 item->GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED);
193 json->SetString(kStateKey, StateString(item->GetState()));
194 json->SetBoolean(kPausedKey, item->IsPaused());
195 json->SetString(kMimeKey, item->GetMimeType());
196 json->SetInteger(kStartTimeKey,
197 (item->GetStartTime() - base::Time::UnixEpoch()).InMilliseconds());
198 json->SetInteger(kBytesReceivedKey, item->GetReceivedBytes());
199 json->SetInteger(kTotalBytesKey, item->GetTotalBytes());
200 json->SetBoolean(kIncognito, item->IsOtr());
201 if (item->GetState() == DownloadItem::INTERRUPTED) {
202 json->SetInteger(kErrorKey, static_cast<int>(item->GetLastReason()));
203 } else if (item->GetState() == DownloadItem::CANCELLED) {
204 json->SetInteger(kErrorKey, static_cast<int>(
205 content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED));
206 }
207 // TODO(benjhayden): Implement endTime and fileSize.
208 // json->SetInteger(kEndTimeKey, -1);
209 json->SetInteger(kFileSizeKey, item->GetTotalBytes());
210 return scoped_ptr<base::DictionaryValue>(json);
211 }
212
213 class DownloadFileIconExtractorImpl : public DownloadFileIconExtractor {
214 public:
215 DownloadFileIconExtractorImpl() {}
216
217 ~DownloadFileIconExtractorImpl() {}
218
219 virtual bool ExtractIconURLForPath(const FilePath& path,
220 IconLoader::IconSize icon_size,
221 IconURLCallback callback) OVERRIDE;
222 private:
223 void OnIconLoadComplete(IconManager::Handle handle, gfx::Image* icon);
224
225 CancelableRequestConsumer cancelable_consumer_;
226 IconURLCallback callback_;
227 };
228
229 bool DownloadFileIconExtractorImpl::ExtractIconURLForPath(
230 const FilePath& path,
231 IconLoader::IconSize icon_size,
232 IconURLCallback callback) {
233 callback_ = callback;
234 IconManager* im = g_browser_process->icon_manager();
235 // The contents of the file at |path| may have changed since a previous
236 // request, in which case the associated icon may also have changed.
237 // Therefore, for the moment we always call LoadIcon instead of attempting
238 // a LookupIcon.
239 im->LoadIcon(
240 path, icon_size, &cancelable_consumer_,
241 base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete,
242 base::Unretained(this)));
243 return true;
244 }
245
246 void DownloadFileIconExtractorImpl::OnIconLoadComplete(
247 IconManager::Handle handle,
248 gfx::Image* icon) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
250 std::string url;
251 if (icon)
252 url = web_ui_util::GetImageDataUrl(*icon->ToSkBitmap());
253 callback_.Run(url);
254 }
255
256 IconLoader::IconSize IconLoaderSizeFromPixelSize(int pixel_size) {
257 switch (pixel_size) {
258 case 16: return IconLoader::SMALL;
259 case 32: return IconLoader::NORMAL;
260 default:
261 NOTREACHED();
262 return IconLoader::NORMAL;
263 }
264 }
265
266 typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap;
267
268 void InitFilterTypeMap(FilterTypeMap& filter_types) {
269 filter_types[kBytesReceivedKey] =
270 DownloadQuery::FILTER_BYTES_RECEIVED;
271 filter_types[kDangerAcceptedKey] =
272 DownloadQuery::FILTER_DANGER_ACCEPTED;
273 filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME;
274 filter_types[kFilenameRegexKey] =
275 DownloadQuery::FILTER_FILENAME_REGEX;
276 filter_types[kMimeKey] = DownloadQuery::FILTER_MIME;
277 filter_types[kPausedKey] = DownloadQuery::FILTER_PAUSED;
278 filter_types[kQueryKey] = DownloadQuery::FILTER_QUERY;
279 filter_types[kStartedAfterKey] = DownloadQuery::FILTER_STARTED_AFTER;
280 filter_types[kStartedBeforeKey] =
281 DownloadQuery::FILTER_STARTED_BEFORE;
282 filter_types[kStartTimeKey] = DownloadQuery::FILTER_START_TIME;
283 filter_types[kTotalBytesKey] = DownloadQuery::FILTER_TOTAL_BYTES;
284 filter_types[kTotalBytesGreaterKey] =
285 DownloadQuery::FILTER_TOTAL_BYTES_GREATER;
286 filter_types[kTotalBytesLessKey] =
287 DownloadQuery::FILTER_TOTAL_BYTES_LESS;
288 filter_types[kUrlKey] = DownloadQuery::FILTER_URL;
289 filter_types[kUrlRegexKey] = DownloadQuery::FILTER_URL_REGEX;
290 }
291
292 typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap;
293
294 void InitSortTypeMap(SortTypeMap& sorter_types) {
295 sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED;
296 sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER;
297 sorter_types[kDangerAcceptedKey] =
298 DownloadQuery::SORT_DANGER_ACCEPTED;
299 sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME;
300 sorter_types[kMimeKey] = DownloadQuery::SORT_MIME;
301 sorter_types[kPausedKey] = DownloadQuery::SORT_PAUSED;
302 sorter_types[kStartTimeKey] = DownloadQuery::SORT_START_TIME;
303 sorter_types[kStateKey] = DownloadQuery::SORT_STATE;
304 sorter_types[kTotalBytesKey] = DownloadQuery::SORT_TOTAL_BYTES;
305 sorter_types[kUrlKey] = DownloadQuery::SORT_URL;
306 }
307
308 bool IsNotTemporaryDownloadFilter(const DownloadItem& item) {
309 return !item.IsTemporary();
310 }
311
312 void GetManagers(
313 Profile* profile,
314 bool include_incognito,
315 DownloadManager** manager, DownloadManager** incognito_manager) {
316 *manager = DownloadServiceFactory::GetForProfile(profile)->
317 GetDownloadManager();
318 *incognito_manager = NULL;
319 if (include_incognito && profile->HasOffTheRecordProfile())
320 *incognito_manager = DownloadServiceFactory::GetForProfile(profile->
321 GetOffTheRecordProfile())->GetDownloadManager();
322 }
323
324 DownloadItem* GetActiveItemInternal(
325 Profile* profile,
326 bool include_incognito,
327 int id) {
328 DownloadManager* manager = NULL;
329 DownloadManager* incognito_manager = NULL;
330 GetManagers(profile, include_incognito, &manager, &incognito_manager);
331 DownloadItem* download_item = manager->GetActiveDownloadItem(id);
332 if (!download_item && incognito_manager)
333 download_item = incognito_manager->GetActiveDownloadItem(id);
334 return download_item;
335 }
336
337 } // namespace
338
339 bool DownloadsFunctionInterface::RunImplImpl(
340 DownloadsFunctionInterface* pimpl) {
341 CHECK(pimpl);
342 if (!pimpl->ParseArgs()) return false;
343 UMA_HISTOGRAM_ENUMERATION(
344 "Download.ApiFunctions", pimpl->function(), DOWNLOADS_FUNCTION_LAST);
345 return pimpl->RunInternal();
346 }
347
348 SyncDownloadsFunction::SyncDownloadsFunction(
349 DownloadsFunctionInterface::DownloadsFunctionName function)
350 : function_(function) {
351 }
352
353 SyncDownloadsFunction::~SyncDownloadsFunction() {}
354
355 bool SyncDownloadsFunction::RunImpl() {
356 return DownloadsFunctionInterface::RunImplImpl(this);
357 }
358
359 DownloadsFunctionInterface::DownloadsFunctionName
360 SyncDownloadsFunction::function() const {
361 return function_;
362 }
363
364 DownloadItem* SyncDownloadsFunction::GetActiveItem(int download_id) {
365 return GetActiveItemInternal(profile(), include_incognito(), download_id);
366 }
367
368 AsyncDownloadsFunction::AsyncDownloadsFunction(
369 DownloadsFunctionInterface::DownloadsFunctionName function)
370 : function_(function) {
371 }
372
373 AsyncDownloadsFunction::~AsyncDownloadsFunction() {}
374
375 bool AsyncDownloadsFunction::RunImpl() {
376 return DownloadsFunctionInterface::RunImplImpl(this);
377 }
378
379 DownloadsFunctionInterface::DownloadsFunctionName
380 AsyncDownloadsFunction::function() const {
381 return function_;
382 }
383
384 DownloadItem* AsyncDownloadsFunction::GetActiveItem(int download_id) {
385 return GetActiveItemInternal(profile(), include_incognito(), download_id);
386 }
387
388 DownloadsDownloadFunction::DownloadsDownloadFunction()
389 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_DOWNLOAD) {
390 }
391
392 DownloadsDownloadFunction::~DownloadsDownloadFunction() {}
393
394 DownloadsDownloadFunction::IOData::IOData()
395 : save_as(false),
396 extra_headers(NULL),
397 method("GET"),
398 rdh(NULL),
399 resource_context(NULL),
400 render_process_host_id(0),
401 render_view_host_routing_id(0) {
402 }
403
404 DownloadsDownloadFunction::IOData::~IOData() {}
405
406 bool DownloadsDownloadFunction::ParseArgs() {
407 base::DictionaryValue* options = NULL;
408 std::string url;
409 iodata_.reset(new IOData());
410 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options));
411 EXTENSION_FUNCTION_VALIDATE(options->GetString(kUrlKey, &url));
412 iodata_->url = GURL(url);
413 if (!iodata_->url.is_valid()) {
414 error_ = download_extension_errors::kInvalidURLError;
415 return false;
416 }
417
418 if (!iodata_->url.SchemeIs("data") &&
419 iodata_->url.GetOrigin() != GetExtension()->url().GetOrigin() &&
420 !GetExtension()->HasHostPermission(iodata_->url)) {
421 error_ = download_extension_errors::kInvalidURLError;
422 return false;
423 }
424
425 if (options->HasKey(kFilenameKey)) {
426 EXTENSION_FUNCTION_VALIDATE(options->GetString(
427 kFilenameKey, &iodata_->filename));
428 if (!ValidateFilename(iodata_->filename)) {
429 error_ = download_extension_errors::kGenericError;
430 return false;
431 }
432 }
433
434 if (options->HasKey(kSaveAsKey)) {
435 EXTENSION_FUNCTION_VALIDATE(options->GetBoolean(
436 kSaveAsKey, &iodata_->save_as));
437 }
438
439 if (options->HasKey(kMethodKey)) {
440 EXTENSION_FUNCTION_VALIDATE(options->GetString(
441 kMethodKey, &iodata_->method));
442 }
443
444 // It's ok to use a pointer to extra_headers without DeepCopy()ing because
445 // |args_| (which owns *extra_headers) is guaranteed to live as long as
446 // |this|.
447 if (options->HasKey(kHeadersKey)) {
448 EXTENSION_FUNCTION_VALIDATE(options->GetList(
449 kHeadersKey, &iodata_->extra_headers));
450 }
451
452 if (options->HasKey(kBodyKey)) {
453 EXTENSION_FUNCTION_VALIDATE(options->GetString(
454 kBodyKey, &iodata_->post_body));
455 }
456
457 if (iodata_->extra_headers != NULL) {
458 for (size_t index = 0; index < iodata_->extra_headers->GetSize(); ++index) {
459 base::DictionaryValue* header = NULL;
460 std::string name;
461 EXTENSION_FUNCTION_VALIDATE(iodata_->extra_headers->GetDictionary(
462 index, &header));
463 EXTENSION_FUNCTION_VALIDATE(header->GetString(
464 kHeaderNameKey, &name));
465 if (header->HasKey(kHeaderBinaryValueKey)) {
466 base::ListValue* binary_value = NULL;
467 EXTENSION_FUNCTION_VALIDATE(header->GetList(
468 kHeaderBinaryValueKey, &binary_value));
469 for (size_t char_i = 0; char_i < binary_value->GetSize(); ++char_i) {
470 int char_value = 0;
471 EXTENSION_FUNCTION_VALIDATE(binary_value->GetInteger(
472 char_i, &char_value));
473 }
474 } else if (header->HasKey(kHeaderValueKey)) {
475 std::string value;
476 EXTENSION_FUNCTION_VALIDATE(header->GetString(
477 kHeaderValueKey, &value));
478 }
479 if (!net::HttpUtil::IsSafeHeader(name)) {
480 error_ = download_extension_errors::kGenericError;
481 return false;
482 }
483 }
484 }
485 iodata_->rdh = content::ResourceDispatcherHost::Get();
486 iodata_->resource_context = profile()->GetResourceContext();
487 iodata_->render_process_host_id = render_view_host()->GetProcess()->GetID();
488 iodata_->render_view_host_routing_id = render_view_host()->GetRoutingID();
489 return true;
490 }
491
492 bool DownloadsDownloadFunction::RunInternal() {
493 VLOG(1) << __FUNCTION__ << " " << iodata_->url.spec();
494 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
495 &DownloadsDownloadFunction::BeginDownloadOnIOThread, this))) {
496 error_ = download_extension_errors::kGenericError;
497 return false;
498 }
499 return true;
500 }
501
502 void DownloadsDownloadFunction::BeginDownloadOnIOThread() {
503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
504 DVLOG(1) << __FUNCTION__ << " " << iodata_->url.spec();
505 content::DownloadSaveInfo save_info;
506 // TODO(benjhayden) Ensure that this filename is interpreted as a path
507 // relative to the default downloads directory without allowing '..'.
508 save_info.suggested_name = iodata_->filename;
509 save_info.prompt_for_save_location = iodata_->save_as;
510
511 scoped_ptr<net::URLRequest> request(new net::URLRequest(iodata_->url, NULL));
512 request->set_method(iodata_->method);
513 if (iodata_->extra_headers != NULL) {
514 for (size_t index = 0; index < iodata_->extra_headers->GetSize(); ++index) {
515 base::DictionaryValue* header = NULL;
516 std::string name, value;
517 CHECK(iodata_->extra_headers->GetDictionary(index, &header));
518 CHECK(header->GetString(kHeaderNameKey, &name));
519 if (header->HasKey(kHeaderBinaryValueKey)) {
520 base::ListValue* binary_value = NULL;
521 CHECK(header->GetList(kHeaderBinaryValueKey, &binary_value));
522 for (size_t char_i = 0; char_i < binary_value->GetSize(); ++char_i) {
523 int char_value = 0;
524 CHECK(binary_value->GetInteger(char_i, &char_value));
525 if ((0 <= char_value) &&
526 (char_value <= 0xff)) {
527 value.push_back(char_value);
528 }
529 }
530 } else if (header->HasKey(kHeaderValueKey)) {
531 CHECK(header->GetString(kHeaderValueKey, &value));
532 }
533 request->SetExtraRequestHeaderByName(name, value, false/*overwrite*/);
534 }
535 }
536 if (!iodata_->post_body.empty()) {
537 request->AppendBytesToUpload(iodata_->post_body.data(),
538 iodata_->post_body.size());
539 }
540
541 // Prevent login prompts for 401/407 responses.
542 request->set_load_flags(request->load_flags() |
543 net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
544
545 net::Error error = iodata_->rdh->BeginDownload(
546 request.Pass(),
547 false, // is_content_initiated
548 iodata_->resource_context,
549 iodata_->render_process_host_id,
550 iodata_->render_view_host_routing_id,
551 false, // prefer_cache
552 save_info,
553 base::Bind(&DownloadsDownloadFunction::OnStarted, this));
554 iodata_.reset();
555
556 if (error != net::OK) {
557 BrowserThread::PostTask(
558 BrowserThread::UI, FROM_HERE,
559 base::Bind(&DownloadsDownloadFunction::OnStarted, this,
560 DownloadId::Invalid(), error));
561 }
562 }
563
564 void DownloadsDownloadFunction::OnStarted(DownloadId dl_id, net::Error error) {
565 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
566 VLOG(1) << __FUNCTION__ << " " << dl_id << " " << error;
567 if (dl_id.local() >= 0) {
568 result_.reset(base::Value::CreateIntegerValue(dl_id.local()));
569 } else {
570 error_ = net::ErrorToString(error);
571 }
572 SendResponse(error_.empty());
573 }
574
575 DownloadsSearchFunction::DownloadsSearchFunction()
576 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_SEARCH),
577 query_(new DownloadQuery()),
578 get_id_(0),
579 has_get_id_(false) {
580 }
581
582 DownloadsSearchFunction::~DownloadsSearchFunction() {}
583
584 bool DownloadsSearchFunction::ParseArgs() {
585 static base::LazyInstance<FilterTypeMap> filter_types =
586 LAZY_INSTANCE_INITIALIZER;
587 if (filter_types.Get().size() == 0)
588 InitFilterTypeMap(filter_types.Get());
589
590 base::DictionaryValue* query_json = NULL;
591 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query_json));
592 for (base::DictionaryValue::Iterator query_json_field(*query_json);
593 query_json_field.HasNext(); query_json_field.Advance()) {
594 FilterTypeMap::const_iterator filter_type =
595 filter_types.Get().find(query_json_field.key());
596
597 if (filter_type != filter_types.Get().end()) {
598 if (!query_->AddFilter(filter_type->second, query_json_field.value())) {
599 error_ = download_extension_errors::kInvalidFilterError;
600 return false;
601 }
602 } else if (query_json_field.key() == kIdKey) {
603 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsInteger(
604 &get_id_));
605 has_get_id_ = true;
606 } else if (query_json_field.key() == kOrderByKey) {
607 if (!ParseOrderBy(query_json_field.value()))
608 return false;
609 } else if (query_json_field.key() == kDangerKey) {
610 std::string danger_str;
611 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsString(
612 &danger_str));
613 content::DownloadDangerType danger_type =
614 DangerEnumFromString(danger_str);
615 if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
616 error_ = download_extension_errors::kInvalidDangerTypeError;
617 return false;
618 }
619 query_->AddFilter(danger_type);
620 } else if (query_json_field.key() == kStateKey) {
621 std::string state_str;
622 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsString(
623 &state_str));
624 DownloadItem::DownloadState state = StateEnumFromString(state_str);
625 if (state == DownloadItem::MAX_DOWNLOAD_STATE) {
626 error_ = download_extension_errors::kInvalidStateError;
627 return false;
628 }
629 query_->AddFilter(state);
630 } else if (query_json_field.key() == kLimitKey) {
631 int limit = 0;
632 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsInteger(
633 &limit));
634 if (limit < 0) {
635 error_ = download_extension_errors::kInvalidQueryLimit;
636 return false;
637 }
638 query_->Limit(limit);
639 } else {
640 EXTENSION_FUNCTION_VALIDATE(false);
641 }
642 }
643 return true;
644 }
645
646 bool DownloadsSearchFunction::ParseOrderBy(const base::Value& order_by_value) {
647 static base::LazyInstance<SortTypeMap> sorter_types =
648 LAZY_INSTANCE_INITIALIZER;
649 if (sorter_types.Get().size() == 0)
650 InitSortTypeMap(sorter_types.Get());
651
652 std::string order_by_str;
653 EXTENSION_FUNCTION_VALIDATE(order_by_value.GetAsString(&order_by_str));
654 std::vector<std::string> order_by_strs;
655 base::SplitString(order_by_str, ' ', &order_by_strs);
656 for (std::vector<std::string>::const_iterator iter = order_by_strs.begin();
657 iter != order_by_strs.end(); ++iter) {
658 std::string term_str = *iter;
659 if (term_str.empty())
660 continue;
661 DownloadQuery::SortDirection direction = DownloadQuery::ASCENDING;
662 if (term_str[0] == '-') {
663 direction = DownloadQuery::DESCENDING;
664 term_str = term_str.substr(1);
665 }
666 SortTypeMap::const_iterator sorter_type =
667 sorter_types.Get().find(term_str);
668 if (sorter_type == sorter_types.Get().end()) {
669 error_ = download_extension_errors::kInvalidOrderByError;
670 return false;
671 }
672 query_->AddSorter(sorter_type->second, direction);
673 }
674 return true;
675 }
676
677 bool DownloadsSearchFunction::RunInternal() {
678 DownloadManager* manager = NULL;
679 DownloadManager* incognito_manager = NULL;
680 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
681 DownloadQuery::DownloadVector all_items, cpp_results;
682 if (has_get_id_) {
683 DownloadItem* item = manager->GetDownloadItem(get_id_);
684 if (!item && incognito_manager)
685 item = incognito_manager->GetDownloadItem(get_id_);
686 if (item)
687 all_items.push_back(item);
688 } else {
689 manager->GetAllDownloads(FilePath(FILE_PATH_LITERAL("")), &all_items);
690 if (incognito_manager)
691 incognito_manager->GetAllDownloads(
692 FilePath(FILE_PATH_LITERAL("")), &all_items);
693 }
694 query_->Search(all_items.begin(), all_items.end(), &cpp_results);
695 base::ListValue* json_results = new base::ListValue();
696 for (DownloadManager::DownloadVector::const_iterator it = cpp_results.begin();
697 it != cpp_results.end(); ++it) {
698 scoped_ptr<base::DictionaryValue> item(DownloadItemToJSON(*it));
699 json_results->Append(item.release());
700 }
701 result_.reset(json_results);
702 return true;
703 }
704
705 DownloadsPauseFunction::DownloadsPauseFunction()
706 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_PAUSE),
707 download_id_(DownloadId::Invalid().local()) {
708 }
709
710 DownloadsPauseFunction::~DownloadsPauseFunction() {}
711
712 bool DownloadsPauseFunction::ParseArgs() {
713 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_));
714 return true;
715 }
716
717 bool DownloadsPauseFunction::RunInternal() {
718 DownloadItem* download_item = GetActiveItem(download_id_);
719 if ((download_item == NULL) || !download_item->IsInProgress()) {
720 // This could be due to an invalid download ID, or it could be due to the
721 // download not being currently active.
722 error_ = download_extension_errors::kInvalidOperationError;
723 } else if (!download_item->IsPaused()) {
724 // If download_item->IsPaused() already then we treat it as a success.
725 download_item->TogglePause();
726 }
727 return error_.empty();
728 }
729
730 DownloadsResumeFunction::DownloadsResumeFunction()
731 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_RESUME),
732 download_id_(DownloadId::Invalid().local()) {
733 }
734
735 DownloadsResumeFunction::~DownloadsResumeFunction() {}
736
737 bool DownloadsResumeFunction::ParseArgs() {
738 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_));
739 return true;
740 }
741
742 bool DownloadsResumeFunction::RunInternal() {
743 DownloadItem* download_item = GetActiveItem(download_id_);
744 if (download_item == NULL) {
745 // This could be due to an invalid download ID, or it could be due to the
746 // download not being currently active.
747 error_ = download_extension_errors::kInvalidOperationError;
748 } else if (download_item->IsPaused()) {
749 // If !download_item->IsPaused() already, then we treat it as a success.
750 download_item->TogglePause();
751 }
752 return error_.empty();
753 }
754
755 DownloadsCancelFunction::DownloadsCancelFunction()
756 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_CANCEL),
757 download_id_(DownloadId::Invalid().local()) {
758 }
759
760 DownloadsCancelFunction::~DownloadsCancelFunction() {}
761
762 bool DownloadsCancelFunction::ParseArgs() {
763 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_));
764 return true;
765 }
766
767 bool DownloadsCancelFunction::RunInternal() {
768 DownloadItem* download_item = GetActiveItem(download_id_);
769 if (download_item != NULL)
770 download_item->Cancel(true);
771 // |download_item| can be NULL if the download ID was invalid or if the
772 // download is not currently active. Either way, we don't consider it a
773 // failure.
774 return error_.empty();
775 }
776
777 DownloadsEraseFunction::DownloadsEraseFunction()
778 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_ERASE) {
779 }
780
781 DownloadsEraseFunction::~DownloadsEraseFunction() {}
782
783 bool DownloadsEraseFunction::ParseArgs() {
784 base::DictionaryValue* query_json = NULL;
785 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query_json));
786 error_ = download_extension_errors::kNotImplementedError;
787 return false;
788 }
789
790 bool DownloadsEraseFunction::RunInternal() {
791 NOTIMPLEMENTED();
792 return false;
793 }
794
795 DownloadsSetDestinationFunction::DownloadsSetDestinationFunction()
796 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_SET_DESTINATION) {
797 }
798
799 DownloadsSetDestinationFunction::~DownloadsSetDestinationFunction() {}
800
801 bool DownloadsSetDestinationFunction::ParseArgs() {
802 int dl_id = 0;
803 std::string path;
804 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
805 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &path));
806 VLOG(1) << __FUNCTION__ << " " << dl_id << " " << &path;
807 error_ = download_extension_errors::kNotImplementedError;
808 return false;
809 }
810
811 bool DownloadsSetDestinationFunction::RunInternal() {
812 NOTIMPLEMENTED();
813 return false;
814 }
815
816 DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction()
817 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_ACCEPT_DANGER) {
818 }
819
820 DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {}
821
822 bool DownloadsAcceptDangerFunction::ParseArgs() {
823 int dl_id = 0;
824 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
825 VLOG(1) << __FUNCTION__ << " " << dl_id;
826 error_ = download_extension_errors::kNotImplementedError;
827 return false;
828 }
829
830 bool DownloadsAcceptDangerFunction::RunInternal() {
831 NOTIMPLEMENTED();
832 return false;
833 }
834
835 DownloadsShowFunction::DownloadsShowFunction()
836 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_SHOW) {
837 }
838
839 DownloadsShowFunction::~DownloadsShowFunction() {}
840
841 bool DownloadsShowFunction::ParseArgs() {
842 int dl_id = 0;
843 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
844 VLOG(1) << __FUNCTION__ << " " << dl_id;
845 error_ = download_extension_errors::kNotImplementedError;
846 return false;
847 }
848
849 bool DownloadsShowFunction::RunInternal() {
850 NOTIMPLEMENTED();
851 return false;
852 }
853
854 DownloadsDragFunction::DownloadsDragFunction()
855 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_DRAG) {
856 }
857
858 DownloadsDragFunction::~DownloadsDragFunction() {}
859
860 bool DownloadsDragFunction::ParseArgs() {
861 int dl_id = 0;
862 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
863 VLOG(1) << __FUNCTION__ << " " << dl_id;
864 error_ = download_extension_errors::kNotImplementedError;
865 return false;
866 }
867
868 bool DownloadsDragFunction::RunInternal() {
869 NOTIMPLEMENTED();
870 return false;
871 }
872
873 DownloadsGetFileIconFunction::DownloadsGetFileIconFunction()
874 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_GET_FILE_ICON),
875 icon_size_(kDefaultIconSize),
876 icon_extractor_(new DownloadFileIconExtractorImpl()) {
877 }
878
879 DownloadsGetFileIconFunction::~DownloadsGetFileIconFunction() {}
880
881 void DownloadsGetFileIconFunction::SetIconExtractorForTesting(
882 DownloadFileIconExtractor* extractor) {
883 DCHECK(extractor);
884 icon_extractor_.reset(extractor);
885 }
886
887 bool DownloadsGetFileIconFunction::ParseArgs() {
888 int dl_id = 0;
889 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
890
891 base::DictionaryValue* options = NULL;
892 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
893 if (options->HasKey(kSizeKey)) {
894 EXTENSION_FUNCTION_VALIDATE(options->GetInteger(kSizeKey, &icon_size_));
895 // We only support 16px and 32px icons. This is enforced in
896 // experimental.downloads.json.
897 DCHECK(icon_size_ == 16 || icon_size_ == 32);
898 }
899
900 DownloadManager* manager = NULL;
901 DownloadManager* incognito_manager = NULL;
902 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
903 DownloadItem* download_item = manager->GetDownloadItem(dl_id);
904 if (!download_item && incognito_manager)
905 download_item = incognito_manager->GetDownloadItem(dl_id);
906 if (!download_item) {
907 // The DownloadItem is is added to history when the path is determined. If
908 // the download is not in history, then we don't have a path / final
909 // filename and no icon.
910 error_ = download_extension_errors::kInvalidOperationError;
911 return false;
912 }
913 // In-progress downloads return the intermediate filename for GetFullPath()
914 // which doesn't have the final extension. Therefore we won't be able to
915 // derive a good file icon for it. So we use GetTargetFilePath() instead.
916 path_ = download_item->GetTargetFilePath();
917 DCHECK(!path_.empty());
918 return true;
919 }
920
921 bool DownloadsGetFileIconFunction::RunInternal() {
922 DCHECK(!path_.empty());
923 DCHECK(icon_extractor_.get());
924 EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath(
925 path_, IconLoaderSizeFromPixelSize(icon_size_),
926 base::Bind(&DownloadsGetFileIconFunction::OnIconURLExtracted, this)));
927 return true;
928 }
929
930 void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
931 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
932 if (url.empty())
933 error_ = download_extension_errors::kIconNotFoundError;
934 else
935 result_.reset(base::Value::CreateStringValue(url));
936 SendResponse(error_.empty());
937 }
938
939 ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(Profile* profile)
940 : profile_(profile),
941 manager_(NULL),
942 delete_item_jsons_(&item_jsons_),
943 delete_on_changed_stats_(&on_changed_stats_) {
944 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
945 DCHECK(profile_);
946 // Register a callback with the DownloadService for this profile to be called
947 // when it creates the DownloadManager, or now if the manager already exists.
948 DownloadServiceFactory::GetForProfile(profile)->OnManagerCreated(base::Bind(
949 &ExtensionDownloadsEventRouter::Init, base::Unretained(this)));
950 }
951
952 // The only public methods on this class are ModelChanged() and
953 // ManagerGoingDown(), and they are only called by DownloadManager, so
954 // there's no way for any methods on this class to be called before
955 // DownloadService calls Init() via the OnManagerCreated Callback above.
956 void ExtensionDownloadsEventRouter::Init(DownloadManager* manager) {
957 DCHECK(manager_ == NULL);
958 manager_ = manager;
959 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
960 manager_->AddObserver(this);
961 }
962
963 ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() {
964 if (manager_ != NULL)
965 manager_->RemoveObserver(this);
966 for (ItemMap::const_iterator iter = downloads_.begin();
967 iter != downloads_.end(); ++iter) {
968 if (iter->second != NULL)
969 iter->second->RemoveObserver(this);
970 }
971 }
972
973 ExtensionDownloadsEventRouter::OnChangedStat::OnChangedStat()
974 : fires(0),
975 total(0) {
976 }
977
978 ExtensionDownloadsEventRouter::OnChangedStat::~OnChangedStat() {
979 if (total > 0)
980 UMA_HISTOGRAM_PERCENTAGE("Download.OnChanged", (fires * 100 / total));
981 }
982
983 void ExtensionDownloadsEventRouter::OnDownloadUpdated(DownloadItem* item) {
984 int download_id = item->GetId();
985 if (item->GetState() == DownloadItem::REMOVING) {
986 // The REMOVING state indicates that this item is being erased.
987 // Let's unregister as an observer so that we don't see any more updates
988 // from it, dispatch the onErased event, and remove its json and is
989 // OnChangedStat from our maps.
990 downloads_.erase(download_id);
991 item->RemoveObserver(this);
992 DispatchEvent(extension_event_names::kOnDownloadErased,
993 base::Value::CreateIntegerValue(download_id));
994 delete item_jsons_[download_id];
995 item_jsons_.erase(download_id);
996 delete on_changed_stats_[download_id];
997 on_changed_stats_.erase(download_id);
998 return;
999 }
1000
1001 base::DictionaryValue* old_json = item_jsons_[download_id];
1002 scoped_ptr<base::DictionaryValue> new_json(DownloadItemToJSON(item));
1003 scoped_ptr<base::DictionaryValue> delta(new base::DictionaryValue());
1004 delta->SetInteger(kIdKey, download_id);
1005 std::set<std::string> new_fields;
1006 bool changed = false;
1007
1008 // For each field in the new json representation of the item except the
1009 // bytesReceived field, if the field has changed from the previous old json,
1010 // set the differences in the |delta| object and remember that something
1011 // significant changed.
1012 for (base::DictionaryValue::Iterator iter(*new_json.get());
1013 iter.HasNext(); iter.Advance()) {
1014 new_fields.insert(iter.key());
1015 if (iter.key() != kBytesReceivedKey) {
1016 base::Value* old_value = NULL;
1017 if (!old_json->HasKey(iter.key()) ||
1018 (old_json->Get(iter.key(), &old_value) &&
1019 !iter.value().Equals(old_value))) {
1020 delta->Set(iter.key() + ".new", iter.value().DeepCopy());
1021 if (old_value)
1022 delta->Set(iter.key() + ".old", old_value->DeepCopy());
1023 changed = true;
1024 }
1025 }
1026 }
1027
1028 // If a field was in the previous json but is not in the new json, set the
1029 // difference in |delta|.
1030 for (base::DictionaryValue::Iterator iter(*old_json);
1031 iter.HasNext(); iter.Advance()) {
1032 if (new_fields.find(iter.key()) == new_fields.end()) {
1033 delta->Set(iter.key() + ".old", iter.value().DeepCopy());
1034 changed = true;
1035 }
1036 }
1037
1038 // Update the OnChangedStat and dispatch the event if something significant
1039 // changed. Replace the stored json with the new json.
1040 ++(on_changed_stats_[download_id]->total);
1041 if (changed) {
1042 DispatchEvent(extension_event_names::kOnDownloadChanged, delta.release());
1043 ++(on_changed_stats_[download_id]->fires);
1044 }
1045 item_jsons_[download_id]->Swap(new_json.get());
1046 }
1047
1048 void ExtensionDownloadsEventRouter::OnDownloadOpened(DownloadItem* item) {
1049 }
1050
1051 void ExtensionDownloadsEventRouter::ModelChanged(DownloadManager* manager) {
1052 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1053 DCHECK(manager_ == manager);
1054 typedef std::set<int> DownloadIdSet;
1055
1056 // Get all the download items.
1057 DownloadManager::DownloadVector current_vec;
1058 manager_->SearchDownloads(string16(), &current_vec);
1059
1060 // Populate set<>s of download item identifiers so that we can find
1061 // differences between the old and the new set of download items.
1062 DownloadIdSet current_set, prev_set;
1063 for (ItemMap::const_iterator iter = downloads_.begin();
1064 iter != downloads_.end(); ++iter) {
1065 prev_set.insert(iter->first);
1066 }
1067 ItemMap current_map;
1068 for (DownloadManager::DownloadVector::const_iterator iter =
1069 current_vec.begin();
1070 iter != current_vec.end(); ++iter) {
1071 DownloadItem* item = *iter;
1072 int item_id = item->GetId();
1073 CHECK(item_id >= 0);
1074 DCHECK(current_map.find(item_id) == current_map.end());
1075 current_set.insert(item_id);
1076 current_map[item_id] = item;
1077 }
1078 DownloadIdSet new_set; // current_set - prev_set;
1079 std::set_difference(current_set.begin(), current_set.end(),
1080 prev_set.begin(), prev_set.end(),
1081 std::insert_iterator<DownloadIdSet>(
1082 new_set, new_set.begin()));
1083
1084 // For each download that was not in the old set of downloads but is in the
1085 // new set of downloads, fire an onCreated event, register as an Observer of
1086 // the item, store a json representation of the item so that we can easily
1087 // find changes in that json representation, and make an OnChangedStat.
1088 for (DownloadIdSet::const_iterator iter = new_set.begin();
1089 iter != new_set.end(); ++iter) {
1090 scoped_ptr<base::DictionaryValue> item(
1091 DownloadItemToJSON(current_map[*iter]));
1092 DispatchEvent(extension_event_names::kOnDownloadCreated, item->DeepCopy());
1093 DCHECK(item_jsons_.find(*iter) == item_jsons_.end());
1094 on_changed_stats_[*iter] = new OnChangedStat();
1095 current_map[*iter]->AddObserver(this);
1096 item_jsons_[*iter] = item.release();
1097 }
1098 downloads_.swap(current_map);
1099
1100 // Dispatching onErased is handled in OnDownloadUpdated when an item
1101 // transitions to the REMOVING state.
1102 }
1103
1104 void ExtensionDownloadsEventRouter::ManagerGoingDown(
1105 DownloadManager* manager) {
1106 manager_->RemoveObserver(this);
1107 manager_ = NULL;
1108 }
1109
1110 void ExtensionDownloadsEventRouter::DispatchEvent(
1111 const char* event_name, base::Value* arg) {
1112 ListValue args;
1113 args.Append(arg);
1114 std::string json_args;
1115 base::JSONWriter::Write(&args, &json_args);
1116 profile_->GetExtensionEventRouter()->DispatchEventToRenderers(
1117 event_name,
1118 json_args,
1119 profile_,
1120 GURL());
1121 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698