diff options
Diffstat (limited to 'chrome/browser/extensions/api/downloads/downloads_api.cc')
-rw-r--r-- | chrome/browser/extensions/api/downloads/downloads_api.cc | 489 |
1 files changed, 321 insertions, 168 deletions
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc index 7de85f4..0bfa228 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; } |