blob: 8e810c0f13c12f85c766d8d045c6ea9c29a9843a [file] [log] [blame]
// 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/extensions/api/downloads/downloads_api.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.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_stats.h"
#include "chrome/browser/download/drag_download_item.h"
#include "chrome/browser/icon_loader.h"
#include "chrome/browser/icon_manager.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/extensions/api/downloads.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_save_info.h"
#include "content/public/browser/download_url_parameters.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/warning_service.h"
#include "extensions/common/permissions/permissions_data.h"
#include "net/base/filename_util.h"
#include "net/base/load_flags.h"
#include "net/http/http_util.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/image/image_skia.h"
using content::BrowserContext;
using content::BrowserThread;
using content::DownloadItem;
using content::DownloadManager;
namespace download_extension_errors {
const char kEmptyFile[] = "Filename not yet determined";
const char kFileAlreadyDeleted[] = "Download file already deleted";
const char kFileNotRemoved[] = "Unable to remove file";
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 kInvalidHeaderName[] = "Invalid request header name";
const char kInvalidHeaderUnsafe[] = "Unsafe request header name";
const char kInvalidHeaderValue[] = "Invalid request header value";
const char kInvalidId[] = "Invalid downloadId";
const char kInvalidOrderBy[] = "Invalid orderBy field";
const char kInvalidQueryLimit[] = "Invalid query limit";
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 kShelfDisabled[] = "Another extension has disabled the shelf";
const char kShelfPermission[] = "downloads.setShelfEnabled requires the "
"\"downloads.shelf\" permission";
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";
const char kUserGesture[] = "User gesture required";
} // namespace download_extension_errors
namespace errors = download_extension_errors;
namespace extensions {
namespace {
namespace downloads = api::downloads;
// Default icon size for getFileIcon() in pixels.
const int kDefaultIconSize = 32;
// Parameter keys
const char kByExtensionIdKey[] = "byExtensionId";
const char kByExtensionNameKey[] = "byExtensionName";
const char kBytesReceivedKey[] = "bytesReceived";
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 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 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";
const char kStateComplete[] = "complete";
const char kStateInProgress[] = "in_progress";
const char kStateInterrupted[] = "interrupted";
const char kStateKey[] = "state";
const char kTotalBytesGreaterKey[] = "totalBytesGreater";
const char kTotalBytesKey[] = "totalBytes";
const char kTotalBytesLessKey[] = "totalBytesLess";
const char kUrlKey[] = "url";
const char kUrlRegexKey[] = "urlRegex";
// Note: Any change to the danger type strings, should be accompanied by a
// corresponding change to downloads.json.
const char* const kDangerStrings[] = {
kDangerSafe,
kDangerFile,
kDangerUrl,
kDangerContent,
kDangerSafe,
kDangerUncommon,
kDangerAccepted,
kDangerHost,
kDangerUnwanted
};
static_assert(arraysize(kDangerStrings) == content::DOWNLOAD_DANGER_TYPE_MAX,
"kDangerStrings should have DOWNLOAD_DANGER_TYPE_MAX elements");
// Note: Any change to the state strings, should be accompanied by a
// corresponding change to downloads.json.
const char* const kStateStrings[] = {
kStateInProgress,
kStateComplete,
kStateInterrupted,
kStateInterrupted,
};
static_assert(arraysize(kStateStrings) == DownloadItem::MAX_DOWNLOAD_STATE,
"kStateStrings should have MAX_DOWNLOAD_STATE elements");
const char* DangerString(content::DownloadDangerType danger) {
DCHECK(danger >= 0);
DCHECK(danger < static_cast<content::DownloadDangerType>(
arraysize(kDangerStrings)));
if (danger < 0 || danger >= static_cast<content::DownloadDangerType>(
arraysize(kDangerStrings)))
return "";
return kDangerStrings[danger];
}
content::DownloadDangerType DangerEnumFromString(const std::string& danger) {
for (size_t i = 0; i < arraysize(kDangerStrings); ++i) {
if (danger == kDangerStrings[i])
return static_cast<content::DownloadDangerType>(i);
}
return content::DOWNLOAD_DANGER_TYPE_MAX;
}
const char* StateString(DownloadItem::DownloadState state) {
DCHECK(state >= 0);
DCHECK(state < static_cast<DownloadItem::DownloadState>(
arraysize(kStateStrings)));
if (state < 0 || state >= static_cast<DownloadItem::DownloadState>(
arraysize(kStateStrings)))
return "";
return kStateStrings[state];
}
DownloadItem::DownloadState StateEnumFromString(const std::string& state) {
for (size_t i = 0; i < arraysize(kStateStrings); ++i) {
if ((kStateStrings[i] != NULL) && (state == kStateStrings[i]))
return static_cast<DownloadItem::DownloadState>(i);
}
return DownloadItem::MAX_DOWNLOAD_STATE;
}
std::string TimeToISO8601(const base::Time& t) {
base::Time::Exploded exploded;
t.UTCExplode(&exploded);
return base::StringPrintf(
"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month,
exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
exploded.millisecond);
}
std::unique_ptr<base::DictionaryValue> DownloadItemToJSON(
DownloadItem* download_item,
Profile* profile) {
base::DictionaryValue* json = new base::DictionaryValue();
json->SetBoolean(kExistsKey, !download_item->GetFileExternallyRemoved());
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()));
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->SetDouble(kBytesReceivedKey, download_item->GetReceivedBytes());
json->SetDouble(kTotalBytesKey, download_item->GetTotalBytes());
json->SetBoolean(kIncognitoKey, profile->IsOffTheRecord());
if (download_item->GetState() == DownloadItem::INTERRUPTED) {
json->SetString(kErrorKey,
content::DownloadInterruptReasonToString(
download_item->GetLastReason()));
} else if (download_item->GetState() == DownloadItem::CANCELLED) {
json->SetString(kErrorKey,
content::DownloadInterruptReasonToString(
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));
}
DownloadedByExtension* by_ext = DownloadedByExtension::Get(download_item);
if (by_ext) {
json->SetString(kByExtensionIdKey, by_ext->id());
json->SetString(kByExtensionNameKey, by_ext->name());
// Lookup the extension's current name() in case the user changed their
// language. This won't work if the extension was uninstalled, so the name
// might be the wrong language.
const Extension* extension =
ExtensionRegistry::Get(profile)
->GetExtensionById(by_ext->id(), ExtensionRegistry::EVERYTHING);
if (extension)
json->SetString(kByExtensionNameKey, extension->name());
}
// TODO(benjhayden): Implement fileSize.
json->SetDouble(kFileSizeKey, download_item->GetTotalBytes());
return std::unique_ptr<base::DictionaryValue>(json);
}
class DownloadFileIconExtractorImpl : public DownloadFileIconExtractor {
public:
DownloadFileIconExtractorImpl() {}
~DownloadFileIconExtractorImpl() override {}
bool ExtractIconURLForPath(const base::FilePath& path,
float scale,
IconLoader::IconSize icon_size,
IconURLCallback callback) override;
private:
void OnIconLoadComplete(
float scale, const IconURLCallback& callback, gfx::Image* icon);
base::CancelableTaskTracker cancelable_task_tracker_;
};
bool DownloadFileIconExtractorImpl::ExtractIconURLForPath(
const base::FilePath& path,
float scale,
IconLoader::IconSize icon_size,
IconURLCallback 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, always call LoadIcon instead of attempting a LookupIcon.
im->LoadIcon(path,
icon_size,
base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete,
base::Unretained(this), scale, callback),
&cancelable_task_tracker_);
return true;
}
void DownloadFileIconExtractorImpl::OnIconLoadComplete(
float scale, const IconURLCallback& callback, gfx::Image* icon) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
callback.Run(!icon ? std::string() : webui::GetBitmapDataUrl(
icon->ToImageSkia()->GetRepresentation(scale).sk_bitmap()));
}
IconLoader::IconSize IconLoaderSizeFromPixelSize(int pixel_size) {
switch (pixel_size) {
case 16: return IconLoader::SMALL;
case 32: return IconLoader::NORMAL;
default:
NOTREACHED();
return IconLoader::NORMAL;
}
}
typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap;
void InitFilterTypeMap(FilterTypeMap* filter_types_ptr) {
FilterTypeMap& filter_types = *filter_types_ptr;
filter_types[kBytesReceivedKey] = DownloadQuery::FILTER_BYTES_RECEIVED;
filter_types[kExistsKey] = DownloadQuery::FILTER_EXISTS;
filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME;
filter_types[kFilenameRegexKey] = DownloadQuery::FILTER_FILENAME_REGEX;
filter_types[kMimeKey] = DownloadQuery::FILTER_MIME;
filter_types[kPausedKey] = DownloadQuery::FILTER_PAUSED;
filter_types[kQueryKey] = DownloadQuery::FILTER_QUERY;
filter_types[kEndedAfterKey] = DownloadQuery::FILTER_ENDED_AFTER;
filter_types[kEndedBeforeKey] = DownloadQuery::FILTER_ENDED_BEFORE;
filter_types[kEndTimeKey] = DownloadQuery::FILTER_END_TIME;
filter_types[kStartedAfterKey] = DownloadQuery::FILTER_STARTED_AFTER;
filter_types[kStartedBeforeKey] = DownloadQuery::FILTER_STARTED_BEFORE;
filter_types[kStartTimeKey] = DownloadQuery::FILTER_START_TIME;
filter_types[kTotalBytesKey] = DownloadQuery::FILTER_TOTAL_BYTES;
filter_types[kTotalBytesGreaterKey] =
DownloadQuery::FILTER_TOTAL_BYTES_GREATER;
filter_types[kTotalBytesLessKey] = DownloadQuery::FILTER_TOTAL_BYTES_LESS;
filter_types[kUrlKey] = DownloadQuery::FILTER_URL;
filter_types[kUrlRegexKey] = DownloadQuery::FILTER_URL_REGEX;
}
typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap;
void InitSortTypeMap(SortTypeMap* sorter_types_ptr) {
SortTypeMap& sorter_types = *sorter_types_ptr;
sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED;
sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER;
sorter_types[kEndTimeKey] = DownloadQuery::SORT_END_TIME;
sorter_types[kExistsKey] = DownloadQuery::SORT_EXISTS;
sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME;
sorter_types[kMimeKey] = DownloadQuery::SORT_MIME;
sorter_types[kPausedKey] = DownloadQuery::SORT_PAUSED;
sorter_types[kStartTimeKey] = DownloadQuery::SORT_START_TIME;
sorter_types[kStateKey] = DownloadQuery::SORT_STATE;
sorter_types[kTotalBytesKey] = DownloadQuery::SORT_TOTAL_BYTES;
sorter_types[kUrlKey] = DownloadQuery::SORT_URL;
}
bool IsNotTemporaryDownloadFilter(const DownloadItem& download_item) {
return !download_item.IsTemporary();
}
// Set |manager| to the on-record DownloadManager, and |incognito_manager| to
// the off-record DownloadManager if one exists and is requested via
// |include_incognito|. This should work regardless of whether |profile| is
// original or incognito.
void GetManagers(
Profile* profile,
bool include_incognito,
DownloadManager** manager,
DownloadManager** incognito_manager) {
*manager = BrowserContext::GetDownloadManager(profile->GetOriginalProfile());
if (profile->HasOffTheRecordProfile() &&
(include_incognito ||
profile->IsOffTheRecord())) {
*incognito_manager = BrowserContext::GetDownloadManager(
profile->GetOffTheRecordProfile());
} else {
*incognito_manager = NULL;
}
}
DownloadItem* GetDownload(Profile* profile, bool include_incognito, int id) {
DownloadManager* manager = NULL;
DownloadManager* incognito_manager = NULL;
GetManagers(profile, include_incognito, &manager, &incognito_manager);
DownloadItem* download_item = manager->GetDownload(id);
if (!download_item && incognito_manager)
download_item = incognito_manager->GetDownload(id);
return download_item;
}
enum DownloadsFunctionName {
DOWNLOADS_FUNCTION_DOWNLOAD = 0,
DOWNLOADS_FUNCTION_SEARCH = 1,
DOWNLOADS_FUNCTION_PAUSE = 2,
DOWNLOADS_FUNCTION_RESUME = 3,
DOWNLOADS_FUNCTION_CANCEL = 4,
DOWNLOADS_FUNCTION_ERASE = 5,
// 6 unused
DOWNLOADS_FUNCTION_ACCEPT_DANGER = 7,
DOWNLOADS_FUNCTION_SHOW = 8,
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,
DOWNLOADS_FUNCTION_SET_SHELF_ENABLED = 14,
DOWNLOADS_FUNCTION_DETERMINE_FILENAME = 15,
// Insert new values here, not at the beginning.
DOWNLOADS_FUNCTION_LAST
};
void RecordApiFunctions(DownloadsFunctionName function) {
UMA_HISTOGRAM_ENUMERATION("Download.ApiFunctions",
function,
DOWNLOADS_FUNCTION_LAST);
}
void CompileDownloadQueryOrderBy(
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 =
LAZY_INSTANCE_INITIALIZER;
if (sorter_types.Get().empty())
InitSortTypeMap(sorter_types.Pointer());
for (std::vector<std::string>::const_iterator iter = order_by_strs.begin();
iter != order_by_strs.end(); ++iter) {
std::string term_str = *iter;
if (term_str.empty())
continue;
DownloadQuery::SortDirection direction = DownloadQuery::ASCENDING;
if (term_str[0] == '-') {
direction = DownloadQuery::DESCENDING;
term_str = term_str.substr(1);
}
SortTypeMap::const_iterator sorter_type =
sorter_types.Get().find(term_str);
if (sorter_type == sorter_types.Get().end()) {
*error = errors::kInvalidOrderBy;
return;
}
query->AddSorter(sorter_type->second, direction);
}
}
void RunDownloadQuery(
const downloads::DownloadQuery& query_in,
DownloadManager* manager,
DownloadManager* incognito_manager,
std::string* error,
DownloadQuery::DownloadVector* results) {
// TODO(benjhayden): Consider switching from LazyInstance to explicit string
// comparisons.
static base::LazyInstance<FilterTypeMap> filter_types =
LAZY_INSTANCE_INITIALIZER;
if (filter_types.Get().empty())
InitFilterTypeMap(filter_types.Pointer());
DownloadQuery query_out;
size_t limit = 1000;
if (query_in.limit.get()) {
if (*query_in.limit.get() < 0) {
*error = errors::kInvalidQueryLimit;
return;
}
limit = *query_in.limit.get();
}
if (limit > 0) {
query_out.Limit(limit);
}
std::string state_string = downloads::ToString(query_in.state);
if (!state_string.empty()) {
DownloadItem::DownloadState state = StateEnumFromString(state_string);
if (state == DownloadItem::MAX_DOWNLOAD_STATE) {
*error = errors::kInvalidState;
return;
}
query_out.AddFilter(state);
}
std::string danger_string =
downloads::ToString(query_in.danger);
if (!danger_string.empty()) {
content::DownloadDangerType danger_type = DangerEnumFromString(
danger_string);
if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
*error = errors::kInvalidDangerType;
return;
}
query_out.AddFilter(danger_type);
}
if (query_in.order_by.get()) {
CompileDownloadQueryOrderBy(*query_in.order_by.get(), error, &query_out);
if (!error->empty())
return;
}
std::unique_ptr<base::DictionaryValue> query_in_value(query_in.ToValue());
for (base::DictionaryValue::Iterator query_json_field(*query_in_value.get());
!query_json_field.IsAtEnd(); query_json_field.Advance()) {
FilterTypeMap::const_iterator filter_type =
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 = errors::kInvalidFilter;
return;
}
}
}
DownloadQuery::DownloadVector all_items;
if (query_in.id.get()) {
DownloadItem* download_item = manager->GetDownload(*query_in.id.get());
if (!download_item && incognito_manager)
download_item = incognito_manager->GetDownload(*query_in.id.get());
if (download_item)
all_items.push_back(download_item);
} else {
manager->GetAllDownloads(&all_items);
if (incognito_manager)
incognito_manager->GetAllDownloads(&all_items);
}
query_out.AddFilter(base::Bind(&IsNotTemporaryDownloadFilter));
query_out.Search(all_items.begin(), all_items.end(), results);
}
DownloadPathReservationTracker::FilenameConflictAction ConvertConflictAction(
downloads::FilenameConflictAction action) {
switch (action) {
case downloads::FILENAME_CONFLICT_ACTION_NONE:
case downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY:
return DownloadPathReservationTracker::UNIQUIFY;
case downloads::FILENAME_CONFLICT_ACTION_OVERWRITE:
return DownloadPathReservationTracker::OVERWRITE;
case 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) {
base::SupportsUserData::Data* data = download_item->GetUserData(kKey);
return (data == NULL) ? NULL :
static_cast<ExtensionDownloadsEventRouterData*>(data);
}
static void Remove(DownloadItem* download_item) {
download_item->RemoveUserData(kKey);
}
explicit ExtensionDownloadsEventRouterData(
DownloadItem* download_item,
std::unique_ptr<base::DictionaryValue> json_item)
: updated_(0),
changed_fired_(0),
json_(std::move(json_item)),
creator_conflict_action_(downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY),
determined_conflict_action_(
downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
download_item->SetUserData(kKey, this);
}
~ExtensionDownloadsEventRouterData() override {
if (updated_ > 0) {
UMA_HISTOGRAM_PERCENTAGE("Download.OnChanged",
(changed_fired_ * 100 / updated_));
}
}
const base::DictionaryValue& json() const { return *json_.get(); }
void set_json(std::unique_ptr<base::DictionaryValue> json_item) {
json_ = std::move(json_item);
}
void OnItemUpdated() { ++updated_; }
void OnChangedFired() { ++changed_fired_; }
static void SetDetermineFilenameTimeoutSecondsForTesting(int s) {
determine_filename_timeout_s_ = s;
}
void BeginFilenameDetermination(
const base::Closure& no_change,
const ExtensionDownloadsEventRouter::FilenameChangedCallback& change) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ClearPendingDeterminers();
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.
// Ensure that the callback is called within a time limit.
weak_ptr_factory_.reset(
new base::WeakPtrFactory<ExtensionDownloadsEventRouterData>(this));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&ExtensionDownloadsEventRouterData::DetermineFilenameTimeout,
weak_ptr_factory_->GetWeakPtr()),
base::TimeDelta::FromSeconds(determine_filename_timeout_s_));
}
void DetermineFilenameTimeout() {
CallFilenameCallback();
}
void ClearPendingDeterminers() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
determined_filename_.clear();
determined_conflict_action_ =
downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY;
determiner_ = DeterminerInfo();
filename_no_change_ = base::Closure();
filename_change_ = ExtensionDownloadsEventRouter::FilenameChangedCallback();
weak_ptr_factory_.reset();
determiners_.clear();
}
void DeterminerRemoved(const std::string& extension_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (DeterminerInfoVector::iterator iter = determiners_.begin();
iter != determiners_.end();) {
if (iter->extension_id == extension_id) {
iter = determiners_.erase(iter);
} else {
++iter;
}
}
// If we just removed the last unreported determiner, then we need to call a
// callback.
CheckAllDeterminersCalled();
}
void AddPendingDeterminer(const std::string& extension_id,
const base::Time& installed) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (size_t index = 0; index < determiners_.size(); ++index) {
if (determiners_[index].extension_id == extension_id) {
DCHECK(false) << extension_id;
return;
}
}
determiners_.push_back(DeterminerInfo(extension_id, installed));
}
bool DeterminerAlreadyReported(const std::string& extension_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (size_t index = 0; index < determiners_.size(); ++index) {
if (determiners_[index].extension_id == extension_id) {
return determiners_[index].reported;
}
}
return false;
}
void CreatorSuggestedFilename(
const base::FilePath& filename,
downloads::FilenameConflictAction conflict_action) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
creator_suggested_filename_ = filename;
creator_conflict_action_ = conflict_action;
}
base::FilePath creator_suggested_filename() const {
return creator_suggested_filename_;
}
downloads::FilenameConflictAction
creator_conflict_action() const {
return creator_conflict_action_;
}
void ResetCreatorSuggestion() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
creator_suggested_filename_.clear();
creator_conflict_action_ =
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|.
bool DeterminerCallback(
Profile* profile,
const std::string& extension_id,
const base::FilePath& filename,
downloads::FilenameConflictAction conflict_action) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
bool found_info = false;
for (size_t index = 0; index < determiners_.size(); ++index) {
if (determiners_[index].extension_id == extension_id) {
found_info = true;
if (determiners_[index].reported)
return false;
determiners_[index].reported = true;
// Do not use filename if another determiner has already overridden the
// filename and they take precedence. Extensions that were installed
// later take precedence over previous extensions.
if (!filename.empty() ||
(conflict_action != downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY)) {
WarningSet warnings;
std::string winner_extension_id;
ExtensionDownloadsEventRouter::DetermineFilenameInternal(
filename,
conflict_action,
determiners_[index].extension_id,
determiners_[index].install_time,
determiner_.extension_id,
determiner_.install_time,
&winner_extension_id,
&determined_filename_,
&determined_conflict_action_,
&warnings);
if (!warnings.empty())
WarningService::NotifyWarningsOnUI(profile, warnings);
if (winner_extension_id == determiners_[index].extension_id)
determiner_ = determiners_[index];
}
break;
}
}
if (!found_info)
return false;
CheckAllDeterminersCalled();
return true;
}
private:
static int determine_filename_timeout_s_;
struct DeterminerInfo {
DeterminerInfo();
DeterminerInfo(const std::string& e_id,
const base::Time& installed);
~DeterminerInfo();
std::string extension_id;
base::Time install_time;
bool reported;
};
typedef std::vector<DeterminerInfo> DeterminerInfoVector;
static const char kKey[];
// This is safe to call even while not waiting for determiners to call back;
// in that case, the callbacks will be null so they won't be Run.
void CheckAllDeterminersCalled() {
for (DeterminerInfoVector::iterator iter = determiners_.begin();
iter != determiners_.end(); ++iter) {
if (!iter->reported)
return;
}
CallFilenameCallback();
// Don't clear determiners_ immediately in case there's a second listener
// for one of the extensions, so that DetermineFilename can return
// 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::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&ExtensionDownloadsEventRouterData::ClearPendingDeterminers,
weak_ptr_factory_->GetWeakPtr()),
base::TimeDelta::FromSeconds(15));
}
void CallFilenameCallback() {
if (determined_filename_.empty() &&
(determined_conflict_action_ ==
downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY)) {
if (!filename_no_change_.is_null())
filename_no_change_.Run();
} else {
if (!filename_change_.is_null()) {
filename_change_.Run(determined_filename_, ConvertConflictAction(
determined_conflict_action_));
}
}
// Clear the callbacks immediately in case they aren't idempotent.
filename_no_change_ = base::Closure();
filename_change_ = ExtensionDownloadsEventRouter::FilenameChangedCallback();
}
int updated_;
int changed_fired_;
std::unique_ptr<base::DictionaryValue> json_;
base::Closure filename_no_change_;
ExtensionDownloadsEventRouter::FilenameChangedCallback filename_change_;
DeterminerInfoVector determiners_;
base::FilePath creator_suggested_filename_;
downloads::FilenameConflictAction
creator_conflict_action_;
base::FilePath determined_filename_;
downloads::FilenameConflictAction
determined_conflict_action_;
DeterminerInfo determiner_;
std::unique_ptr<base::WeakPtrFactory<ExtensionDownloadsEventRouterData>>
weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ExtensionDownloadsEventRouterData);
};
int ExtensionDownloadsEventRouterData::determine_filename_timeout_s_ = 15;
ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo(
const std::string& e_id,
const base::Time& installed)
: extension_id(e_id),
install_time(installed),
reported(false) {
}
ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo()
: reported(false) {
}
ExtensionDownloadsEventRouterData::DeterminerInfo::~DeterminerInfo() {}
const char ExtensionDownloadsEventRouterData::kKey[] =
"DownloadItem ExtensionDownloadsEventRouterData";
bool OnDeterminingFilenameWillDispatchCallback(
bool* any_determiners,
ExtensionDownloadsEventRouterData* data,
content::BrowserContext* context,
const Extension* extension,
Event* event,
const base::DictionaryValue* listener_filter) {
*any_determiners = true;
base::Time installed =
ExtensionPrefs::Get(context)->GetInstallTime(extension->id());
data->AddPendingDeterminer(extension->id(), installed);
return true;
}
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
const char DownloadedByExtension::kKey[] =
"DownloadItem DownloadedByExtension";
DownloadedByExtension* DownloadedByExtension::Get(
content::DownloadItem* item) {
base::SupportsUserData::Data* data = item->GetUserData(kKey);
return (data == NULL) ? NULL :
static_cast<DownloadedByExtension*>(data);
}
DownloadedByExtension::DownloadedByExtension(
content::DownloadItem* item,
const std::string& id,
const std::string& name)
: id_(id),
name_(name) {
item->SetUserData(kKey, this);
}
DownloadsDownloadFunction::DownloadsDownloadFunction() {}
DownloadsDownloadFunction::~DownloadsDownloadFunction() {}
bool DownloadsDownloadFunction::RunAsync() {
std::unique_ptr<downloads::Download::Params> params(
downloads::Download::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
const downloads::DownloadOptions& options = params->options;
GURL download_url(options.url);
if (Fault(!download_url.is_valid(), errors::kInvalidURL, &error_))
return false;
Profile* current_profile = GetProfile();
if (include_incognito() && GetProfile()->HasOffTheRecordProfile())
current_profile = GetProfile()->GetOffTheRecordProfile();
content::StoragePartition* storage_partition =
BrowserContext::GetStoragePartition(
render_frame_host()->GetProcess()->GetBrowserContext(),
render_frame_host()->GetSiteInstance());
std::unique_ptr<content::DownloadUrlParameters> download_params(
new content::DownloadUrlParameters(
download_url, render_frame_host()->GetProcess()->GetID(),
render_frame_host()->GetRenderViewHost()->GetRoutingID(),
render_frame_host()->GetRoutingID(),
storage_partition->GetURLRequestContext()));
base::FilePath creator_suggested_filename;
if (options.filename.get()) {
#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));
base::string16 filename16;
EXTENSION_FUNCTION_VALIDATE(options_value->GetString(
kFilenameKey, &filename16));
creator_suggested_filename = base::FilePath(filename16);
#elif defined(OS_POSIX)
creator_suggested_filename = base::FilePath(*options.filename.get());
#endif
if (!net::IsSafePortableRelativePath(creator_suggested_filename)) {
error_ = errors::kInvalidFilename;
return false;
}
}
if (options.save_as.get())
download_params->set_prompt(*options.save_as.get());
if (options.headers.get()) {
for (const downloads::HeaderNameValuePair& name_value : *options.headers) {
if (!net::HttpUtil::IsValidHeaderName(name_value.name)) {
error_ = errors::kInvalidHeaderName;
return false;
}
if (!net::HttpUtil::IsSafeHeader(name_value.name)) {
error_ = errors::kInvalidHeaderUnsafe;
return false;
}
if (!net::HttpUtil::IsValidHeaderValue(name_value.value)) {
error_ = errors::kInvalidHeaderValue;
return false;
}
download_params->add_request_header(name_value.name, name_value.value);
}
}
std::string method_string =
downloads::ToString(options.method);
if (!method_string.empty())
download_params->set_method(method_string);
if (options.body.get())
download_params->set_post_body(*options.body.get());
download_params->set_callback(base::Bind(
&DownloadsDownloadFunction::OnStarted, this,
creator_suggested_filename, options.conflict_action));
// Prevent login prompts for 401/407 responses.
download_params->set_do_not_prompt_for_login(true);
DownloadManager* manager = BrowserContext::GetDownloadManager(
current_profile);
manager->DownloadUrl(std::move(download_params));
RecordDownloadSource(DOWNLOAD_INITIATED_BY_EXTENSION);
RecordApiFunctions(DOWNLOADS_FUNCTION_DOWNLOAD);
return true;
}
void DownloadsDownloadFunction::OnStarted(
const base::FilePath& creator_suggested_filename,
downloads::FilenameConflictAction creator_conflict_action,
DownloadItem* item,
content::DownloadInterruptReason interrupt_reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
VLOG(1) << __FUNCTION__ << " " << item << " " << interrupt_reason;
if (item) {
DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
SetResult(base::MakeUnique<base::FundamentalValue>(
static_cast<int>(item->GetId())));
if (!creator_suggested_filename.empty() ||
(creator_conflict_action !=
downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY)) {
ExtensionDownloadsEventRouterData* data =
ExtensionDownloadsEventRouterData::Get(item);
if (!data) {
data = new ExtensionDownloadsEventRouterData(
item, std::unique_ptr<base::DictionaryValue>(
new base::DictionaryValue()));
}
data->CreatorSuggestedFilename(
creator_suggested_filename, creator_conflict_action);
}
new DownloadedByExtension(item, extension()->id(), extension()->name());
item->UpdateObservers();
} else {
DCHECK_NE(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
error_ = content::DownloadInterruptReasonToString(interrupt_reason);
}
SendResponse(error_.empty());
}
DownloadsSearchFunction::DownloadsSearchFunction() {}
DownloadsSearchFunction::~DownloadsSearchFunction() {}
bool DownloadsSearchFunction::RunSync() {
std::unique_ptr<downloads::Search::Params> params(
downloads::Search::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadManager* manager = NULL;
DownloadManager* incognito_manager = NULL;
GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager);
ExtensionDownloadsEventRouter* router =
DownloadServiceFactory::GetForBrowserContext(
manager->GetBrowserContext())->GetExtensionEventRouter();
router->CheckForHistoryFilesRemoval();
if (incognito_manager) {
ExtensionDownloadsEventRouter* incognito_router =
DownloadServiceFactory::GetForBrowserContext(
incognito_manager->GetBrowserContext())->GetExtensionEventRouter();
incognito_router->CheckForHistoryFilesRemoval();
}
DownloadQuery::DownloadVector results;
RunDownloadQuery(params->query,
manager,
incognito_manager,
&error_,
&results);
if (!error_.empty())
return false;
std::unique_ptr<base::ListValue> json_results(new base::ListValue());
for (DownloadManager::DownloadVector::const_iterator it = results.begin();
it != results.end(); ++it) {
DownloadItem* download_item = *it;
uint32_t download_id = download_item->GetId();
bool off_record = ((incognito_manager != NULL) &&
(incognito_manager->GetDownload(download_id) != NULL));
std::unique_ptr<base::DictionaryValue> json_item(DownloadItemToJSON(
*it, off_record ? GetProfile()->GetOffTheRecordProfile()
: GetProfile()->GetOriginalProfile()));
json_results->Append(json_item.release());
}
SetResult(std::move(json_results));
RecordApiFunctions(DOWNLOADS_FUNCTION_SEARCH);
return true;
}
DownloadsPauseFunction::DownloadsPauseFunction() {}
DownloadsPauseFunction::~DownloadsPauseFunction() {}
bool DownloadsPauseFunction::RunSync() {
std::unique_ptr<downloads::Pause::Params> params(
downloads::Pause::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
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() {}
DownloadsResumeFunction::~DownloadsResumeFunction() {}
bool DownloadsResumeFunction::RunSync() {
std::unique_ptr<downloads::Resume::Params> params(
downloads::Resume::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
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() {}
DownloadsCancelFunction::~DownloadsCancelFunction() {}
bool DownloadsCancelFunction::RunSync() {
std::unique_ptr<downloads::Resume::Params> params(
downloads::Resume::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
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.
RecordApiFunctions(DOWNLOADS_FUNCTION_CANCEL);
return true;
}
DownloadsEraseFunction::DownloadsEraseFunction() {}
DownloadsEraseFunction::~DownloadsEraseFunction() {}
bool DownloadsEraseFunction::RunSync() {
std::unique_ptr<downloads::Erase::Params> params(
downloads::Erase::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadManager* manager = NULL;
DownloadManager* incognito_manager = NULL;
GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager);
DownloadQuery::DownloadVector results;
RunDownloadQuery(params->query,
manager,
incognito_manager,
&error_,
&results);
if (!error_.empty())
return false;
std::unique_ptr<base::ListValue> json_results(new base::ListValue());
for (DownloadManager::DownloadVector::const_iterator it = results.begin();
it != results.end(); ++it) {
json_results->AppendInteger(static_cast<int>((*it)->GetId()));
(*it)->Remove();
}
SetResult(std::move(json_results));
RecordApiFunctions(DOWNLOADS_FUNCTION_ERASE);
return true;
}
DownloadsRemoveFileFunction::DownloadsRemoveFileFunction() {
}
DownloadsRemoveFileFunction::~DownloadsRemoveFileFunction() {
}
bool DownloadsRemoveFileFunction::RunAsync() {
std::unique_ptr<downloads::RemoveFile::Params> params(
downloads::RemoveFile::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), 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;
RecordApiFunctions(DOWNLOADS_FUNCTION_REMOVE_FILE);
download_item->DeleteFile(
base::Bind(&DownloadsRemoveFileFunction::Done, this));
return true;
}
void DownloadsRemoveFileFunction::Done(bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!success) {
error_ = errors::kFileNotRemoved;
}
SendResponse(error_.empty());
}
DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() {}
DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {}
DownloadsAcceptDangerFunction::OnPromptCreatedCallback*
DownloadsAcceptDangerFunction::on_prompt_created_ = NULL;
bool DownloadsAcceptDangerFunction::RunAsync() {
std::unique_ptr<downloads::AcceptDanger::Params> params(
downloads::AcceptDanger::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
PromptOrWait(params->download_id, 10);
return true;
}
void DownloadsAcceptDangerFunction::PromptOrWait(int download_id, int retries) {
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), download_id);
content::WebContents* web_contents = dispatcher()->GetVisibleWebContents();
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_)) {
SendResponse(error_.empty());
return;
}
bool visible = platform_util::IsVisible(web_contents->GetNativeView());
if (!visible) {
if (retries > 0) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&DownloadsAcceptDangerFunction::PromptOrWait,
this, download_id, retries - 1),
base::TimeDelta::FromMilliseconds(100));
return;
}
error_ = errors::kInvisibleContext;
SendResponse(error_.empty());
return;
}
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.
DownloadDangerPrompt* prompt = DownloadDangerPrompt::Create(
download_item,
web_contents,
true,
base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback,
this, download_id));
// DownloadDangerPrompt deletes itself
if (on_prompt_created_ && !on_prompt_created_->is_null())
on_prompt_created_->Run(prompt);
SendResponse(error_.empty());
}
void DownloadsAcceptDangerFunction::DangerPromptCallback(
int download_id, DownloadDangerPrompt::Action action) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DownloadItem* download_item =
GetDownload(GetProfile(), 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());
}
DownloadsShowFunction::DownloadsShowFunction() {}
DownloadsShowFunction::~DownloadsShowFunction() {}
bool DownloadsShowFunction::RunAsync() {
std::unique_ptr<downloads::Show::Params> params(
downloads::Show::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
if (InvalidId(download_item, &error_))
return false;
download_item->ShowDownloadInShell();
RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW);
return true;
}
DownloadsShowDefaultFolderFunction::DownloadsShowDefaultFolderFunction() {}
DownloadsShowDefaultFolderFunction::~DownloadsShowDefaultFolderFunction() {}
bool DownloadsShowDefaultFolderFunction::RunAsync() {
DownloadManager* manager = NULL;
DownloadManager* incognito_manager = NULL;
GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager);
platform_util::OpenItem(
GetProfile(), DownloadPrefs::FromDownloadManager(manager)->DownloadPath(),
platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback());
RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER);
return true;
}
DownloadsOpenFunction::DownloadsOpenFunction() {}
DownloadsOpenFunction::~DownloadsOpenFunction() {}
bool DownloadsOpenFunction::RunSync() {
std::unique_ptr<downloads::Open::Params> params(
downloads::Open::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
if (InvalidId(download_item, &error_) ||
Fault(!user_gesture(), errors::kUserGesture, &error_) ||
Fault(download_item->GetState() != DownloadItem::COMPLETE,
errors::kNotComplete,
&error_) ||
Fault(!extension()->permissions_data()->HasAPIPermission(
APIPermission::kDownloadsOpen),
errors::kOpenPermission,
&error_))
return false;
download_item->OpenDownload();
RecordApiFunctions(DOWNLOADS_FUNCTION_OPEN);
return true;
}
DownloadsDragFunction::DownloadsDragFunction() {}
DownloadsDragFunction::~DownloadsDragFunction() {}
bool DownloadsDragFunction::RunAsync() {
std::unique_ptr<downloads::Drag::Params> params(
downloads::Drag::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
content::WebContents* web_contents =
dispatcher()->GetVisibleWebContents();
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);
gfx::NativeView view = web_contents->GetNativeView();
{
// Enable nested tasks during DnD, while |DragDownload()| blocks.
base::MessageLoop::ScopedNestableTaskAllower allow(
base::MessageLoop::current());
DragDownloadItem(download_item, icon, view);
}
return true;
}
DownloadsSetShelfEnabledFunction::DownloadsSetShelfEnabledFunction() {}
DownloadsSetShelfEnabledFunction::~DownloadsSetShelfEnabledFunction() {}
bool DownloadsSetShelfEnabledFunction::RunSync() {
std::unique_ptr<downloads::SetShelfEnabled::Params> params(
downloads::SetShelfEnabled::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (!extension()->permissions_data()->HasAPIPermission(
APIPermission::kDownloadsShelf)) {
error_ = download_extension_errors::kShelfPermission;
return false;
}
RecordApiFunctions(DOWNLOADS_FUNCTION_SET_SHELF_ENABLED);
DownloadManager* manager = NULL;
DownloadManager* incognito_manager = NULL;
GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager);
DownloadService* service = NULL;
DownloadService* incognito_service = NULL;
if (manager) {
service = DownloadServiceFactory::GetForBrowserContext(
manager->GetBrowserContext());
service->GetExtensionEventRouter()->SetShelfEnabled(extension(),
params->enabled);
}
if (incognito_manager) {
incognito_service = DownloadServiceFactory::GetForBrowserContext(
incognito_manager->GetBrowserContext());
incognito_service->GetExtensionEventRouter()->SetShelfEnabled(
extension(), params->enabled);
}
BrowserList* browsers = BrowserList::GetInstance();
if (browsers) {
for (BrowserList::const_iterator iter = browsers->begin();
iter != browsers->end(); ++iter) {
const Browser* browser = *iter;
DownloadService* current_service =
DownloadServiceFactory::GetForBrowserContext(browser->profile());
if (((current_service == service) ||
(current_service == incognito_service)) &&
browser->window()->IsDownloadShelfVisible() &&
!current_service->IsShelfEnabled())
browser->window()->GetDownloadShelf()->Close(DownloadShelf::AUTOMATIC);
}
}
if (params->enabled &&
((manager && !service->IsShelfEnabled()) ||
(incognito_manager && !incognito_service->IsShelfEnabled()))) {
error_ = download_extension_errors::kShelfDisabled;
return false;
}
return true;
}
DownloadsGetFileIconFunction::DownloadsGetFileIconFunction()
: icon_extractor_(new DownloadFileIconExtractorImpl()) {
}
DownloadsGetFileIconFunction::~DownloadsGetFileIconFunction() {}
void DownloadsGetFileIconFunction::SetIconExtractorForTesting(
DownloadFileIconExtractor* extractor) {
DCHECK(extractor);
icon_extractor_.reset(extractor);
}
bool DownloadsGetFileIconFunction::RunAsync() {
std::unique_ptr<downloads::GetFileIcon::Params> params(
downloads::GetFileIcon::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
const downloads::GetFileIconOptions* options =
params->options.get();
int icon_size = kDefaultIconSize;
if (options && options->size.get())
icon_size = *options->size.get();
DownloadItem* download_item =
GetDownload(GetProfile(), include_incognito(), params->download_id);
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.
DCHECK(icon_extractor_.get());
DCHECK(icon_size == 16 || icon_size == 32);
float scale = 1.0;
content::WebContents* web_contents =
dispatcher()->GetVisibleWebContents();
if (web_contents) {
scale = ui::GetScaleFactorForNativeView(
web_contents->GetRenderWidgetHostView()->GetNativeView());
}
EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath(
download_item->GetTargetFilePath(),
scale,
IconLoaderSizeFromPixelSize(icon_size),
base::Bind(&DownloadsGetFileIconFunction::OnIconURLExtracted, this)));
return true;
}
void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (Fault(url.empty(), errors::kIconNotFound, &error_)) {
SendResponse(false);
return;
}
RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON);
SetResult(base::MakeUnique<base::StringValue>(url));
SendResponse(true);
}
ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(
Profile* profile,
DownloadManager* manager)
: profile_(profile),
notifier_(manager, this),
extension_registry_observer_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(profile_);
extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
EventRouter* router = EventRouter::Get(profile_);
if (router)
router->RegisterObserver(this,
downloads::OnDeterminingFilename::kEventName);
}
ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
EventRouter* router = EventRouter::Get(profile_);
if (router)
router->UnregisterObserver(this);
}
void ExtensionDownloadsEventRouter::
SetDetermineFilenameTimeoutSecondsForTesting(int s) {
ExtensionDownloadsEventRouterData::
SetDetermineFilenameTimeoutSecondsForTesting(s);
}
void ExtensionDownloadsEventRouter::SetShelfEnabled(const Extension* extension,
bool enabled) {
std::set<const Extension*>::iterator iter =
shelf_disabling_extensions_.find(extension);
if (iter == shelf_disabling_extensions_.end()) {
if (!enabled)
shelf_disabling_extensions_.insert(extension);
} else if (enabled) {
shelf_disabling_extensions_.erase(extension);
}
}
bool ExtensionDownloadsEventRouter::IsShelfEnabled() const {
return shelf_disabling_extensions_.empty();
}
// The method by which extensions hook into the filename determination process
// is based on the method by which the omnibox API allows extensions to hook
// into the omnibox autocompletion process. Extensions that wish to play a part
// in the filename determination process call
// chrome.downloads.onDeterminingFilename.addListener, which adds an
// EventListener object to ExtensionEventRouter::listeners().
//
// When a download's filename is being determined, DownloadTargetDeterminer (via
// ChromeDownloadManagerDelegate (CDMD) ::NotifyExtensions()) passes 2 callbacks
// to ExtensionDownloadsEventRouter::OnDeterminingFilename (ODF), which stores
// the callbacks in the item's ExtensionDownloadsEventRouterData (EDERD) along
// with all of the extension IDs that are listening for onDeterminingFilename
// events. ODF dispatches chrome.downloads.onDeterminingFilename.
//
// When the extension's event handler calls |suggestCallback|,
// downloads_custom_bindings.js calls
// DownloadsInternalDetermineFilenameFunction::RunAsync, which calls
// EDER::DetermineFilename, which notifies the item's EDERD.
//
// When the last extension's event handler returns, EDERD calls one of the two
// callbacks that CDMD passed to ODF, allowing DownloadTargetDeterminer to
// continue the filename determination process. If multiple extensions wish to
// override the filename, then the extension that was last installed wins.
void ExtensionDownloadsEventRouter::OnDeterminingFilename(
DownloadItem* item,
const base::FilePath& suggested_path,
const base::Closure& no_change,
const FilenameChangedCallback& change) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ExtensionDownloadsEventRouterData* data =
ExtensionDownloadsEventRouterData::Get(item);
if (!data) {
no_change.Run();
return;
}
data->BeginFilenameDetermination(no_change, change);
bool any_determiners = false;
base::DictionaryValue* json = DownloadItemToJSON(
item, profile_).release();
json->SetString(kFilenameKey, suggested_path.LossyDisplayName());
DispatchEvent(events::DOWNLOADS_ON_DETERMINING_FILENAME,
downloads::OnDeterminingFilename::kEventName, false,
base::Bind(&OnDeterminingFilenameWillDispatchCallback,
&any_determiners, data),
json);
if (!any_determiners) {
data->ClearPendingDeterminers();
if (!data->creator_suggested_filename().empty() ||
(data->creator_conflict_action() !=
downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY)) {
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();
}
}
}
void ExtensionDownloadsEventRouter::DetermineFilenameInternal(
const base::FilePath& filename,
downloads::FilenameConflictAction conflict_action,
const std::string& suggesting_extension_id,
const base::Time& suggesting_install_time,
const std::string& incumbent_extension_id,
const base::Time& incumbent_install_time,
std::string* winner_extension_id,
base::FilePath* determined_filename,
downloads::FilenameConflictAction* determined_conflict_action,
WarningSet* warnings) {
DCHECK(!filename.empty() ||
(conflict_action != downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY));
DCHECK(!suggesting_extension_id.empty());
if (incumbent_extension_id.empty()) {
*winner_extension_id = suggesting_extension_id;
*determined_filename = filename;
*determined_conflict_action = conflict_action;
return;
}
if (suggesting_install_time < incumbent_install_time) {
*winner_extension_id = incumbent_extension_id;
warnings->insert(Warning::CreateDownloadFilenameConflictWarning(
suggesting_extension_id,
incumbent_extension_id,
filename,
*determined_filename));
return;
}
*winner_extension_id = suggesting_extension_id;
warnings->insert(Warning::CreateDownloadFilenameConflictWarning(
incumbent_extension_id,
suggesting_extension_id,
*determined_filename,
filename));
*determined_filename = filename;
*determined_conflict_action = conflict_action;
}
bool ExtensionDownloadsEventRouter::DetermineFilename(
Profile* profile,
bool include_incognito,
const std::string& ext_id,
int download_id,
const base::FilePath& const_filename,
downloads::FilenameConflictAction conflict_action,
std::string* error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RecordApiFunctions(DOWNLOADS_FUNCTION_DETERMINE_FILENAME);
DownloadItem* item = GetDownload(profile, include_incognito, download_id);
ExtensionDownloadsEventRouterData* data =
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 (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;
base::FilePath::StringType filename_str(const_filename.value());
// Allow windows-style directory separators on all platforms.
std::replace(filename_str.begin(), filename_str.end(),
FILE_PATH_LITERAL('\\'), FILE_PATH_LITERAL('/'));
base::FilePath filename(filename_str);
bool valid_filename = net::IsSafePortableRelativePath(filename);
filename = (valid_filename ? filename.NormalizePathSeparators() :
base::FilePath());
// 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(
profile, ext_id, filename, conflict_action),
errors::kUnexpectedDeterminer, error) ||
Fault((!const_filename.empty() && !valid_filename),
errors::kInvalidFilename, error))
return false;
return true;
}
void ExtensionDownloadsEventRouter::OnListenerRemoved(
const EventListenerInfo& details) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DownloadManager* manager = notifier_.GetManager();
if (!manager)
return;
bool determiner_removed = (
details.event_name == downloads::OnDeterminingFilename::kEventName);
EventRouter* router = EventRouter::Get(profile_);
bool any_listeners =
router->HasEventListener(downloads::OnChanged::kEventName) ||
router->HasEventListener(downloads::OnDeterminingFilename::kEventName);
if (!determiner_removed && any_listeners)
return;
DownloadManager::DownloadVector items;
manager->GetAllDownloads(&items);
for (DownloadManager::DownloadVector::const_iterator iter =
items.begin();
iter != items.end(); ++iter) {
ExtensionDownloadsEventRouterData* data =
ExtensionDownloadsEventRouterData::Get(*iter);
if (!data)
continue;
if (determiner_removed) {
// Notify any items that may be waiting for callbacks from this
// extension/determiner. This will almost always be a no-op, however, it
// is possible for an extension renderer to be unloaded while a download
// item is waiting for a determiner. In that case, the download item
// should proceed.
data->DeterminerRemoved(details.extension_id);
}
if (!any_listeners &&
data->creator_suggested_filename().empty()) {
ExtensionDownloadsEventRouterData::Remove(*iter);
}
}
}
// That's all the methods that have to do with filename determination. The rest
// have to do with the other, less special events.
void ExtensionDownloadsEventRouter::OnDownloadCreated(
DownloadManager* manager, DownloadItem* download_item) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (download_item->IsTemporary())
return;
EventRouter* router = EventRouter::Get(profile_);
// Avoid allocating a bunch of memory in DownloadItemToJSON if it isn't going
// to be used.
if (!router ||
(!router->HasEventListener(downloads::OnCreated::kEventName) &&
!router->HasEventListener(downloads::OnChanged::kEventName) &&
!router->HasEventListener(
downloads::OnDeterminingFilename::kEventName))) {
return;
}
std::unique_ptr<base::DictionaryValue> json_item(
DownloadItemToJSON(download_item, profile_));
DispatchEvent(events::DOWNLOADS_ON_CREATED, downloads::OnCreated::kEventName,
true, Event::WillDispatchCallback(), json_item->DeepCopy());
if (!ExtensionDownloadsEventRouterData::Get(download_item) &&
(router->HasEventListener(downloads::OnChanged::kEventName) ||
router->HasEventListener(
downloads::OnDeterminingFilename::kEventName))) {
new ExtensionDownloadsEventRouterData(download_item, std::move(json_item));
}
}
void ExtensionDownloadsEventRouter::OnDownloadUpdated(
DownloadManager* manager, DownloadItem* download_item) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
EventRouter* router = EventRouter::Get(profile_);
ExtensionDownloadsEventRouterData* data =
ExtensionDownloadsEventRouterData::Get(download_item);
if (download_item->IsTemporary() ||
!router->HasEventListener(downloads::OnChanged::kEventName)) {
return;
}
if (!data) {
// The download_item probably transitioned from temporary to not temporary,
// or else an event listener was added.
data = new ExtensionDownloadsEventRouterData(
download_item,
std::unique_ptr<base::DictionaryValue>(new base::DictionaryValue()));
}
std::unique_ptr<base::DictionaryValue> new_json(
DownloadItemToJSON(download_item, profile_));
std::unique_ptr<base::DictionaryValue> delta(new base::DictionaryValue());
delta->SetInteger(kIdKey, download_item->GetId());
std::set<std::string> new_fields;
bool changed = false;
// For each field in the new json representation of the download_item except
// the bytesReceived field, if the field has changed from the previous old
// json, set the differences in the |delta| object and remember that something
// significant changed.
for (base::DictionaryValue::Iterator iter(*new_json.get());
!iter.IsAtEnd(); iter.Advance()) {
new_fields.insert(iter.key());
if (IsDownloadDeltaField(iter.key())) {
const base::Value* old_value = NULL;
if (!data->json().HasKey(iter.key()) ||
(data->json().Get(iter.key(), &old_value) &&
!iter.value().Equals(old_value))) {
delta->Set(iter.key() + ".current", iter.value().DeepCopy());
if (old_value)
delta->Set(iter.key() + ".previous", old_value->DeepCopy());
changed = true;
}
}
}
// If a field was in the previous json but is not in the new json, set the
// difference in |delta|.
for (base::DictionaryValue::Iterator iter(data->json());
!iter.IsAtEnd(); iter.Advance()) {
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;
}
}
// Update the OnChangedStat and dispatch the event if something significant
// changed. Replace the stored json with the new json.
data->OnItemUpdated();
if (changed) {
DispatchEvent(events::DOWNLOADS_ON_CHANGED,
downloads::OnChanged::kEventName, true,
Event::WillDispatchCallback(), delta.release());
data->OnChangedFired();
}
data->set_json(std::move(new_json));
}
void ExtensionDownloadsEventRouter::OnDownloadRemoved(
DownloadManager* manager, DownloadItem* download_item) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (download_item->IsTemporary())
return;
DispatchEvent(
events::DOWNLOADS_ON_ERASED, downloads::OnErased::kEventName, true,
Event::WillDispatchCallback(),
new base::FundamentalValue(static_cast<int>(download_item->GetId())));
}
void ExtensionDownloadsEventRouter::DispatchEvent(
events::HistogramValue histogram_value,
const std::string& event_name,
bool include_incognito,
const Event::WillDispatchCallback& will_dispatch_callback,
base::Value* arg) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!EventRouter::Get(profile_))
return;
std::unique_ptr<base::ListValue> args(new base::ListValue());
args->Append(arg);
std::string json_args;
base::JSONWriter::Write(*args, &json_args);
std::unique_ptr<Event> event(
new Event(histogram_value, event_name, std::move(args)));
// The downloads system wants to share on-record events with off-record
// extension renderers even in incognito_split_mode because that's how
// chrome://downloads works. The "restrict_to_profile" mechanism does not
// anticipate this, so it does not automatically prevent sharing off-record
// events with on-record extension renderers.
event->restrict_to_browser_context =
(include_incognito && !profile_->IsOffTheRecord()) ? NULL : profile_;
event->will_dispatch_callback = will_dispatch_callback;
EventRouter::Get(profile_)->BroadcastEvent(std::move(event));
DownloadsNotificationSource notification_source;
notification_source.event_name = event_name;
notification_source.profile = profile_;
content::Source<DownloadsNotificationSource> content_source(
&notification_source);
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
content_source,
content::Details<std::string>(&json_args));
}
void ExtensionDownloadsEventRouter::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionInfo::Reason reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::set<const Extension*>::iterator iter =
shelf_disabling_extensions_.find(extension);
if (iter != shelf_disabling_extensions_.end())
shelf_disabling_extensions_.erase(iter);
}
void ExtensionDownloadsEventRouter::CheckForHistoryFilesRemoval() {
static const int kFileExistenceRateLimitSeconds = 10;
DownloadManager* manager = notifier_.GetManager();
if (!manager)
return;
base::Time now(base::Time::Now());
int delta = now.ToTimeT() - last_checked_removal_.ToTimeT();
if (delta <= kFileExistenceRateLimitSeconds)
return;
last_checked_removal_ = now;
manager->CheckForHistoryFilesRemoval();
}
} // namespace extensions