| Index: chrome/browser/extensions/api/downloads/downloads_api.cc
|
| diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
|
| index 7de85f433c1e248f0b2fdf26c1b591b61cd9f32a..0bfa228dc14254543c92f411f33ed413baaffb91 100644
|
| --- a/chrome/browser/extensions/api/downloads/downloads_api.cc
|
| +++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
|
| @@ -14,6 +14,7 @@
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| #include "base/callback.h"
|
| +#include "base/file_util.h"
|
| #include "base/files/file_path.h"
|
| #include "base/json/json_writer.h"
|
| #include "base/lazy_instance.h"
|
| @@ -30,9 +31,11 @@
|
| #include "chrome/browser/chrome_notification_types.h"
|
| #include "chrome/browser/download/download_danger_prompt.h"
|
| #include "chrome/browser/download/download_file_icon_extractor.h"
|
| +#include "chrome/browser/download/download_prefs.h"
|
| #include "chrome/browser/download/download_query.h"
|
| #include "chrome/browser/download/download_service.h"
|
| #include "chrome/browser/download/download_service_factory.h"
|
| +#include "chrome/browser/download/download_shelf.h"
|
| #include "chrome/browser/download/download_util.h"
|
| #include "chrome/browser/extensions/event_names.h"
|
| #include "chrome/browser/extensions/event_router.h"
|
| @@ -43,8 +46,10 @@
|
| #include "chrome/browser/extensions/extension_system.h"
|
| #include "chrome/browser/icon_loader.h"
|
| #include "chrome/browser/icon_manager.h"
|
| +#include "chrome/browser/platform_util.h"
|
| #include "chrome/browser/renderer_host/chrome_render_message_filter.h"
|
| #include "chrome/browser/ui/browser.h"
|
| +#include "chrome/browser/ui/browser_window.h"
|
| #include "chrome/common/cancelable_task_tracker.h"
|
| #include "chrome/common/extensions/api/downloads.h"
|
| #include "chrome/common/extensions/permissions/permissions_data.h"
|
| @@ -75,24 +80,34 @@ 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 kInvalidDangerTypeError[] = "Invalid danger type";
|
| -const char kInvalidFilenameError[] = "Invalid filename";
|
| -const char kInvalidFilterError[] = "Invalid query filter";
|
| -const char kInvalidOperationError[] = "Invalid operation";
|
| -const char kInvalidOrderByError[] = "Invalid orderBy field";
|
| +const char kEmptyFile[] = "Filename not yet determined";
|
| +const char kFileAlreadyDeleted[] = "Download file already deleted";
|
| +const char kIconNotFound[] = "Icon not found";
|
| +const char kInvalidDangerType[] = "Invalid danger type";
|
| +const char kInvalidFilename[] = "Invalid filename";
|
| +const char kInvalidFilter[] = "Invalid query filter";
|
| +const char kInvalidHeader[] = "Invalid request header";
|
| +const char kInvalidId[] = "Invalid downloadId";
|
| +const char kInvalidOrderBy[] = "Invalid orderBy field";
|
| const char kInvalidQueryLimit[] = "Invalid query limit";
|
| -const char kInvalidStateError[] = "Invalid state";
|
| -const char kInvalidURLError[] = "Invalid URL";
|
| -const char kNotImplementedError[] = "NotImplemented";
|
| -const char kTooManyListenersError[] = "Each extension may have at most one "
|
| +const char kInvalidState[] = "Invalid state";
|
| +const char kInvalidURL[] = "Invalid URL";
|
| +const char kInvisibleContext[] = "Javascript execution context is not visible "
|
| + "(tab, window, popup bubble)";
|
| +const char kNotComplete[] = "Download must be complete";
|
| +const char kNotDangerous[] = "Download must be dangerous";
|
| +const char kNotInProgress[] = "Download must be in progress";
|
| +const char kNotResumable[] = "DownloadItem.canResume must be true";
|
| +const char kOpenPermission[] = "The \"downloads.open\" permission is required";
|
| +const char kTooManyListeners[] = "Each extension may have at most one "
|
| "onDeterminingFilename listener between all of its renderer execution "
|
| "contexts.";
|
| +const char kUnexpectedDeterminer[] = "Unexpected determineFilename call";
|
|
|
| } // namespace download_extension_errors
|
|
|
| +namespace errors = download_extension_errors;
|
| +
|
| namespace {
|
|
|
| // Default icon size for getFileIcon() in pixels.
|
| @@ -100,29 +115,31 @@ const int kDefaultIconSize = 32;
|
|
|
| // Parameter keys
|
| const char kBytesReceivedKey[] = "bytesReceived";
|
| -const char kDangerAcceptedKey[] = "dangerAccepted";
|
| +const char kCanResumeKey[] = "canResume";
|
| +const char kDangerAccepted[] = "accepted";
|
| const char kDangerContent[] = "content";
|
| const char kDangerFile[] = "file";
|
| +const char kDangerHost[] = "host";
|
| const char kDangerKey[] = "danger";
|
| const char kDangerSafe[] = "safe";
|
| const char kDangerUncommon[] = "uncommon";
|
| const char kDangerUnwanted[] = "unwanted";
|
| -const char kDangerAccepted[] = "accepted";
|
| -const char kDangerHost[] = "host";
|
| const char kDangerUrl[] = "url";
|
| const char kEndTimeKey[] = "endTime";
|
| const char kEndedAfterKey[] = "endedAfter";
|
| const char kEndedBeforeKey[] = "endedBefore";
|
| const char kErrorKey[] = "error";
|
| +const char kEstimatedEndTimeKey[] = "estimatedEndTime";
|
| const char kExistsKey[] = "exists";
|
| const char kFileSizeKey[] = "fileSize";
|
| const char kFilenameKey[] = "filename";
|
| const char kFilenameRegexKey[] = "filenameRegex";
|
| const char kIdKey[] = "id";
|
| -const char kIncognito[] = "incognito";
|
| +const char kIncognitoKey[] = "incognito";
|
| const char kMimeKey[] = "mime";
|
| const char kPausedKey[] = "paused";
|
| const char kQueryKey[] = "query";
|
| +const char kReferrerUrlKey[] = "referrer";
|
| const char kStartTimeKey[] = "startTime";
|
| const char kStartedAfterKey[] = "startedAfter";
|
| const char kStartedBeforeKey[] = "startedBefore";
|
| @@ -216,30 +233,34 @@ scoped_ptr<base::DictionaryValue> DownloadItemToJSON(
|
| json->SetInteger(kIdKey, download_item->GetId());
|
| const GURL& url = download_item->GetOriginalUrl();
|
| json->SetString(kUrlKey, (url.is_valid() ? url.spec() : std::string()));
|
| + const GURL& referrer = download_item->GetReferrerUrl();
|
| + json->SetString(kReferrerUrlKey, (referrer.is_valid() ? referrer.spec()
|
| + : std::string()));
|
| json->SetString(kFilenameKey,
|
| download_item->GetTargetFilePath().LossyDisplayName());
|
| json->SetString(kDangerKey, DangerString(download_item->GetDangerType()));
|
| - if (download_item->GetDangerType() !=
|
| - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
|
| - json->SetBoolean(kDangerAcceptedKey,
|
| - download_item->GetDangerType() ==
|
| - content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
|
| json->SetString(kStateKey, StateString(download_item->GetState()));
|
| + json->SetBoolean(kCanResumeKey, download_item->CanResume());
|
| json->SetBoolean(kPausedKey, download_item->IsPaused());
|
| json->SetString(kMimeKey, download_item->GetMimeType());
|
| json->SetString(kStartTimeKey, TimeToISO8601(download_item->GetStartTime()));
|
| json->SetInteger(kBytesReceivedKey, download_item->GetReceivedBytes());
|
| json->SetInteger(kTotalBytesKey, download_item->GetTotalBytes());
|
| - json->SetBoolean(kIncognito, incognito);
|
| + json->SetBoolean(kIncognitoKey, incognito);
|
| if (download_item->GetState() == DownloadItem::INTERRUPTED) {
|
| - json->SetInteger(kErrorKey, static_cast<int>(
|
| + json->SetString(kErrorKey, content::InterruptReasonDebugString(
|
| download_item->GetLastReason()));
|
| } else if (download_item->GetState() == DownloadItem::CANCELLED) {
|
| - json->SetInteger(kErrorKey, static_cast<int>(
|
| + json->SetString(kErrorKey, content::InterruptReasonDebugString(
|
| content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED));
|
| }
|
| if (!download_item->GetEndTime().is_null())
|
| json->SetString(kEndTimeKey, TimeToISO8601(download_item->GetEndTime()));
|
| + base::TimeDelta time_remaining;
|
| + if (download_item->TimeRemaining(&time_remaining)) {
|
| + base::Time now = base::Time::Now();
|
| + json->SetString(kEstimatedEndTimeKey, TimeToISO8601(now + time_remaining));
|
| + }
|
| // TODO(benjhayden): Implement fileSize.
|
| json->SetInteger(kFileSizeKey, download_item->GetTotalBytes());
|
| return scoped_ptr<base::DictionaryValue>(json);
|
| @@ -300,7 +321,6 @@ typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap;
|
|
|
| void InitFilterTypeMap(FilterTypeMap& filter_types) {
|
| filter_types[kBytesReceivedKey] = DownloadQuery::FILTER_BYTES_RECEIVED;
|
| - filter_types[kDangerAcceptedKey] = DownloadQuery::FILTER_DANGER_ACCEPTED;
|
| filter_types[kExistsKey] = DownloadQuery::FILTER_EXISTS;
|
| filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME;
|
| filter_types[kFilenameRegexKey] = DownloadQuery::FILTER_FILENAME_REGEX;
|
| @@ -326,7 +346,6 @@ typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap;
|
| void InitSortTypeMap(SortTypeMap& sorter_types) {
|
| sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED;
|
| sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER;
|
| - sorter_types[kDangerAcceptedKey] = DownloadQuery::SORT_DANGER_ACCEPTED;
|
| sorter_types[kEndTimeKey] = DownloadQuery::SORT_END_TIME;
|
| sorter_types[kExistsKey] = DownloadQuery::SORT_EXISTS;
|
| sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME;
|
| @@ -372,16 +391,6 @@ DownloadItem* GetDownload(Profile* profile, bool include_incognito, int id) {
|
| return download_item;
|
| }
|
|
|
| -DownloadItem* GetDownloadIfInProgress(
|
| - Profile* profile,
|
| - bool include_incognito,
|
| - int id) {
|
| - DownloadItem* download_item = GetDownload(profile, include_incognito, id);
|
| - if (download_item && (download_item->GetState() == DownloadItem::IN_PROGRESS))
|
| - return download_item;
|
| - return NULL;
|
| -}
|
| -
|
| enum DownloadsFunctionName {
|
| DOWNLOADS_FUNCTION_DOWNLOAD = 0,
|
| DOWNLOADS_FUNCTION_SEARCH = 1,
|
| @@ -395,6 +404,8 @@ enum DownloadsFunctionName {
|
| DOWNLOADS_FUNCTION_DRAG = 9,
|
| DOWNLOADS_FUNCTION_GET_FILE_ICON = 10,
|
| DOWNLOADS_FUNCTION_OPEN = 11,
|
| + DOWNLOADS_FUNCTION_REMOVE_FILE = 12,
|
| + DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER = 13,
|
| // Insert new values here, not at the beginning.
|
| DOWNLOADS_FUNCTION_LAST
|
| };
|
| @@ -406,7 +417,9 @@ void RecordApiFunctions(DownloadsFunctionName function) {
|
| }
|
|
|
| void CompileDownloadQueryOrderBy(
|
| - const std::string& order_by_str, std::string* error, DownloadQuery* query) {
|
| + const std::vector<std::string>& order_by_strs,
|
| + std::string* error,
|
| + DownloadQuery* query) {
|
| // TODO(benjhayden): Consider switching from LazyInstance to explicit string
|
| // comparisons.
|
| static base::LazyInstance<SortTypeMap> sorter_types =
|
| @@ -414,8 +427,6 @@ void CompileDownloadQueryOrderBy(
|
| if (sorter_types.Get().size() == 0)
|
| InitSortTypeMap(sorter_types.Get());
|
|
|
| - std::vector<std::string> order_by_strs;
|
| - base::SplitString(order_by_str, ' ', &order_by_strs);
|
| for (std::vector<std::string>::const_iterator iter = order_by_strs.begin();
|
| iter != order_by_strs.end(); ++iter) {
|
| std::string term_str = *iter;
|
| @@ -429,7 +440,7 @@ void CompileDownloadQueryOrderBy(
|
| SortTypeMap::const_iterator sorter_type =
|
| sorter_types.Get().find(term_str);
|
| if (sorter_type == sorter_types.Get().end()) {
|
| - *error = download_extension_errors::kInvalidOrderByError;
|
| + *error = errors::kInvalidOrderBy;
|
| return;
|
| }
|
| query->AddSorter(sorter_type->second, direction);
|
| @@ -451,19 +462,24 @@ void RunDownloadQuery(
|
|
|
| DownloadQuery query_out;
|
|
|
| + size_t limit = 1000;
|
| if (query_in.limit.get()) {
|
| if (*query_in.limit.get() < 0) {
|
| - *error = download_extension_errors::kInvalidQueryLimit;
|
| + *error = errors::kInvalidQueryLimit;
|
| return;
|
| }
|
| - query_out.Limit(*query_in.limit.get());
|
| + limit = *query_in.limit.get();
|
| }
|
| + if (limit > 0) {
|
| + query_out.Limit(limit);
|
| + }
|
| +
|
| std::string state_string =
|
| extensions::api::downloads::ToString(query_in.state);
|
| if (!state_string.empty()) {
|
| DownloadItem::DownloadState state = StateEnumFromString(state_string);
|
| if (state == DownloadItem::MAX_DOWNLOAD_STATE) {
|
| - *error = download_extension_errors::kInvalidStateError;
|
| + *error = errors::kInvalidState;
|
| return;
|
| }
|
| query_out.AddFilter(state);
|
| @@ -474,7 +490,7 @@ void RunDownloadQuery(
|
| content::DownloadDangerType danger_type = DangerEnumFromString(
|
| danger_string);
|
| if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
|
| - *error = download_extension_errors::kInvalidDangerTypeError;
|
| + *error = errors::kInvalidDangerType;
|
| return;
|
| }
|
| query_out.AddFilter(danger_type);
|
| @@ -492,7 +508,7 @@ void RunDownloadQuery(
|
| filter_types.Get().find(query_json_field.key());
|
| if (filter_type != filter_types.Get().end()) {
|
| if (!query_out.AddFilter(filter_type->second, query_json_field.value())) {
|
| - *error = download_extension_errors::kInvalidFilterError;
|
| + *error = errors::kInvalidFilter;
|
| return;
|
| }
|
| }
|
| @@ -514,6 +530,21 @@ void RunDownloadQuery(
|
| query_out.Search(all_items.begin(), all_items.end(), results);
|
| }
|
|
|
| +DownloadPathReservationTracker::FilenameConflictAction ConvertConflictAction(
|
| + extensions::api::downloads::FilenameConflictAction action) {
|
| + switch (action) {
|
| + case extensions::api::downloads::FILENAME_CONFLICT_ACTION_NONE:
|
| + case extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY:
|
| + return DownloadPathReservationTracker::UNIQUIFY;
|
| + case extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE:
|
| + return DownloadPathReservationTracker::OVERWRITE;
|
| + case extensions::api::downloads::FILENAME_CONFLICT_ACTION_PROMPT:
|
| + return DownloadPathReservationTracker::PROMPT;
|
| + }
|
| + NOTREACHED();
|
| + return DownloadPathReservationTracker::UNIQUIFY;
|
| +}
|
| +
|
| class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
|
| public:
|
| static ExtensionDownloadsEventRouterData* Get(DownloadItem* download_item) {
|
| @@ -532,6 +563,8 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
|
| : updated_(0),
|
| changed_fired_(0),
|
| json_(json_item.Pass()),
|
| + creator_conflict_action_(
|
| + extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY),
|
| determined_conflict_action_(
|
| extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY) {
|
| download_item->SetUserData(kKey, this);
|
| @@ -557,6 +590,10 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
|
| const ExtensionDownloadsEventRouter::FilenameChangedCallback& change) {
|
| filename_no_change_ = no_change;
|
| filename_change_ = change;
|
| + determined_filename_ = creator_suggested_filename_;
|
| + determined_conflict_action_ = creator_conflict_action_;
|
| + // determiner_.install_time should default to 0 so that creator suggestions
|
| + // should be lower priority than any actual onDeterminingFilename listeners.
|
| }
|
|
|
| void ClearPendingDeterminers() {
|
| @@ -604,6 +641,28 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
|
| return false;
|
| }
|
|
|
| + void CreatorSuggestedFilename(
|
| + const base::FilePath& filename,
|
| + extensions::api::downloads::FilenameConflictAction conflict_action) {
|
| + creator_suggested_filename_ = filename;
|
| + creator_conflict_action_ = conflict_action;
|
| + }
|
| +
|
| + base::FilePath creator_suggested_filename() const {
|
| + return creator_suggested_filename_;
|
| + }
|
| +
|
| + extensions::api::downloads::FilenameConflictAction
|
| + creator_conflict_action() const {
|
| + return creator_conflict_action_;
|
| + }
|
| +
|
| + void ResetCreatorSuggestion() {
|
| + creator_suggested_filename_.clear();
|
| + creator_conflict_action_ =
|
| + extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY;
|
| + }
|
| +
|
| // Returns false if this |extension_id| was not expected or if this
|
| // |extension_id| has already reported. The caller is responsible for
|
| // validating |filename|.
|
| @@ -665,22 +724,15 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
|
| filename_no_change_.Run();
|
| } else {
|
| if (!filename_change_.is_null()) {
|
| - DownloadPathReservationTracker::FilenameConflictAction conflict_action =
|
| - DownloadPathReservationTracker::UNIQUIFY;
|
| - if (determined_conflict_action_ ==
|
| - extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE)
|
| - conflict_action = DownloadPathReservationTracker::OVERWRITE;
|
| - if (determined_conflict_action_ ==
|
| - extensions::api::downloads::FILENAME_CONFLICT_ACTION_PROMPT)
|
| - conflict_action = DownloadPathReservationTracker::PROMPT;
|
| - filename_change_.Run(determined_filename_, conflict_action);
|
| + filename_change_.Run(determined_filename_, ConvertConflictAction(
|
| + determined_conflict_action_));
|
| }
|
| }
|
| // Don't clear determiners_ immediately in case there's a second listener
|
| // for one of the extensions, so that DetermineFilename can return
|
| - // kTooManyListenersError. After a few seconds, DetermineFilename will
|
| - // return kInvalidOperationError instead of kTooManyListenersError so that
|
| - // determiners_ doesn't keep hogging memory.
|
| + // kTooManyListeners. After a few seconds, DetermineFilename will return
|
| + // kUnexpectedDeterminer instead of kTooManyListeners so that determiners_
|
| + // doesn't keep hogging memory.
|
| weak_ptr_factory_.reset(
|
| new base::WeakPtrFactory<ExtensionDownloadsEventRouterData>(this));
|
| base::MessageLoopForUI::current()->PostDelayedTask(
|
| @@ -699,6 +751,9 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
|
|
|
| DeterminerInfoVector determiners_;
|
|
|
| + base::FilePath creator_suggested_filename_;
|
| + extensions::api::downloads::FilenameConflictAction
|
| + creator_conflict_action_;
|
| base::FilePath determined_filename_;
|
| extensions::api::downloads::FilenameConflictAction
|
| determined_conflict_action_;
|
| @@ -796,6 +851,35 @@ void OnDeterminingFilenameWillDispatchCallback(
|
| data->AddPendingDeterminer(extension->id(), installed);
|
| }
|
|
|
| +bool Fault(bool error,
|
| + const char* message_in,
|
| + std::string* message_out) {
|
| + if (!error)
|
| + return false;
|
| + *message_out = message_in;
|
| + return true;
|
| +}
|
| +
|
| +bool InvalidId(DownloadItem* valid_item, std::string* message_out) {
|
| + return Fault(!valid_item, errors::kInvalidId, message_out);
|
| +}
|
| +
|
| +bool IsDownloadDeltaField(const std::string& field) {
|
| + return ((field == kUrlKey) ||
|
| + (field == kFilenameKey) ||
|
| + (field == kDangerKey) ||
|
| + (field == kMimeKey) ||
|
| + (field == kStartTimeKey) ||
|
| + (field == kEndTimeKey) ||
|
| + (field == kStateKey) ||
|
| + (field == kCanResumeKey) ||
|
| + (field == kPausedKey) ||
|
| + (field == kErrorKey) ||
|
| + (field == kTotalBytesKey) ||
|
| + (field == kFileSizeKey) ||
|
| + (field == kExistsKey));
|
| +}
|
| +
|
| } // namespace
|
|
|
| DownloadsDownloadFunction::DownloadsDownloadFunction() {}
|
| @@ -808,10 +892,8 @@ bool DownloadsDownloadFunction::RunImpl() {
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| const extensions::api::downloads::DownloadOptions& options = params->options;
|
| GURL download_url(options.url);
|
| - if (!download_url.is_valid()) {
|
| - error_ = download_extension_errors::kInvalidURLError;
|
| + if (Fault(!download_url.is_valid(), errors::kInvalidURL, &error_))
|
| return false;
|
| - }
|
|
|
| Profile* current_profile = profile();
|
| if (include_incognito() && profile()->HasOffTheRecordProfile())
|
| @@ -824,28 +906,24 @@ bool DownloadsDownloadFunction::RunImpl() {
|
| render_view_host()->GetRoutingID(),
|
| current_profile->GetResourceContext()));
|
|
|
| + base::FilePath creator_suggested_filename;
|
| if (options.filename.get()) {
|
| - // TODO(benjhayden): Make json_schema_compiler generate string16s instead of
|
| - // std::strings. Can't get filename16 from options.ToValue() because that
|
| - // converts it from std::string.
|
| +#if defined(OS_WIN)
|
| + // Can't get filename16 from options.ToValue() because that converts it from
|
| + // std::string.
|
| base::DictionaryValue* options_value = NULL;
|
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options_value));
|
| - string16 filename16;
|
| + base::string16 filename16;
|
| EXTENSION_FUNCTION_VALIDATE(options_value->GetString(
|
| kFilenameKey, &filename16));
|
| -#if defined(OS_WIN)
|
| - base::FilePath file_path(filename16);
|
| + creator_suggested_filename = base::FilePath(filename16);
|
| #elif defined(OS_POSIX)
|
| - base::FilePath file_path(*options.filename.get());
|
| + creator_suggested_filename = base::FilePath(*options.filename.get());
|
| #endif
|
| - if (!net::IsSafePortableBasename(file_path) ||
|
| - (file_path.DirName().value() != base::FilePath::kCurrentDirectory)) {
|
| - error_ = download_extension_errors::kInvalidFilenameError;
|
| + if (!net::IsSafePortableRelativePath(creator_suggested_filename)) {
|
| + error_ = errors::kInvalidFilename;
|
| return false;
|
| }
|
| - // TODO(benjhayden) Ensure that this filename is interpreted as a path
|
| - // relative to the default downloads directory without allowing '..'.
|
| - download_params->set_suggested_name(filename16);
|
| }
|
|
|
| if (options.save_as.get())
|
| @@ -859,7 +937,7 @@ bool DownloadsDownloadFunction::RunImpl() {
|
| ++iter) {
|
| const HeaderNameValuePair& name_value = **iter;
|
| if (!net::HttpUtil::IsSafeHeader(name_value.name)) {
|
| - error_ = download_extension_errors::kGenericError;
|
| + error_ = errors::kInvalidHeader;
|
| return false;
|
| }
|
| download_params->add_request_header(name_value.name, name_value.value);
|
| @@ -873,24 +951,40 @@ bool DownloadsDownloadFunction::RunImpl() {
|
| if (options.body.get())
|
| download_params->set_post_body(*options.body.get());
|
| download_params->set_callback(base::Bind(
|
| - &DownloadsDownloadFunction::OnStarted, this));
|
| + &DownloadsDownloadFunction::OnStarted, this,
|
| + creator_suggested_filename, options.conflict_action));
|
| // Prevent login prompts for 401/407 responses.
|
| download_params->set_load_flags(net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
|
|
|
| DownloadManager* manager = BrowserContext::GetDownloadManager(
|
| current_profile);
|
| manager->DownloadUrl(download_params.Pass());
|
| + download_util::RecordDownloadSource(download_util::INITIATED_BY_EXTENSION);
|
| RecordApiFunctions(DOWNLOADS_FUNCTION_DOWNLOAD);
|
| return true;
|
| }
|
|
|
| void DownloadsDownloadFunction::OnStarted(
|
| - DownloadItem* item, net::Error error) {
|
| + const base::FilePath& creator_suggested_filename,
|
| + extensions::api::downloads::FilenameConflictAction creator_conflict_action,
|
| + DownloadItem* item,
|
| + net::Error error) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| VLOG(1) << __FUNCTION__ << " " << item << " " << error;
|
| if (item) {
|
| DCHECK_EQ(net::OK, error);
|
| SetResult(base::Value::CreateIntegerValue(item->GetId()));
|
| + if (!creator_suggested_filename.empty()) {
|
| + ExtensionDownloadsEventRouterData* data =
|
| + ExtensionDownloadsEventRouterData::Get(item);
|
| + if (!data) {
|
| + data = new ExtensionDownloadsEventRouterData(
|
| + item,
|
| + scoped_ptr<base::DictionaryValue>(new base::DictionaryValue()));
|
| + }
|
| + data->CreatorSuggestedFilename(
|
| + creator_suggested_filename, creator_conflict_action);
|
| + }
|
| } else {
|
| DCHECK_NE(net::OK, error);
|
| error_ = net::ErrorToString(error);
|
| @@ -944,20 +1038,17 @@ bool DownloadsPauseFunction::RunImpl() {
|
| scoped_ptr<extensions::api::downloads::Pause::Params> params(
|
| extensions::api::downloads::Pause::Params::Create(*args_));
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| - DownloadItem* download_item = GetDownloadIfInProgress(
|
| + DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| - if (download_item == NULL) {
|
| - // 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 the item is already paused, this is a no-op and the
|
| - // operation will silently succeed.
|
| - download_item->Pause();
|
| - }
|
| - if (error_.empty())
|
| - RecordApiFunctions(DOWNLOADS_FUNCTION_PAUSE);
|
| - return error_.empty();
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
|
| + errors::kNotInProgress, &error_))
|
| + return false;
|
| + // If the item is already paused, this is a no-op and the operation will
|
| + // silently succeed.
|
| + download_item->Pause();
|
| + RecordApiFunctions(DOWNLOADS_FUNCTION_PAUSE);
|
| + return true;
|
| }
|
|
|
| DownloadsResumeFunction::DownloadsResumeFunction() {}
|
| @@ -968,20 +1059,17 @@ bool DownloadsResumeFunction::RunImpl() {
|
| scoped_ptr<extensions::api::downloads::Resume::Params> params(
|
| extensions::api::downloads::Resume::Params::Create(*args_));
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| - DownloadItem* download_item = GetDownloadIfInProgress(
|
| + DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| - if (download_item == NULL) {
|
| - // 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 {
|
| - // Note that if the item isn't paused, this will be a no-op, and
|
| - // the extension call will seem successful.
|
| - download_item->Resume();
|
| - }
|
| - if (error_.empty())
|
| - RecordApiFunctions(DOWNLOADS_FUNCTION_RESUME);
|
| - return error_.empty();
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(download_item->IsPaused() && !download_item->CanResume(),
|
| + errors::kNotResumable, &error_))
|
| + return false;
|
| + // Note that if the item isn't paused, this will be a no-op, and the extension
|
| + // call will seem successful.
|
| + download_item->Resume();
|
| + RecordApiFunctions(DOWNLOADS_FUNCTION_RESUME);
|
| + return true;
|
| }
|
|
|
| DownloadsCancelFunction::DownloadsCancelFunction() {}
|
| @@ -992,9 +1080,10 @@ bool DownloadsCancelFunction::RunImpl() {
|
| scoped_ptr<extensions::api::downloads::Resume::Params> params(
|
| extensions::api::downloads::Resume::Params::Create(*args_));
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| - DownloadItem* download_item = GetDownloadIfInProgress(
|
| + DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| - if (download_item != NULL)
|
| + if (download_item &&
|
| + (download_item->GetState() == DownloadItem::IN_PROGRESS))
|
| 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, it's not a failure.
|
| @@ -1032,6 +1121,52 @@ bool DownloadsEraseFunction::RunImpl() {
|
| return true;
|
| }
|
|
|
| +DownloadsRemoveFileFunction::DownloadsRemoveFileFunction() {}
|
| +
|
| +DownloadsRemoveFileFunction::~DownloadsRemoveFileFunction() {
|
| + if (item_) {
|
| + item_->RemoveObserver(this);
|
| + item_ = NULL;
|
| + }
|
| +}
|
| +
|
| +bool DownloadsRemoveFileFunction::RunImpl() {
|
| + scoped_ptr<extensions::api::downloads::RemoveFile::Params> params(
|
| + extensions::api::downloads::RemoveFile::Params::Create(*args_));
|
| + EXTENSION_FUNCTION_VALIDATE(params.get());
|
| + DownloadItem* download_item = GetDownload(
|
| + profile(), include_incognito(), params->download_id);
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault((download_item->GetState() != DownloadItem::COMPLETE),
|
| + errors::kNotComplete, &error_) ||
|
| + Fault(download_item->GetFileExternallyRemoved(),
|
| + errors::kFileAlreadyDeleted, &error_))
|
| + return false;
|
| + item_ = download_item;
|
| + item_->AddObserver(this);
|
| + RecordApiFunctions(DOWNLOADS_FUNCTION_REMOVE_FILE);
|
| + download_item->DeleteFile();
|
| + return true;
|
| +}
|
| +
|
| +void DownloadsRemoveFileFunction::OnDownloadUpdated(DownloadItem* download) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + DCHECK_EQ(item_, download);
|
| + if (!item_->GetFileExternallyRemoved())
|
| + return;
|
| + item_->RemoveObserver(this);
|
| + item_ = NULL;
|
| + SendResponse(true);
|
| +}
|
| +
|
| +void DownloadsRemoveFileFunction::OnDownloadDestroyed(DownloadItem* download) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + DCHECK_EQ(item_, download);
|
| + item_->RemoveObserver(this);
|
| + item_ = NULL;
|
| + SendResponse(true);
|
| +}
|
| +
|
| DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() {}
|
|
|
| DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {}
|
| @@ -1040,16 +1175,16 @@ bool DownloadsAcceptDangerFunction::RunImpl() {
|
| scoped_ptr<extensions::api::downloads::AcceptDanger::Params> params(
|
| extensions::api::downloads::AcceptDanger::Params::Create(*args_));
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| - DownloadItem* download_item = GetDownloadIfInProgress(
|
| + DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| content::WebContents* web_contents =
|
| dispatcher()->delegate()->GetVisibleWebContents();
|
| - if (!download_item ||
|
| - !download_item->IsDangerous() ||
|
| - !web_contents) {
|
| - error_ = download_extension_errors::kInvalidOperationError;
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
|
| + errors::kNotInProgress, &error_) ||
|
| + Fault(!download_item->IsDangerous(), errors::kNotDangerous, &error_) ||
|
| + Fault(!web_contents, errors::kInvisibleContext, &error_))
|
| return false;
|
| - }
|
| RecordApiFunctions(DOWNLOADS_FUNCTION_ACCEPT_DANGER);
|
| // DownloadDangerPrompt displays a modal dialog using native widgets that the
|
| // user must either accept or cancel. It cannot be scripted.
|
| @@ -1058,20 +1193,29 @@ bool DownloadsAcceptDangerFunction::RunImpl() {
|
| web_contents,
|
| true,
|
| base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback,
|
| - this, true, params->download_id),
|
| - base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback,
|
| - this, false, params->download_id));
|
| + this, params->download_id));
|
| // DownloadDangerPrompt deletes itself
|
| return true;
|
| }
|
|
|
| void DownloadsAcceptDangerFunction::DangerPromptCallback(
|
| - bool accept, int download_id) {
|
| - if (accept) {
|
| - DownloadItem* download_item = GetDownloadIfInProgress(
|
| - profile(), include_incognito(), download_id);
|
| - if (download_item)
|
| + int download_id, DownloadDangerPrompt::Action action) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + DownloadItem* download_item = GetDownload(
|
| + profile(), include_incognito(), download_id);
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
|
| + errors::kNotInProgress, &error_))
|
| + return;
|
| + switch (action) {
|
| + case DownloadDangerPrompt::ACCEPT:
|
| download_item->ValidateDangerousDownload();
|
| + break;
|
| + case DownloadDangerPrompt::CANCEL:
|
| + download_item->Remove();
|
| + break;
|
| + case DownloadDangerPrompt::DISMISS:
|
| + break;
|
| }
|
| SendResponse(error_.empty());
|
| }
|
| @@ -1086,15 +1230,27 @@ bool DownloadsShowFunction::RunImpl() {
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| - if (!download_item) {
|
| - error_ = download_extension_errors::kInvalidOperationError;
|
| + if (InvalidId(download_item, &error_))
|
| return false;
|
| - }
|
| download_item->ShowDownloadInShell();
|
| RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW);
|
| return true;
|
| }
|
|
|
| +DownloadsShowDefaultFolderFunction::DownloadsShowDefaultFolderFunction() {}
|
| +
|
| +DownloadsShowDefaultFolderFunction::~DownloadsShowDefaultFolderFunction() {}
|
| +
|
| +bool DownloadsShowDefaultFolderFunction::RunImpl() {
|
| + DownloadManager* manager = NULL;
|
| + DownloadManager* incognito_manager = NULL;
|
| + GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
|
| + platform_util::OpenItem(DownloadPrefs::FromDownloadManager(
|
| + manager)->DownloadPath());
|
| + RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER);
|
| + return true;
|
| +}
|
| +
|
| DownloadsOpenFunction::DownloadsOpenFunction() {}
|
|
|
| DownloadsOpenFunction::~DownloadsOpenFunction() {}
|
| @@ -1105,12 +1261,13 @@ bool DownloadsOpenFunction::RunImpl() {
|
| EXTENSION_FUNCTION_VALIDATE(params.get());
|
| DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| - if (!download_item || download_item->GetState() != DownloadItem::COMPLETE ||
|
| - !GetExtension()->HasAPIPermission(
|
| - extensions::APIPermission::kDownloadsOpen)) {
|
| - error_ = download_extension_errors::kInvalidOperationError;
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(download_item->GetState() != DownloadItem::COMPLETE,
|
| + errors::kNotComplete, &error_) ||
|
| + Fault(!GetExtension()->HasAPIPermission(
|
| + extensions::APIPermission::kDownloadsOpen),
|
| + errors::kOpenPermission, &error_))
|
| return false;
|
| - }
|
| download_item->OpenDownload();
|
| RecordApiFunctions(DOWNLOADS_FUNCTION_OPEN);
|
| return true;
|
| @@ -1128,10 +1285,9 @@ bool DownloadsDragFunction::RunImpl() {
|
| profile(), include_incognito(), params->download_id);
|
| content::WebContents* web_contents =
|
| dispatcher()->delegate()->GetVisibleWebContents();
|
| - if (!download_item || !web_contents) {
|
| - error_ = download_extension_errors::kInvalidOperationError;
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(!web_contents, errors::kInvisibleContext, &error_))
|
| return false;
|
| - }
|
| RecordApiFunctions(DOWNLOADS_FUNCTION_DRAG);
|
| gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath(
|
| download_item->GetTargetFilePath(), IconLoader::NORMAL);
|
| @@ -1168,10 +1324,10 @@ bool DownloadsGetFileIconFunction::RunImpl() {
|
| icon_size = *options->size.get();
|
| DownloadItem* download_item = GetDownload(
|
| profile(), include_incognito(), params->download_id);
|
| - if (!download_item || download_item->GetTargetFilePath().empty()) {
|
| - error_ = download_extension_errors::kInvalidOperationError;
|
| + if (InvalidId(download_item, &error_) ||
|
| + Fault(download_item->GetTargetFilePath().empty(),
|
| + errors::kEmptyFile, &error_))
|
| return false;
|
| - }
|
| // In-progress downloads return the intermediate filename for GetFullPath()
|
| // which doesn't have the final extension. Therefore a good file icon can't be
|
| // found, so use GetTargetFilePath() instead.
|
| @@ -1186,13 +1342,13 @@ bool DownloadsGetFileIconFunction::RunImpl() {
|
|
|
| void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| - if (url.empty()) {
|
| - error_ = download_extension_errors::kIconNotFoundError;
|
| - } else {
|
| - RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON);
|
| - SetResult(base::Value::CreateStringValue(url));
|
| + if (Fault(url.empty(), errors::kIconNotFound, &error_)) {
|
| + SendResponse(false);
|
| + return;
|
| }
|
| - SendResponse(error_.empty());
|
| + RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON);
|
| + SetResult(base::Value::CreateStringValue(url));
|
| + SendResponse(true);
|
| }
|
|
|
| ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(
|
| @@ -1267,7 +1423,14 @@ void ExtensionDownloadsEventRouter::OnDeterminingFilename(
|
| json);
|
| if (!any_determiners) {
|
| data->ClearPendingDeterminers();
|
| - no_change.Run();
|
| + if (!data->creator_suggested_filename().empty()) {
|
| + change.Run(data->creator_suggested_filename(),
|
| + ConvertConflictAction(data->creator_conflict_action()));
|
| + // If all listeners are removed, don't keep |data| around.
|
| + data->ResetCreatorSuggestion();
|
| + } else {
|
| + no_change.Run();
|
| + }
|
| }
|
| }
|
|
|
| @@ -1281,28 +1444,19 @@ bool ExtensionDownloadsEventRouter::DetermineFilename(
|
| std::string* error) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| DownloadItem* item = GetDownload(profile, include_incognito, download_id);
|
| - if (!item) {
|
| - *error = download_extension_errors::kInvalidOperationError;
|
| - return false;
|
| - }
|
| ExtensionDownloadsEventRouterData* data =
|
| - ExtensionDownloadsEventRouterData::Get(item);
|
| - if (!data) {
|
| - *error = download_extension_errors::kInvalidOperationError;
|
| - return false;
|
| - }
|
| + item ? ExtensionDownloadsEventRouterData::Get(item) : NULL;
|
| // maxListeners=1 in downloads.idl and suggestCallback in
|
| // downloads_custom_bindings.js should prevent duplicate DeterminerCallback
|
| // calls from the same renderer, but an extension may have more than one
|
| // renderer, so don't DCHECK(!reported).
|
| - if (data->DeterminerAlreadyReported(ext_id)) {
|
| - *error = download_extension_errors::kTooManyListenersError;
|
| + if (InvalidId(item, error) ||
|
| + Fault(item->GetState() != DownloadItem::IN_PROGRESS,
|
| + errors::kNotInProgress, error) ||
|
| + Fault(!data, errors::kUnexpectedDeterminer, error) ||
|
| + Fault(data->DeterminerAlreadyReported(ext_id),
|
| + errors::kTooManyListeners, error))
|
| return false;
|
| - }
|
| - if (item->GetState() != DownloadItem::IN_PROGRESS) {
|
| - *error = download_extension_errors::kInvalidOperationError;
|
| - return false;
|
| - }
|
| base::FilePath::StringType filename_str(const_filename.value());
|
| // Allow windows-style directory separators on all platforms.
|
| std::replace(filename_str.begin(), filename_str.end(),
|
| @@ -1311,17 +1465,13 @@ bool ExtensionDownloadsEventRouter::DetermineFilename(
|
| bool valid_filename = net::IsSafePortableRelativePath(filename);
|
| filename = (valid_filename ? filename.NormalizePathSeparators() :
|
| base::FilePath());
|
| - if (!data->DeterminerCallback(ext_id, filename, conflict_action)) {
|
| - // Nobody expects this ext_id!
|
| - *error = download_extension_errors::kInvalidOperationError;
|
| + // If the invalid filename check is moved to before DeterminerCallback(), then
|
| + // it will block forever waiting for this ext_id to report.
|
| + if (Fault(!data->DeterminerCallback(ext_id, filename, conflict_action),
|
| + errors::kUnexpectedDeterminer, error) ||
|
| + Fault((!const_filename.empty() && !valid_filename),
|
| + errors::kInvalidFilename, error))
|
| return false;
|
| - }
|
| - if (!const_filename.empty() && !valid_filename) {
|
| - // If this is moved to before DeterminerCallback(), then it will block
|
| - // forever waiting for this ext_id to report.
|
| - *error = download_extension_errors::kInvalidFilenameError;
|
| - return false;
|
| - }
|
| return true;
|
| }
|
|
|
| @@ -1357,7 +1507,8 @@ void ExtensionDownloadsEventRouter::OnListenerRemoved(
|
| // should proceed.
|
| data->DeterminerRemoved(details.extension_id);
|
| }
|
| - if (!any_listeners) {
|
| + if (!any_listeners &&
|
| + data->creator_suggested_filename().empty()) {
|
| ExtensionDownloadsEventRouterData::Remove(*iter);
|
| }
|
| }
|
| @@ -1427,7 +1578,7 @@ void ExtensionDownloadsEventRouter::OnDownloadUpdated(
|
| for (base::DictionaryValue::Iterator iter(*new_json.get());
|
| !iter.IsAtEnd(); iter.Advance()) {
|
| new_fields.insert(iter.key());
|
| - if (iter.key() != kBytesReceivedKey) {
|
| + if (IsDownloadDeltaField(iter.key())) {
|
| const base::Value* old_value = NULL;
|
| if (!data->json().HasKey(iter.key()) ||
|
| (data->json().Get(iter.key(), &old_value) &&
|
| @@ -1444,7 +1595,9 @@ void ExtensionDownloadsEventRouter::OnDownloadUpdated(
|
| // difference in |delta|.
|
| for (base::DictionaryValue::Iterator iter(data->json());
|
| !iter.IsAtEnd(); iter.Advance()) {
|
| - if (new_fields.find(iter.key()) == new_fields.end()) {
|
| + if ((new_fields.find(iter.key()) == new_fields.end()) &&
|
| + IsDownloadDeltaField(iter.key())) {
|
| + // estimatedEndTime disappears after completion, but bytesReceived stays.
|
| delta->Set(iter.key() + ".previous", iter.value().DeepCopy());
|
| changed = true;
|
| }
|
|
|