| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/download/download_extension_api.h" |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <iterator> |
| #include <set> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/stl_util.h" |
| #include "base/string16.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/download/download_file_icon_extractor.h" |
| #include "chrome/browser/download/download_service.h" |
| #include "chrome/browser/download/download_service_factory.h" |
| #include "chrome/browser/download/download_util.h" |
| #include "chrome/browser/extensions/extension_event_names.h" |
| #include "chrome/browser/extensions/extension_event_router.h" |
| #include "chrome/browser/icon_loader.h" |
| #include "chrome/browser/icon_manager.h" |
| #include "chrome/browser/renderer_host/chrome_render_message_filter.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/webui/web_ui_util.h" |
| #include "content/browser/download/download_file_manager.h" |
| #include "content/browser/download/download_id.h" |
| #include "content/browser/download/download_state_info.h" |
| #include "content/browser/download/download_types.h" |
| #include "content/browser/download/interrupt_reasons.h" |
| #include "content/browser/renderer_host/render_view_host.h" |
| #include "content/browser/renderer_host/resource_dispatcher_host.h" |
| #include "content/public/browser/download_item.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request.h" |
| |
| using content::BrowserThread; |
| using content::DownloadItem; |
| using content::DownloadManager; |
| |
| namespace download_extension_errors { |
| |
| // Error messages |
| const char kGenericError[] = "I'm afraid I can't do that."; |
| const char kIconNotFoundError[] = "Icon not found."; |
| const char kInvalidOperationError[] = "Invalid operation."; |
| const char kInvalidURLError[] = "Invalid URL."; |
| const char kNotImplementedError[] = "NotImplemented."; |
| |
| } // namespace download_extension_errors |
| |
| namespace { |
| |
| // Default icon size for getFileIcon() in pixels. |
| const int kDefaultIconSize = 32; |
| |
| // Parameter keys |
| const char kBodyKey[] = "body"; |
| const char kBytesReceivedKey[] = "bytesReceived"; |
| const char kDangerAcceptedKey[] = "dangerAccepted"; |
| const char kDangerContent[] = "content"; |
| const char kDangerFile[] = "file"; |
| const char kDangerKey[] = "danger"; |
| const char kDangerSafe[] = "safe"; |
| const char kDangerUrl[] = "url"; |
| const char kEndTimeKey[] = "endTime"; |
| const char kErrorKey[] = "error"; |
| const char kFileSizeKey[] = "fileSize"; |
| const char kFilenameKey[] = "filename"; |
| const char kHeaderNameKey[] = "name"; |
| const char kHeaderValueKey[] = "value"; |
| const char kHeadersKey[] = "headers"; |
| const char kIdKey[] = "id"; |
| const char kMethodKey[] = "method"; |
| const char kMimeKey[] = "mime"; |
| const char kPausedKey[] = "paused"; |
| const char kSaveAsKey[] = "saveAs"; |
| const char kSizeKey[] = "size"; |
| const char kStartTimeKey[] = "startTime"; |
| const char kStateComplete[] = "complete"; |
| const char kStateInProgress[] = "in_progress"; |
| const char kStateInterrupted[] = "interrupted"; |
| const char kStateKey[] = "state"; |
| const char kTotalBytesKey[] = "totalBytes"; |
| const char kUrlKey[] = "url"; |
| |
| const char* DangerString(DownloadStateInfo::DangerType danger) { |
| switch (danger) { |
| case DownloadStateInfo::MAYBE_DANGEROUS_CONTENT: |
| case DownloadStateInfo::NOT_DANGEROUS: return kDangerSafe; |
| case DownloadStateInfo::DANGEROUS_FILE: return kDangerFile; |
| case DownloadStateInfo::DANGEROUS_URL: return kDangerUrl; |
| case DownloadStateInfo::DANGEROUS_CONTENT: return kDangerContent; |
| default: |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| const char* StateString(DownloadItem::DownloadState state) { |
| switch (state) { |
| case DownloadItem::IN_PROGRESS: return kStateInProgress; |
| case DownloadItem::COMPLETE: return kStateComplete; |
| case DownloadItem::INTERRUPTED: // fall through |
| case DownloadItem::CANCELLED: return kStateInterrupted; |
| case DownloadItem::REMOVING: // fall through |
| default: |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| } // namespace |
| |
| bool DownloadsFunctionInterface::RunImplImpl( |
| DownloadsFunctionInterface* pimpl) { |
| CHECK(pimpl); |
| if (!pimpl->ParseArgs()) return false; |
| UMA_HISTOGRAM_ENUMERATION( |
| "Download.ApiFunctions", pimpl->function(), DOWNLOADS_FUNCTION_LAST); |
| return pimpl->RunInternal(); |
| } |
| |
| SyncDownloadsFunction::SyncDownloadsFunction( |
| DownloadsFunctionInterface::DownloadsFunctionName function) |
| : function_(function) { |
| } |
| |
| SyncDownloadsFunction::~SyncDownloadsFunction() {} |
| |
| bool SyncDownloadsFunction::RunImpl() { |
| return DownloadsFunctionInterface::RunImplImpl(this); |
| } |
| |
| DownloadsFunctionInterface::DownloadsFunctionName |
| SyncDownloadsFunction::function() const { |
| return function_; |
| } |
| |
| AsyncDownloadsFunction::AsyncDownloadsFunction( |
| DownloadsFunctionInterface::DownloadsFunctionName function) |
| : function_(function) { |
| } |
| |
| AsyncDownloadsFunction::~AsyncDownloadsFunction() {} |
| |
| bool AsyncDownloadsFunction::RunImpl() { |
| return DownloadsFunctionInterface::RunImplImpl(this); |
| } |
| |
| DownloadsFunctionInterface::DownloadsFunctionName |
| AsyncDownloadsFunction::function() const { |
| return function_; |
| } |
| |
| DownloadsDownloadFunction::DownloadsDownloadFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_DOWNLOAD) { |
| } |
| |
| DownloadsDownloadFunction::~DownloadsDownloadFunction() {} |
| |
| DownloadsDownloadFunction::IOData::IOData() |
| : save_as(false), |
| extra_headers(NULL), |
| method("GET"), |
| rdh(NULL), |
| resource_context(NULL), |
| render_process_host_id(0), |
| render_view_host_routing_id(0) { |
| } |
| |
| DownloadsDownloadFunction::IOData::~IOData() {} |
| |
| bool DownloadsDownloadFunction::ParseArgs() { |
| base::DictionaryValue* options = NULL; |
| std::string url; |
| iodata_.reset(new IOData()); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options)); |
| EXTENSION_FUNCTION_VALIDATE(options->GetString(kUrlKey, &url)); |
| iodata_->url = GURL(url); |
| if (!iodata_->url.is_valid()) { |
| error_ = download_extension_errors::kInvalidURLError; |
| return false; |
| } |
| if (options->HasKey(kFilenameKey)) |
| EXTENSION_FUNCTION_VALIDATE(options->GetString( |
| kFilenameKey, &iodata_->filename)); |
| // TODO(benjhayden): More robust validation of filename. |
| if (((iodata_->filename[0] == L'.') && (iodata_->filename[1] == L'.')) || |
| (iodata_->filename[0] == L'/')) { |
| error_ = download_extension_errors::kGenericError; |
| return false; |
| } |
| if (options->HasKey(kSaveAsKey)) |
| EXTENSION_FUNCTION_VALIDATE(options->GetBoolean( |
| kSaveAsKey, &iodata_->save_as)); |
| if (options->HasKey(kMethodKey)) |
| EXTENSION_FUNCTION_VALIDATE(options->GetString( |
| kMethodKey, &iodata_->method)); |
| // It's ok to use a pointer to extra_headers without DeepCopy()ing because |
| // |args_| (which owns *extra_headers) is guaranteed to live as long as |
| // |this|. |
| if (options->HasKey(kHeadersKey)) |
| EXTENSION_FUNCTION_VALIDATE(options->GetList( |
| kHeadersKey, &iodata_->extra_headers)); |
| if (options->HasKey(kBodyKey)) |
| EXTENSION_FUNCTION_VALIDATE(options->GetString( |
| kBodyKey, &iodata_->post_body)); |
| if (iodata_->extra_headers != NULL) { |
| for (size_t index = 0; index < iodata_->extra_headers->GetSize(); ++index) { |
| base::DictionaryValue* header = NULL; |
| std::string name, value; |
| EXTENSION_FUNCTION_VALIDATE(iodata_->extra_headers->GetDictionary( |
| index, &header)); |
| EXTENSION_FUNCTION_VALIDATE(header->GetString( |
| kHeaderNameKey, &name)); |
| EXTENSION_FUNCTION_VALIDATE(header->GetString( |
| kHeaderValueKey, &value)); |
| if (!net::HttpUtil::IsSafeHeader(name)) { |
| error_ = download_extension_errors::kGenericError; |
| return false; |
| } |
| } |
| } |
| iodata_->rdh = g_browser_process->resource_dispatcher_host(); |
| iodata_->resource_context = &profile()->GetResourceContext(); |
| iodata_->render_process_host_id = render_view_host()->process()->GetID(); |
| iodata_->render_view_host_routing_id = render_view_host()->routing_id(); |
| return true; |
| } |
| |
| bool DownloadsDownloadFunction::RunInternal() { |
| VLOG(1) << __FUNCTION__ << " " << iodata_->url.spec(); |
| if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( |
| &DownloadsDownloadFunction::BeginDownloadOnIOThread, this))) { |
| error_ = download_extension_errors::kGenericError; |
| return false; |
| } |
| return true; |
| } |
| |
| void DownloadsDownloadFunction::BeginDownloadOnIOThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DVLOG(1) << __FUNCTION__ << " " << iodata_->url.spec(); |
| DownloadSaveInfo save_info; |
| // TODO(benjhayden) Ensure that this filename is interpreted as a path |
| // relative to the default downloads directory without allowing '..'. |
| save_info.suggested_name = iodata_->filename; |
| net::URLRequest* request = new net::URLRequest(iodata_->url, iodata_->rdh); |
| request->set_method(iodata_->method); |
| if (iodata_->extra_headers != NULL) { |
| for (size_t index = 0; index < iodata_->extra_headers->GetSize(); ++index) { |
| base::DictionaryValue* header = NULL; |
| std::string name, value; |
| CHECK(iodata_->extra_headers->GetDictionary(index, &header)); |
| CHECK(header->GetString("name", &name)); |
| CHECK(header->GetString("value", &value)); |
| request->SetExtraRequestHeaderByName(name, value, false/*overwrite*/); |
| } |
| } |
| if (!iodata_->post_body.empty()) { |
| request->AppendBytesToUpload(iodata_->post_body.data(), |
| iodata_->post_body.size()); |
| } |
| iodata_->rdh->BeginDownload( |
| request, |
| save_info, |
| iodata_->save_as, |
| base::Bind(&DownloadsDownloadFunction::OnStarted, this), |
| iodata_->render_process_host_id, |
| iodata_->render_view_host_routing_id, |
| *(iodata_->resource_context)); |
| iodata_.reset(); |
| } |
| |
| void DownloadsDownloadFunction::OnStarted(DownloadId dl_id, net::Error error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| VLOG(1) << __FUNCTION__ << " " << dl_id << " " << error; |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( |
| &DownloadsDownloadFunction::RespondOnUIThread, this, |
| dl_id.local(), error)); |
| } |
| |
| void DownloadsDownloadFunction::RespondOnUIThread(int dl_id, net::Error error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(1) << __FUNCTION__; |
| if (dl_id >= 0) { |
| result_.reset(base::Value::CreateIntegerValue(dl_id)); |
| } else { |
| error_ = net::ErrorToString(error); |
| } |
| SendResponse(error_.empty()); |
| } |
| |
| DownloadsSearchFunction::DownloadsSearchFunction() |
| : SyncDownloadsFunction(DOWNLOADS_FUNCTION_SEARCH) { |
| } |
| |
| DownloadsSearchFunction::~DownloadsSearchFunction() {} |
| |
| bool DownloadsSearchFunction::ParseArgs() { |
| base::DictionaryValue* query_json = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query_json)); |
| error_ = download_extension_errors::kNotImplementedError; |
| return false; |
| } |
| |
| bool DownloadsSearchFunction::RunInternal() { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| DownloadsPauseFunction::DownloadsPauseFunction() |
| : SyncDownloadsFunction(DOWNLOADS_FUNCTION_PAUSE), |
| download_id_(DownloadId::Invalid().local()) { |
| } |
| |
| DownloadsPauseFunction::~DownloadsPauseFunction() {} |
| |
| bool DownloadsPauseFunction::ParseArgs() { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_)); |
| return true; |
| } |
| |
| bool DownloadsPauseFunction::RunInternal() { |
| DownloadManager* download_manager = |
| DownloadServiceFactory::GetForProfile(profile())->GetDownloadManager(); |
| DownloadItem* download_item = |
| download_manager->GetActiveDownloadItem(download_id_); |
| DCHECK(!download_item || download_item->IsInProgress()); |
| |
| if (!download_item) { |
| // This could be due to an invalid download ID, or it could be due to the |
| // download not being currently active. |
| error_ = download_extension_errors::kInvalidOperationError; |
| } else if (!download_item->IsPaused()) { |
| // If download_item->IsPaused() already then we treat it as a success. |
| download_item->TogglePause(); |
| } |
| return error_.empty(); |
| } |
| |
| DownloadsResumeFunction::DownloadsResumeFunction() |
| : SyncDownloadsFunction(DOWNLOADS_FUNCTION_RESUME), |
| download_id_(DownloadId::Invalid().local()) { |
| } |
| |
| DownloadsResumeFunction::~DownloadsResumeFunction() {} |
| |
| bool DownloadsResumeFunction::ParseArgs() { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_)); |
| return true; |
| } |
| |
| bool DownloadsResumeFunction::RunInternal() { |
| DownloadManager* download_manager = |
| DownloadServiceFactory::GetForProfile(profile())->GetDownloadManager(); |
| DownloadItem* download_item = |
| download_manager->GetActiveDownloadItem(download_id_); |
| DCHECK(!download_item || download_item->IsInProgress()); |
| |
| if (!download_item) { |
| // This could be due to an invalid download ID, or it could be due to the |
| // download not being currently active. |
| error_ = download_extension_errors::kInvalidOperationError; |
| } else if (download_item->IsPaused()) { |
| // If !download_item->IsPaused() already, then we treat it as a success. |
| download_item->TogglePause(); |
| } |
| return error_.empty(); |
| } |
| |
| DownloadsCancelFunction::DownloadsCancelFunction() |
| : SyncDownloadsFunction(DOWNLOADS_FUNCTION_CANCEL), |
| download_id_(DownloadId::Invalid().local()) { |
| } |
| |
| DownloadsCancelFunction::~DownloadsCancelFunction() {} |
| |
| bool DownloadsCancelFunction::ParseArgs() { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_)); |
| return true; |
| } |
| |
| bool DownloadsCancelFunction::RunInternal() { |
| DownloadManager* download_manager = |
| DownloadServiceFactory::GetForProfile(profile())->GetDownloadManager(); |
| DownloadItem* download_item = |
| download_manager->GetActiveDownloadItem(download_id_); |
| |
| if (download_item) |
| download_item->Cancel(true); |
| // |download_item| can be NULL if the download ID was invalid or if the |
| // download is not currently active. Either way, we don't consider it a |
| // failure. |
| return error_.empty(); |
| } |
| |
| DownloadsEraseFunction::DownloadsEraseFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_ERASE) { |
| } |
| |
| DownloadsEraseFunction::~DownloadsEraseFunction() {} |
| |
| bool DownloadsEraseFunction::ParseArgs() { |
| base::DictionaryValue* query_json = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query_json)); |
| error_ = download_extension_errors::kNotImplementedError; |
| return false; |
| } |
| |
| bool DownloadsEraseFunction::RunInternal() { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| DownloadsSetDestinationFunction::DownloadsSetDestinationFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_SET_DESTINATION) { |
| } |
| |
| DownloadsSetDestinationFunction::~DownloadsSetDestinationFunction() {} |
| |
| bool DownloadsSetDestinationFunction::ParseArgs() { |
| int dl_id = 0; |
| std::string path; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id)); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &path)); |
| VLOG(1) << __FUNCTION__ << " " << dl_id << " " << &path; |
| error_ = download_extension_errors::kNotImplementedError; |
| return false; |
| } |
| |
| bool DownloadsSetDestinationFunction::RunInternal() { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_ACCEPT_DANGER) { |
| } |
| |
| DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {} |
| |
| bool DownloadsAcceptDangerFunction::ParseArgs() { |
| int dl_id = 0; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id)); |
| VLOG(1) << __FUNCTION__ << " " << dl_id; |
| error_ = download_extension_errors::kNotImplementedError; |
| return false; |
| } |
| |
| bool DownloadsAcceptDangerFunction::RunInternal() { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| DownloadsShowFunction::DownloadsShowFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_SHOW) { |
| } |
| |
| DownloadsShowFunction::~DownloadsShowFunction() {} |
| |
| bool DownloadsShowFunction::ParseArgs() { |
| int dl_id = 0; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id)); |
| VLOG(1) << __FUNCTION__ << " " << dl_id; |
| error_ = download_extension_errors::kNotImplementedError; |
| return false; |
| } |
| |
| bool DownloadsShowFunction::RunInternal() { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| DownloadsDragFunction::DownloadsDragFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_DRAG) { |
| } |
| |
| DownloadsDragFunction::~DownloadsDragFunction() {} |
| |
| bool DownloadsDragFunction::ParseArgs() { |
| int dl_id = 0; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id)); |
| VLOG(1) << __FUNCTION__ << " " << dl_id; |
| error_ = download_extension_errors::kNotImplementedError; |
| return false; |
| } |
| |
| bool DownloadsDragFunction::RunInternal() { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| namespace { |
| |
| class DownloadFileIconExtractorImpl : public DownloadFileIconExtractor { |
| public: |
| DownloadFileIconExtractorImpl() {} |
| |
| ~DownloadFileIconExtractorImpl() {} |
| |
| virtual bool ExtractIconURLForPath(const FilePath& path, |
| IconLoader::IconSize icon_size, |
| IconURLCallback callback) OVERRIDE; |
| private: |
| void OnIconLoadComplete(IconManager::Handle handle, gfx::Image* icon); |
| |
| CancelableRequestConsumer cancelable_consumer_; |
| IconURLCallback callback_; |
| }; |
| |
| bool DownloadFileIconExtractorImpl::ExtractIconURLForPath( |
| const FilePath& path, |
| IconLoader::IconSize icon_size, |
| IconURLCallback callback) { |
| callback_ = callback; |
| IconManager* im = g_browser_process->icon_manager(); |
| // The contents of the file at |path| may have changed since a previous |
| // request, in which case the associated icon may also have changed. |
| // Therefore, for the moment we always call LoadIcon instead of attempting |
| // a LookupIcon. |
| im->LoadIcon( |
| path, icon_size, &cancelable_consumer_, |
| base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete, |
| base::Unretained(this))); |
| return true; |
| } |
| |
| void DownloadFileIconExtractorImpl::OnIconLoadComplete( |
| IconManager::Handle handle, |
| gfx::Image* icon) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| std::string url; |
| if (icon) |
| url = web_ui_util::GetImageDataUrl(*icon); |
| callback_.Run(url); |
| } |
| |
| IconLoader::IconSize IconLoaderSizeFromPixelSize(int pixel_size) { |
| switch (pixel_size) { |
| case 16: return IconLoader::SMALL; |
| case 32: return IconLoader::NORMAL; |
| default: |
| NOTREACHED(); |
| return IconLoader::NORMAL; |
| } |
| } |
| |
| } // namespace |
| |
| DownloadsGetFileIconFunction::DownloadsGetFileIconFunction() |
| : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_GET_FILE_ICON), |
| icon_size_(kDefaultIconSize), |
| icon_extractor_(new DownloadFileIconExtractorImpl()) { |
| } |
| |
| DownloadsGetFileIconFunction::~DownloadsGetFileIconFunction() {} |
| |
| void DownloadsGetFileIconFunction::SetIconExtractorForTesting( |
| DownloadFileIconExtractor* extractor) { |
| DCHECK(extractor); |
| icon_extractor_.reset(extractor); |
| } |
| |
| bool DownloadsGetFileIconFunction::ParseArgs() { |
| int dl_id = 0; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id)); |
| |
| base::DictionaryValue* options = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options)); |
| if (options->HasKey(kSizeKey)) { |
| EXTENSION_FUNCTION_VALIDATE(options->GetInteger(kSizeKey, &icon_size_)); |
| // We only support 16px and 32px icons. This is enforced in |
| // experimental.downloads.json. |
| DCHECK(icon_size_ == 16 || icon_size_ == 32); |
| } |
| |
| DownloadManager* download_manager = |
| DownloadServiceFactory::GetForProfile(profile())->GetDownloadManager(); |
| DownloadItem* download_item = download_manager->GetDownloadItem(dl_id); |
| if (download_item == NULL) { |
| // The DownloadItem is is added to history when the path is determined. If |
| // the download is not in history, then we don't have a path / final |
| // filename and no icon. |
| error_ = download_extension_errors::kInvalidOperationError; |
| return false; |
| } |
| // In-progress downloads return the intermediate filename for GetFullPath() |
| // which doesn't have the final extension. Therefore we won't be able to |
| // derive a good file icon for it. So we use GetTargetFilePath() instead. |
| path_ = download_item->GetTargetFilePath(); |
| DCHECK(!path_.empty()); |
| return true; |
| } |
| |
| bool DownloadsGetFileIconFunction::RunInternal() { |
| DCHECK(!path_.empty()); |
| DCHECK(icon_extractor_.get()); |
| EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath( |
| path_, IconLoaderSizeFromPixelSize(icon_size_), |
| base::Bind(&DownloadsGetFileIconFunction::OnIconURLExtracted, this))); |
| return true; |
| } |
| |
| void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (url.empty()) |
| error_ = download_extension_errors::kIconNotFoundError; |
| else |
| result_.reset(base::Value::CreateStringValue(url)); |
| SendResponse(error_.empty()); |
| } |
| |
| namespace { |
| base::DictionaryValue* DownloadItemToJSON(DownloadItem* item) { |
| base::DictionaryValue* json = new base::DictionaryValue(); |
| json->SetInteger(kIdKey, item->GetId()); |
| json->SetString(kUrlKey, item->GetOriginalUrl().spec()); |
| json->SetString(kFilenameKey, item->GetFullPath().LossyDisplayName()); |
| json->SetString(kDangerKey, DangerString(item->GetDangerType())); |
| json->SetBoolean(kDangerAcceptedKey, |
| item->GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED); |
| json->SetString(kStateKey, StateString(item->GetState())); |
| json->SetBoolean(kPausedKey, item->IsPaused()); |
| json->SetString(kMimeKey, item->GetMimeType()); |
| json->SetInteger(kStartTimeKey, |
| (item->GetStartTime() - base::Time::UnixEpoch()).InMilliseconds()); |
| json->SetInteger(kBytesReceivedKey, item->GetReceivedBytes()); |
| json->SetInteger(kTotalBytesKey, item->GetTotalBytes()); |
| if (item->GetState() == DownloadItem::INTERRUPTED) |
| json->SetInteger(kErrorKey, static_cast<int>(item->GetLastReason())); |
| // TODO(benjhayden): Implement endTime and fileSize. |
| // json->SetInteger(kEndTimeKey, -1); |
| json->SetInteger(kFileSizeKey, item->GetTotalBytes()); |
| return json; |
| } |
| } // anonymous namespace |
| |
| ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(Profile* profile) |
| : profile_(profile), |
| manager_(NULL) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| // Register a callback with the DownloadService for this profile to be called |
| // when it creates the DownloadManager, or now if the manager already exists. |
| DownloadServiceFactory::GetForProfile(profile)->OnManagerCreated(base::Bind( |
| &ExtensionDownloadsEventRouter::Init, base::Unretained(this))); |
| } |
| |
| // The only public methods on this class are ModelChanged() and |
| // ManagerGoingDown(), and they are only called by DownloadManager, so |
| // there's no way for any methods on this class to be called before |
| // DownloadService calls Init() via the OnManagerCreated Callback above. |
| void ExtensionDownloadsEventRouter::Init(DownloadManager* manager) { |
| DCHECK(manager_ == NULL); |
| manager_ = manager; |
| manager_->AddObserver(this); |
| } |
| |
| ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() { |
| if (manager_ != NULL) |
| manager_->RemoveObserver(this); |
| } |
| |
| void ExtensionDownloadsEventRouter::ModelChanged() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (manager_ == NULL) |
| return; |
| DownloadManager::DownloadVector current_vec; |
| manager_->SearchDownloads(string16(), ¤t_vec); |
| DownloadIdSet current_set; |
| ItemMap current_map; |
| for (DownloadManager::DownloadVector::const_iterator iter = |
| current_vec.begin(); |
| iter != current_vec.end(); ++iter) { |
| DownloadItem* item = *iter; |
| int item_id = item->GetId(); |
| // TODO(benjhayden): Remove the following line when every item's id >= 0, |
| // which will allow firing onErased events for items from the history. |
| if (item_id < 0) continue; |
| DCHECK(current_map.find(item_id) == current_map.end()); |
| current_set.insert(item_id); |
| current_map[item_id] = item; |
| } |
| DownloadIdSet new_set; // current_set - downloads_; |
| DownloadIdSet erased_set; // downloads_ - current_set; |
| std::insert_iterator<DownloadIdSet> new_insertor(new_set, new_set.begin()); |
| std::insert_iterator<DownloadIdSet> erased_insertor( |
| erased_set, erased_set.begin()); |
| std::set_difference(current_set.begin(), current_set.end(), |
| downloads_.begin(), downloads_.end(), |
| new_insertor); |
| std::set_difference(downloads_.begin(), downloads_.end(), |
| current_set.begin(), current_set.end(), |
| erased_insertor); |
| for (DownloadIdSet::const_iterator iter = new_set.begin(); |
| iter != new_set.end(); ++iter) { |
| DispatchEvent(extension_event_names::kOnDownloadCreated, |
| DownloadItemToJSON(current_map[*iter])); |
| } |
| for (DownloadIdSet::const_iterator iter = erased_set.begin(); |
| iter != erased_set.end(); ++iter) { |
| DispatchEvent(extension_event_names::kOnDownloadErased, |
| base::Value::CreateIntegerValue(*iter)); |
| } |
| downloads_.swap(current_set); |
| } |
| |
| void ExtensionDownloadsEventRouter::ManagerGoingDown() { |
| manager_->RemoveObserver(this); |
| manager_ = NULL; |
| } |
| |
| void ExtensionDownloadsEventRouter::DispatchEvent( |
| const char* event_name, base::Value* arg) { |
| ListValue args; |
| args.Append(arg); |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| profile_->GetExtensionEventRouter()->DispatchEventToRenderers( |
| event_name, |
| json_args, |
| profile_, |
| GURL()); |
| } |