From f1d784d6938b8fe8e0d257e41b26341992c2552c Mon Sep 17 00:00:00 2001 From: "benjhayden@chromium.org" Date: Sun, 28 Jul 2013 18:36:09 +0000 Subject: A few minor changes to the chrome.downloads extension API Reviewers: jhawkins: webui Done: asanka: */download/* jam: content/public/* asargent: */extensions/* Replaced the "Invalid operation" error message with more specific and helpful messages Add DownloadItem.referrer Add DownloadItem.estimatedEndTime Add DownloadItem.canResume Change DownloadQuery.limit to default to 1000 Change 'conflict_action' to 'conflictAction'. Change DownloadQuery.orderBy and DownloadQuery.query to arrays of strings Remove DownloadItem.dangerAccepted in favor of DownloadItem.danger == 'accepted' Add showDefaultFolder(). Disallow access to packaged_apps. download() now updates the Download.Sources histogram. http://crbug.com/240322 Document using startedAfter and limit to page through search() results. DownloadItem.error is now a string enum instead of mysterious numbers. Add downloads.removeFile(id) and DownloadItem::DeleteFile() so that the file may be deleted separately from the history entry. Calling open() for dangerous downloads has always been guaranteed to return kInvalidOperation because unaccepted dangerous downloads cannot transition to state='complete', and open() returns kInvalidOperation for incomplete items. We can wait until after launch to figure out whether there needs to be some mechanism to allow extensions to override the request header blacklist. This seems like an edge case for which we may never receive feature requests. Staged docs preview: http://basho.cam.corp.google.com:8000/extensions/downloads.html#type-DownloadItem BUG=240322 R=asanka@chromium.org, asargent@chromium.org, jam@chromium.org Review URL: https://codereview.chromium.org/16924017 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214133 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/download/download_danger_prompt.cc | 74 +- chrome/browser/download/download_danger_prompt.h | 7 +- .../download/download_danger_prompt_browsertest.cc | 21 +- chrome/browser/download/download_query.cc | 58 +- chrome/browser/download/download_query.h | 2 +- chrome/browser/download/download_query_unittest.cc | 32 +- chrome/browser/download/download_util.h | 3 + .../extensions/api/downloads/downloads_api.cc | 489 ++- .../extensions/api/downloads/downloads_api.h | 74 +- .../api/downloads/downloads_api_browsertest.cc | 3615 ++++++++++++++++++++ .../api/downloads/downloads_api_unittest.cc | 3544 ------------------- .../extension_function_histogram_value.h | 2 + chrome/browser/ui/webui/downloads_dom_handler.cc | 10 +- chrome/browser/ui/webui/downloads_dom_handler.h | 3 +- chrome/chrome_tests.gypi | 2 +- .../extensions/api/_permission_features.json | 2 +- chrome/common/extensions/api/downloads.idl | 185 +- .../extensions/downloads_custom_bindings.js | 29 +- content/browser/download/download_item_impl.cc | 14 + content/browser/download/download_item_impl.h | 1 + content/public/browser/download_item.h | 6 + content/public/test/mock_download_item.h | 1 + 22 files changed, 4280 insertions(+), 3894 deletions(-) create mode 100644 chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc delete mode 100644 chrome/browser/extensions/api/downloads/downloads_api_unittest.cc diff --git a/chrome/browser/download/download_danger_prompt.cc b/chrome/browser/download/download_danger_prompt.cc index 95ec322..09c1624 100644 --- a/chrome/browser/download/download_danger_prompt.cc +++ b/chrome/browser/download/download_danger_prompt.cc @@ -5,6 +5,7 @@ #include "chrome/browser/download/download_danger_prompt.h" #include "base/bind.h" +#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/download/chrome_download_manager_delegate.h" #include "chrome/browser/ui/tab_modal_confirm_dialog.h" #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" @@ -23,8 +24,7 @@ class DownloadDangerPromptImpl public: DownloadDangerPromptImpl(content::DownloadItem* item, bool show_context, - const base::Closure& accepted, - const base::Closure& canceled); + const OnDone& done); virtual ~DownloadDangerPromptImpl(); // DownloadDangerPrompt @@ -33,7 +33,6 @@ class DownloadDangerPromptImpl private: // content::DownloadItem::Observer virtual void OnDownloadUpdated(content::DownloadItem* download) OVERRIDE; - virtual void OnDownloadOpened(content::DownloadItem* download) OVERRIDE; // TabModalConfirmDialogDelegate virtual string16 GetTitle() OVERRIDE; @@ -42,20 +41,11 @@ class DownloadDangerPromptImpl virtual void OnAccepted() OVERRIDE; virtual void OnCanceled() OVERRIDE; - // Runs |callback|. PrepareToClose() is called beforehand. Doing so prevents - // this object from responding to state changes in |download_| that might - // result from invoking the callback. |callback| must refer to either - // |accepted_| or |canceled_|. - void RunCallback(const base::Closure& callback); - - // Resets |accepted_|, |canceled_| and removes the observer from |download_|, - // in preparation for closing the prompt. - void PrepareToClose(); + void RunDone(Action action); content::DownloadItem* download_; bool show_context_; - base::Closure accepted_; - base::Closure canceled_; + OnDone done_; DISALLOW_COPY_AND_ASSIGN(DownloadDangerPromptImpl); }; @@ -63,29 +53,29 @@ class DownloadDangerPromptImpl DownloadDangerPromptImpl::DownloadDangerPromptImpl( content::DownloadItem* download, bool show_context, - const base::Closure& accepted, - const base::Closure& canceled) + const OnDone& done) : download_(download), show_context_(show_context), - accepted_(accepted), - canceled_(canceled) { - DCHECK(!accepted_.is_null()); - // canceled_ is allowed to be null. - DCHECK(download_); + done_(done) { + DCHECK(!done_.is_null()); download_->AddObserver(this); } DownloadDangerPromptImpl::~DownloadDangerPromptImpl() { // |this| might be deleted without invoking any callbacks. E.g. pressing Esc // on GTK or if the user navigates away from the page showing the prompt. - PrepareToClose(); + RunDone(DISMISS); } void DownloadDangerPromptImpl::InvokeActionForTesting(Action action) { - if (action == ACCEPT) - Accept(); - else - Cancel(); + switch (action) { + case ACCEPT: Accept(); break; + case CANCEL: Cancel(); break; + case DISMISS: + RunDone(DISMISS); + Cancel(); + break; + } } void DownloadDangerPromptImpl::OnDownloadUpdated( @@ -93,12 +83,10 @@ void DownloadDangerPromptImpl::OnDownloadUpdated( // If the download is nolonger dangerous (accepted externally) or the download // is in a terminal state, then the download danger prompt is no longer // necessary. - if (!download->IsDangerous() || download->IsDone()) + if (!download->IsDangerous() || download->IsDone()) { + RunDone(DISMISS); Cancel(); -} - -void DownloadDangerPromptImpl::OnDownloadOpened( - content::DownloadItem* download) { + } } string16 DownloadDangerPromptImpl::GetTitle() { @@ -144,30 +132,25 @@ string16 DownloadDangerPromptImpl::GetAcceptButtonTitle() { } void DownloadDangerPromptImpl::OnAccepted() { - RunCallback(accepted_); + RunDone(ACCEPT); } void DownloadDangerPromptImpl::OnCanceled() { - RunCallback(canceled_); + RunDone(CANCEL); } -void DownloadDangerPromptImpl::RunCallback(const base::Closure& callback) { +void DownloadDangerPromptImpl::RunDone(Action action) { // Invoking the callback can cause the download item state to change or cause // the constrained window to close, and |callback| refers to a member // variable. - base::Closure callback_copy = callback; - PrepareToClose(); - if (!callback_copy.is_null()) - callback_copy.Run(); -} - -void DownloadDangerPromptImpl::PrepareToClose() { - accepted_.Reset(); - canceled_.Reset(); + OnDone done = done_; + done_.Reset(); if (download_ != NULL) { download_->RemoveObserver(this); download_ = NULL; } + if (!done.is_null()) + done.Run(action); } } // namespace @@ -177,10 +160,9 @@ DownloadDangerPrompt* DownloadDangerPrompt::Create( content::DownloadItem* item, content::WebContents* web_contents, bool show_context, - const base::Closure& accepted, - const base::Closure& canceled) { + const OnDone& done) { DownloadDangerPromptImpl* prompt = new DownloadDangerPromptImpl( - item, show_context, accepted, canceled); + item, show_context, done); // |prompt| will be deleted when the dialog is done. TabModalConfirmDialog::Create(prompt, web_contents); return prompt; diff --git a/chrome/browser/download/download_danger_prompt.h b/chrome/browser/download/download_danger_prompt.h index 2ab5bb0..8c2407f 100644 --- a/chrome/browser/download/download_danger_prompt.h +++ b/chrome/browser/download/download_danger_prompt.h @@ -24,8 +24,10 @@ class DownloadDangerPrompt { // Actions resulting from showing the danger prompt. enum Action { ACCEPT, - CANCEL + CANCEL, + DISMISS, }; + typedef base::Callback OnDone; // Return a new self-deleting DownloadDangerPrompt. |accepted| or |canceled| // will be run when the the respective action is invoked. |canceled| may also @@ -39,8 +41,7 @@ class DownloadDangerPrompt { content::DownloadItem* item, content::WebContents* web_contents, bool show_context, - const base::Closure& accepted, - const base::Closure& canceled); + const OnDone& done); protected: friend class DownloadDangerPromptTest; diff --git a/chrome/browser/download/download_danger_prompt_browsertest.cc b/chrome/browser/download/download_danger_prompt_browsertest.cc index 5a7c9e8..f203b71 100644 --- a/chrome/browser/download/download_danger_prompt_browsertest.cc +++ b/chrome/browser/download/download_danger_prompt_browsertest.cc @@ -86,10 +86,7 @@ class DownloadDangerPromptTest : public InProcessBrowserTest { &download_, browser()->tab_strip_model()->GetActiveWebContents(), false, - base::Bind(&DownloadDangerPromptTest::PromptCallback, this, - DownloadDangerPrompt::ACCEPT), - base::Bind(&DownloadDangerPromptTest::PromptCallback, this, - DownloadDangerPrompt::CANCEL)); + base::Bind(&DownloadDangerPromptTest::PromptCallback, this)); content::RunAllPendingInMessageLoop(); } @@ -112,26 +109,26 @@ class DownloadDangerPromptTest : public InProcessBrowserTest { IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, TestAll) { OpenNewTab(); - // The Accept action should cause the accept callback to be invoked. + // Clicking the Accept button should invoke the ACCEPT action. SetUpExpectations(DownloadDangerPrompt::ACCEPT); SimulatePromptAction(DownloadDangerPrompt::ACCEPT); VerifyExpectations(); - // The Discard action should cause the discard callback to be invoked. + // Clicking the Cancel button should invoke the CANCEL action. SetUpExpectations(DownloadDangerPrompt::CANCEL); SimulatePromptAction(DownloadDangerPrompt::CANCEL); VerifyExpectations(); // If the download is no longer dangerous (because it was accepted), the - // dialog should dismiss itself. - SetUpExpectations(DownloadDangerPrompt::CANCEL); + // dialog should DISMISS itself. + SetUpExpectations(DownloadDangerPrompt::DISMISS); EXPECT_CALL(download(), IsDangerous()).WillOnce(Return(false)); download_observer()->OnDownloadUpdated(&download()); VerifyExpectations(); - // If the download is in a terminal state then the dialog should dismiss + // If the download is in a terminal state then the dialog should DISMISS // itself. - SetUpExpectations(DownloadDangerPrompt::CANCEL); + SetUpExpectations(DownloadDangerPrompt::DISMISS); EXPECT_CALL(download(), IsDangerous()).WillOnce(Return(true)); EXPECT_CALL(download(), IsDone()).WillOnce(Return(true)); download_observer()->OnDownloadUpdated(&download()); @@ -146,8 +143,10 @@ IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, TestAll) { SimulatePromptAction(DownloadDangerPrompt::ACCEPT); VerifyExpectations(); - // If the containing tab is closed, the dialog should be canceled. + // If the containing tab is closed, the dialog should DISMISS itself. OpenNewTab(); + // TODO(benjhayden): + // SetUpExpectations(DownloadDangerPrompt::DISMISS); SetUpExpectations(DownloadDangerPrompt::CANCEL); chrome::CloseTab(browser()); VerifyExpectations(); diff --git a/chrome/browser/download/download_query.cc b/chrome/browser/download/download_query.cc index 14bc73e..57ea7f3 100644 --- a/chrome/browser/download/download_query.cc +++ b/chrome/browser/download/download_query.cc @@ -50,22 +50,30 @@ template<> bool GetAs(const base::Value& in, std::string* out) { template<> bool GetAs(const base::Value& in, string16* out) { return in.GetAsString(out); } +template<> bool GetAs(const base::Value& in, std::vector* out) { + out->clear(); + const base::ListValue* list = NULL; + if (!in.GetAsList(&list)) + return false; + for (size_t i = 0; i < list->GetSize(); ++i) { + string16 element; + if (!list->GetString(i, &element)) { + out->clear(); + return false; + } + out->push_back(element); + } + return true; +} // The next several functions are helpers for making Callbacks that access // DownloadItem fields. -static bool MatchesQuery(const string16& query, const DownloadItem& item) { - if (query.empty()) - return true; - - DCHECK_EQ(query, base::i18n::ToLower(query)); - +static bool MatchesQuery( + const std::vector& query_terms, + const DownloadItem& item) { + DCHECK(!query_terms.empty()); string16 url_raw(UTF8ToUTF16(item.GetOriginalUrl().spec())); - if (base::i18n::StringSearchIgnoringCaseAndAccents( - query, url_raw, NULL, NULL)) { - return true; - } - string16 url_formatted = url_raw; if (item.GetBrowserContext()) { Profile* profile = Profile::FromBrowserContext(item.GetBrowserContext()); @@ -73,14 +81,21 @@ static bool MatchesQuery(const string16& query, const DownloadItem& item) { item.GetOriginalUrl(), profile->GetPrefs()->GetString(prefs::kAcceptLanguages)); } - if (base::i18n::StringSearchIgnoringCaseAndAccents( - query, url_formatted, NULL, NULL)) { - return true; - } - string16 path(item.GetTargetFilePath().LossyDisplayName()); - return base::i18n::StringSearchIgnoringCaseAndAccents( - query, path, NULL, NULL); + + for (std::vector::const_iterator it = query_terms.begin(); + it != query_terms.end(); ++it) { + string16 term = base::i18n::ToLower(*it); + if (!base::i18n::StringSearchIgnoringCaseAndAccents( + term, url_raw, NULL, NULL) && + !base::i18n::StringSearchIgnoringCaseAndAccents( + term, url_formatted, NULL, NULL) && + !base::i18n::StringSearchIgnoringCaseAndAccents( + term, path, NULL, NULL)) { + return false; + } + } + return true; } static int64 GetStartTimeMsEpoch(const DownloadItem& item) { @@ -268,9 +283,10 @@ bool DownloadQuery::AddFilter(DownloadQuery::FilterType type, case FILTER_PAUSED: return AddFilter(BuildFilter(value, EQ, &IsPaused)); case FILTER_QUERY: { - string16 query; - return GetAs(value, &query) && - AddFilter(base::Bind(&MatchesQuery, query)); + std::vector query_terms; + return GetAs(value, &query_terms) && + (query_terms.empty() || + AddFilter(base::Bind(&MatchesQuery, query_terms))); } case FILTER_ENDED_AFTER: return AddFilter(BuildFilter(value, GT, &GetEndTime)); diff --git a/chrome/browser/download/download_query.h b/chrome/browser/download/download_query.h index 2da7a1b..533265a 100644 --- a/chrome/browser/download/download_query.h +++ b/chrome/browser/download/download_query.h @@ -58,7 +58,7 @@ class DownloadQuery { FILTER_FILENAME_REGEX, // string FILTER_MIME, // string FILTER_PAUSED, // bool - FILTER_QUERY, // string + FILTER_QUERY, // vector FILTER_STARTED_AFTER, // string FILTER_STARTED_BEFORE, // string FILTER_START_TIME, // string diff --git a/chrome/browser/download/download_query_unittest.cc b/chrome/browser/download/download_query_unittest.cc index 00b36ad..17bf351 100644 --- a/chrome/browser/download/download_query_unittest.cc +++ b/chrome/browser/download/download_query_unittest.cc @@ -128,6 +128,26 @@ template<> void DownloadQueryTest::AddFilter( CHECK(query_.AddFilter(name, *value.get())); } +template<> void DownloadQueryTest::AddFilter( + DownloadQuery::FilterType name, std::vector cpp_value) { + scoped_ptr list(new base::ListValue()); + for (std::vector::const_iterator it = cpp_value.begin(); + it != cpp_value.end(); ++it) { + list->Append(Value::CreateStringValue(*it)); + } + CHECK(query_.AddFilter(name, *list.get())); +} + +template<> void DownloadQueryTest::AddFilter( + DownloadQuery::FilterType name, std::vector cpp_value) { + scoped_ptr list(new base::ListValue()); + for (std::vector::const_iterator it = cpp_value.begin(); + it != cpp_value.end(); ++it) { + list->Append(Value::CreateStringValue(*it)); + } + CHECK(query_.AddFilter(name, *list.get())); +} + #if defined(OS_WIN) template<> void DownloadQueryTest::AddFilter( DownloadQuery::FilterType name, std::wstring cpp_value) { @@ -177,7 +197,9 @@ TEST_F(DownloadQueryTest, DownloadQueryTest_FilterGenericQueryFilename) { GURL fail_url("http://example.com/fail"); EXPECT_CALL(mock(0), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url)); EXPECT_CALL(mock(1), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url)); - AddFilter(DownloadQuery::FILTER_QUERY, "query"); + std::vector query_terms; + query_terms.push_back("query"); + AddFilter(DownloadQuery::FILTER_QUERY, query_terms); ExpectStandardFilterResults(); } @@ -196,7 +218,9 @@ TEST_F(DownloadQueryTest, DownloadQueryTest_FilterGenericQueryUrl) { EXPECT_CALL(mock(0), GetOriginalUrl()).WillRepeatedly(ReturnRef(match_url)); GURL fail_url("http://example.com/fail"); EXPECT_CALL(mock(1), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url)); - AddFilter(DownloadQuery::FILTER_QUERY, "query"); + std::vector query_terms; + query_terms.push_back("query"); + AddFilter(DownloadQuery::FILTER_QUERY, query_terms); ExpectStandardFilterResults(); } @@ -222,7 +246,9 @@ TEST_F(DownloadQueryTest, DownloadQueryTest_FilterGenericQueryFilenameI18N) { GURL fail_url("http://example.com/fail"); EXPECT_CALL(mock(0), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url)); EXPECT_CALL(mock(1), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url)); - AddFilter(DownloadQuery::FILTER_QUERY, kTestString); + std::vector query_terms; + query_terms.push_back(kTestString); + AddFilter(DownloadQuery::FILTER_QUERY, query_terms); ExpectStandardFilterResults(); } diff --git a/chrome/browser/download/download_util.h b/chrome/browser/download/download_util.h index 7900868..e2aca39 100644 --- a/chrome/browser/download/download_util.h +++ b/chrome/browser/download/download_util.h @@ -186,6 +186,9 @@ enum ChromeDownloadSource { // The download was initiated by the PDF plugin.. INITIATED_BY_PDF_SAVE, + // The download was initiated by chrome.downloads.download(). + INITIATED_BY_EXTENSION, + CHROME_DOWNLOAD_SOURCE_LAST_ENTRY, }; 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 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( + json->SetString(kErrorKey, content::InterruptReasonDebugString( download_item->GetLastReason())); } else if (download_item->GetState() == DownloadItem::CANCELLED) { - json->SetInteger(kErrorKey, static_cast( + 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(json); @@ -300,7 +321,6 @@ typedef base::hash_map 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 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& order_by_strs, + std::string* error, + DownloadQuery* query) { // TODO(benjhayden): Consider switching from LazyInstance to explicit string // comparisons. static base::LazyInstance sorter_types = @@ -414,8 +427,6 @@ void CompileDownloadQueryOrderBy( if (sorter_types.Get().size() == 0) InitSortTypeMap(sorter_types.Get()); - std::vector order_by_strs; - base::SplitString(order_by_str, ' ', &order_by_strs); for (std::vector::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(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(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 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 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 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 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 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; } diff --git a/chrome/browser/extensions/api/downloads/downloads_api.h b/chrome/browser/extensions/api/downloads/downloads_api.h index 77b7777..465148c1 100644 --- a/chrome/browser/extensions/api/downloads/downloads_api.h +++ b/chrome/browser/extensions/api/downloads/downloads_api.h @@ -12,6 +12,7 @@ #include "base/strings/string16.h" #include "base/values.h" #include "chrome/browser/download/all_download_item_notifier.h" +#include "chrome/browser/download/download_danger_prompt.h" #include "chrome/browser/download/download_path_reservation_tracker.h" #include "chrome/browser/extensions/event_router.h" #include "chrome/browser/extensions/extension_function.h" @@ -34,18 +35,26 @@ class ResourceDispatcherHost; namespace download_extension_errors { // Errors that can be returned through chrome.runtime.lastError.message. -extern const char kGenericError[]; -extern const char kIconNotFoundError[]; -extern const char kInvalidDangerTypeError[]; -extern const char kInvalidFilenameError[]; -extern const char kInvalidFilterError[]; -extern const char kInvalidOperationError[]; -extern const char kInvalidOrderByError[]; +extern const char kEmptyFile[]; +extern const char kFileAlreadyDeleted[]; +extern const char kIconNotFound[]; +extern const char kInvalidDangerType[]; +extern const char kInvalidFilename[]; +extern const char kInvalidFilter[]; +extern const char kInvalidHeader[]; +extern const char kInvalidId[]; +extern const char kInvalidOrderBy[]; extern const char kInvalidQueryLimit[]; -extern const char kInvalidStateError[]; -extern const char kInvalidURLError[]; -extern const char kNotImplementedError[]; -extern const char kTooManyListenersError[]; +extern const char kInvalidState[]; +extern const char kInvalidURL[]; +extern const char kInvisibleContext[]; +extern const char kNotComplete[]; +extern const char kNotDangerous[]; +extern const char kNotInProgress[]; +extern const char kNotResumable[]; +extern const char kOpenPermission[]; +extern const char kTooManyListeners[]; +extern const char kUnexpectedDeterminer[]; } // namespace download_extension_errors @@ -60,7 +69,12 @@ class DownloadsDownloadFunction : public AsyncExtensionFunction { virtual ~DownloadsDownloadFunction(); private: - void OnStarted(content::DownloadItem* item, net::Error error); + void OnStarted( + const base::FilePath& creator_suggested_filename, + extensions::api::downloads::FilenameConflictAction + creator_conflict_action, + content::DownloadItem* item, + net::Error error); DISALLOW_COPY_AND_ASSIGN(DownloadsDownloadFunction); }; @@ -130,6 +144,25 @@ class DownloadsEraseFunction : public SyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(DownloadsEraseFunction); }; +class DownloadsRemoveFileFunction : public AsyncExtensionFunction, + public content::DownloadItem::Observer { + public: + DECLARE_EXTENSION_FUNCTION("downloads.removeFile", DOWNLOADS_REMOVEFILE) + DownloadsRemoveFileFunction(); + virtual bool RunImpl() OVERRIDE; + + protected: + virtual ~DownloadsRemoveFileFunction(); + + private: + virtual void OnDownloadUpdated(content::DownloadItem* item) OVERRIDE; + virtual void OnDownloadDestroyed(content::DownloadItem* item) OVERRIDE; + + content::DownloadItem* item_; + + DISALLOW_COPY_AND_ASSIGN(DownloadsRemoveFileFunction); +}; + class DownloadsAcceptDangerFunction : public AsyncExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("downloads.acceptDanger", DOWNLOADS_ACCEPTDANGER) @@ -138,7 +171,8 @@ class DownloadsAcceptDangerFunction : public AsyncExtensionFunction { protected: virtual ~DownloadsAcceptDangerFunction(); - void DangerPromptCallback(bool accept, int download_id); + void DangerPromptCallback(int download_id, + DownloadDangerPrompt::Action action); private: DISALLOW_COPY_AND_ASSIGN(DownloadsAcceptDangerFunction); @@ -157,6 +191,20 @@ class DownloadsShowFunction : public AsyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(DownloadsShowFunction); }; +class DownloadsShowDefaultFolderFunction : public AsyncExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION( + "downloads.showDefaultFolder", DOWNLOADS_SHOWDEFAULTFOLDER) + DownloadsShowDefaultFolderFunction(); + virtual bool RunImpl() OVERRIDE; + + protected: + virtual ~DownloadsShowDefaultFolderFunction(); + + private: + DISALLOW_COPY_AND_ASSIGN(DownloadsShowDefaultFolderFunction); +}; + class DownloadsOpenFunction : public SyncExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("downloads.open", DOWNLOADS_OPEN) diff --git a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc new file mode 100644 index 0000000..5060617 --- /dev/null +++ b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc @@ -0,0 +1,3615 @@ +// 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 + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/json/json_reader.h" +#include "base/message_loop/message_loop.h" +#include "base/prefs/pref_service.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/download/download_file_icon_extractor.h" +#include "chrome/browser/download/download_service.h" +#include "chrome/browser/download/download_service_factory.h" +#include "chrome/browser/download/download_test_file_activity_observer.h" +#include "chrome/browser/extensions/api/downloads/downloads_api.h" +#include "chrome/browser/extensions/event_names.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/history/download_row.h" +#include "chrome/browser/net/url_request_mock_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/download_item.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/test/download_test_observer.h" +#include "content/test/net/url_request_slow_download_job.h" +#include "net/base/data_url.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_job_factory.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "webkit/browser/blob/blob_storage_controller.h" +#include "webkit/browser/blob/blob_url_request_job.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/common/blob/blob_data.h" + +using content::BrowserContext; +using content::BrowserThread; +using content::DownloadItem; +using content::DownloadManager; +using content::URLRequestSlowDownloadJob; + +namespace events = extensions::event_names; + +namespace errors = download_extension_errors; + +namespace api = extensions::api::downloads; + +namespace { + +// Comparator that orders download items by their ID. Can be used with +// std::sort. +struct DownloadIdComparator { + bool operator() (DownloadItem* first, DownloadItem* second) { + return first->GetId() < second->GetId(); + } +}; + +class DownloadsEventsListener : public content::NotificationObserver { + public: + DownloadsEventsListener() + : waiting_(false) { + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, + content::NotificationService::AllSources()); + } + + virtual ~DownloadsEventsListener() { + registrar_.Remove(this, chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, + content::NotificationService::AllSources()); + STLDeleteElements(&events_); + } + + void ClearEvents() { + STLDeleteElements(&events_); + events_.clear(); + } + + class Event { + public: + Event(Profile* profile, + const std::string& event_name, + const std::string& json_args, + base::Time caught) + : profile_(profile), + event_name_(event_name), + json_args_(json_args), + args_(base::JSONReader::Read(json_args)), + caught_(caught) { + } + + const base::Time& caught() { return caught_; } + + bool Satisfies(const Event& other) const { + return other.SatisfiedBy(*this); + } + + bool SatisfiedBy(const Event& other) const { + if ((profile_ != other.profile_) || + (event_name_ != other.event_name_)) + return false; + if (((event_name_ == events::kOnDownloadDeterminingFilename) || + (event_name_ == events::kOnDownloadCreated) || + (event_name_ == events::kOnDownloadChanged)) && + args_.get() && + other.args_.get()) { + base::ListValue* left_list = NULL; + base::DictionaryValue* left_dict = NULL; + base::ListValue* right_list = NULL; + base::DictionaryValue* right_dict = NULL; + if (!args_->GetAsList(&left_list) || + !other.args_->GetAsList(&right_list) || + !left_list->GetDictionary(0, &left_dict) || + !right_list->GetDictionary(0, &right_dict)) + return false; + for (base::DictionaryValue::Iterator iter(*left_dict); + !iter.IsAtEnd(); iter.Advance()) { + base::Value* right_value = NULL; + if (!right_dict->HasKey(iter.key()) || + (right_dict->Get(iter.key(), &right_value) && + !iter.value().Equals(right_value))) { + return false; + } + } + return true; + } else if ((event_name_ == events::kOnDownloadErased) && + args_.get() && + other.args_.get()) { + int my_id = -1, other_id = -1; + return (args_->GetAsInteger(&my_id) && + other.args_->GetAsInteger(&other_id) && + my_id == other_id); + } + return json_args_ == other.json_args_; + } + + std::string Debug() { + return base::StringPrintf("Event(%p, %s, %s, %f)", + profile_, + event_name_.c_str(), + json_args_.c_str(), + caught_.ToJsTime()); + } + + private: + Profile* profile_; + std::string event_name_; + std::string json_args_; + scoped_ptr args_; + base::Time caught_; + + DISALLOW_COPY_AND_ASSIGN(Event); + }; + + typedef ExtensionDownloadsEventRouter::DownloadsNotificationSource + DownloadsNotificationSource; + + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE { + switch (type) { + case chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT: + { + DownloadsNotificationSource* dns = + content::Source(source).ptr(); + Event* new_event = new Event( + dns->profile, + dns->event_name, + *content::Details(details).ptr(), base::Time::Now()); + events_.push_back(new_event); + if (waiting_ && + waiting_for_.get() && + new_event->Satisfies(*waiting_for_)) { + waiting_ = false; + base::MessageLoopForUI::current()->Quit(); + } + break; + } + default: + NOTREACHED(); + } + } + + bool WaitFor(Profile* profile, + const std::string& event_name, + const std::string& json_args) { + waiting_for_.reset(new Event(profile, event_name, json_args, base::Time())); + for (std::deque::const_iterator iter = events_.begin(); + iter != events_.end(); ++iter) { + if ((*iter)->Satisfies(*waiting_for_.get())) { + return true; + } + } + waiting_ = true; + content::RunMessageLoop(); + bool success = !waiting_; + if (waiting_) { + // Print the events that were caught since the last WaitFor() call to help + // find the erroneous event. + // TODO(benjhayden) Fuzzy-match and highlight the erroneous event. + for (std::deque::const_iterator iter = events_.begin(); + iter != events_.end(); ++iter) { + if ((*iter)->caught() > last_wait_) { + LOG(INFO) << "Caught " << (*iter)->Debug(); + } + } + if (waiting_for_.get()) { + LOG(INFO) << "Timed out waiting for " << waiting_for_->Debug(); + } + waiting_ = false; + } + waiting_for_.reset(); + last_wait_ = base::Time::Now(); + return success; + } + + private: + bool waiting_; + base::Time last_wait_; + scoped_ptr waiting_for_; + content::NotificationRegistrar registrar_; + std::deque events_; + + DISALLOW_COPY_AND_ASSIGN(DownloadsEventsListener); +}; + +class DownloadExtensionTest : public ExtensionApiTest { + public: + DownloadExtensionTest() + : extension_(NULL), + incognito_browser_(NULL), + current_browser_(NULL) { + } + + protected: + // Used with CreateHistoryDownloads + struct HistoryDownloadInfo { + // Filename to use. CreateHistoryDownloads will append this filename to the + // temporary downloads directory specified by downloads_directory(). + const base::FilePath::CharType* filename; + + // State for the download. Note that IN_PROGRESS downloads will be created + // as CANCELLED. + DownloadItem::DownloadState state; + + // Danger type for the download. Only use DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS + // and DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT. + content::DownloadDangerType danger_type; + }; + + void LoadExtension(const char* name) { + // Store the created Extension object so that we can attach it to + // ExtensionFunctions. Also load the extension in incognito profiles for + // testing incognito. + extension_ = LoadExtensionIncognito(test_data_dir_.AppendASCII(name)); + CHECK(extension_); + content::WebContents* tab = chrome::AddSelectedTabWithURL( + current_browser(), + extension_->GetResourceURL("empty.html"), + content::PAGE_TRANSITION_LINK); + extensions::ExtensionSystem::Get(current_browser()->profile())-> + event_router()->AddEventListener( + extensions::event_names::kOnDownloadCreated, + tab->GetRenderProcessHost(), + GetExtensionId()); + extensions::ExtensionSystem::Get(current_browser()->profile())-> + event_router()->AddEventListener( + extensions::event_names::kOnDownloadChanged, + tab->GetRenderProcessHost(), + GetExtensionId()); + extensions::ExtensionSystem::Get(current_browser()->profile())-> + event_router()->AddEventListener( + extensions::event_names::kOnDownloadErased, + tab->GetRenderProcessHost(), + GetExtensionId()); + } + + content::RenderProcessHost* AddFilenameDeterminer() { + content::WebContents* tab = chrome::AddSelectedTabWithURL( + current_browser(), + extension_->GetResourceURL("empty.html"), + content::PAGE_TRANSITION_LINK); + extensions::ExtensionSystem::Get(current_browser()->profile())-> + event_router()->AddEventListener( + extensions::event_names::kOnDownloadDeterminingFilename, + tab->GetRenderProcessHost(), + GetExtensionId()); + return tab->GetRenderProcessHost(); + } + + void RemoveFilenameDeterminer(content::RenderProcessHost* host) { + extensions::ExtensionSystem::Get(current_browser()->profile())-> + event_router()->RemoveEventListener( + extensions::event_names::kOnDownloadDeterminingFilename, + host, + GetExtensionId()); + } + + Browser* current_browser() { return current_browser_; } + + // InProcessBrowserTest + virtual void SetUpOnMainThread() OVERRIDE { + ExtensionApiTest::SetUpOnMainThread(); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); + InProcessBrowserTest::SetUpOnMainThread(); + GoOnTheRecord(); + CreateAndSetDownloadsDirectory(); + current_browser()->profile()->GetPrefs()->SetBoolean( + prefs::kPromptForDownload, false); + GetOnRecordManager()->RemoveAllDownloads(); + events_listener_.reset(new DownloadsEventsListener()); + // Disable file chooser for current profile. + DownloadTestFileActivityObserver observer(current_browser()->profile()); + observer.EnableFileChooser(false); + } + + void GoOnTheRecord() { current_browser_ = browser(); } + + void GoOffTheRecord() { + if (!incognito_browser_) { + incognito_browser_ = CreateIncognitoBrowser(); + GetOffRecordManager()->RemoveAllDownloads(); + // Disable file chooser for incognito profile. + DownloadTestFileActivityObserver observer(incognito_browser_->profile()); + observer.EnableFileChooser(false); + } + current_browser_ = incognito_browser_; + } + + bool WaitFor(const std::string& event_name, const std::string& json_args) { + return events_listener_->WaitFor( + current_browser()->profile(), event_name, json_args); + } + + bool WaitForInterruption( + DownloadItem* item, + content::DownloadInterruptReason expected_error, + const std::string& on_created_event) { + if (!WaitFor(events::kOnDownloadCreated, on_created_event)) + return false; + // Now, onCreated is always fired before interruption. + return WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"error\": {\"current\": \"%s\"}," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"interrupted\"}}]", + item->GetId(), + content::InterruptReasonDebugString( + expected_error).c_str())); + } + + void ClearEvents() { + events_listener_->ClearEvents(); + } + + std::string GetExtensionURL() { + return extension_->url().spec(); + } + std::string GetExtensionId() { + return extension_->id(); + } + + std::string GetFilename(const char* path) { + std::string result = + downloads_directory_.path().AppendASCII(path).AsUTF8Unsafe(); +#if defined(OS_WIN) + for (std::string::size_type next = result.find("\\"); + next != std::string::npos; + next = result.find("\\", next)) { + result.replace(next, 1, "\\\\"); + next += 2; + } +#endif + return result; + } + + DownloadManager* GetOnRecordManager() { + return BrowserContext::GetDownloadManager(browser()->profile()); + } + DownloadManager* GetOffRecordManager() { + return BrowserContext::GetDownloadManager( + browser()->profile()->GetOffTheRecordProfile()); + } + DownloadManager* GetCurrentManager() { + return (current_browser_ == incognito_browser_) ? + GetOffRecordManager() : GetOnRecordManager(); + } + + // Creates a set of history downloads based on the provided |history_info| + // array. |count| is the number of elements in |history_info|. On success, + // |items| will contain |count| DownloadItems in the order that they were + // specified in |history_info|. Returns true on success and false otherwise. + bool CreateHistoryDownloads(const HistoryDownloadInfo* history_info, + size_t count, + DownloadManager::DownloadVector* items) { + DownloadIdComparator download_id_comparator; + base::Time current = base::Time::Now(); + items->clear(); + GetOnRecordManager()->GetAllDownloads(items); + CHECK_EQ(0, static_cast(items->size())); + std::vector url_chain; + url_chain.push_back(GURL()); + for (size_t i = 0; i < count; ++i) { + DownloadItem* item = GetOnRecordManager()->CreateDownloadItem( + content::DownloadItem::kInvalidId + 1 + i, + downloads_directory().Append(history_info[i].filename), + downloads_directory().Append(history_info[i].filename), + url_chain, GURL(), // URL Chain, referrer + current, current, // start_time, end_time + 1, 1, // received_bytes, total_bytes + history_info[i].state, // state + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + content::DOWNLOAD_INTERRUPT_REASON_NONE, + false); // opened + items->push_back(item); + } + + // Order by ID so that they are in the order that we created them. + std::sort(items->begin(), items->end(), download_id_comparator); + // Set the danger type if necessary. + for (size_t i = 0; i < count; ++i) { + if (history_info[i].danger_type != + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) { + EXPECT_EQ(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT, + history_info[i].danger_type); + items->at(i)->OnContentCheckCompleted(history_info[i].danger_type); + } + } + return true; + } + + void CreateSlowTestDownloads( + size_t count, DownloadManager::DownloadVector* items) { + for (size_t i = 0; i < count; ++i) { + scoped_ptr observer( + CreateInProgressDownloadObserver(1)); + GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl); + ui_test_utils::NavigateToURLWithDisposition( + current_browser(), slow_download_url, CURRENT_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + observer->WaitForFinished(); + EXPECT_EQ( + 1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS)); + } + GetCurrentManager()->GetAllDownloads(items); + ASSERT_EQ(count, items->size()); + } + + DownloadItem* CreateSlowTestDownload() { + scoped_ptr observer( + CreateInProgressDownloadObserver(1)); + GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl); + DownloadManager* manager = GetCurrentManager(); + + EXPECT_EQ(0, manager->InProgressCount()); + if (manager->InProgressCount() != 0) + return NULL; + + ui_test_utils::NavigateToURLWithDisposition( + current_browser(), slow_download_url, CURRENT_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + observer->WaitForFinished(); + EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS)); + + DownloadManager::DownloadVector items; + manager->GetAllDownloads(&items); + + DownloadItem* new_item = NULL; + for (DownloadManager::DownloadVector::iterator iter = items.begin(); + iter != items.end(); ++iter) { + if ((*iter)->GetState() == DownloadItem::IN_PROGRESS) { + // There should be only one IN_PROGRESS item. + EXPECT_EQ(NULL, new_item); + new_item = *iter; + } + } + return new_item; + } + + void FinishPendingSlowDownloads() { + scoped_ptr observer( + CreateDownloadObserver(1)); + GURL finish_url(URLRequestSlowDownloadJob::kFinishDownloadUrl); + ui_test_utils::NavigateToURLWithDisposition( + current_browser(), finish_url, NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + observer->WaitForFinished(); + EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::COMPLETE)); + } + + content::DownloadTestObserver* CreateDownloadObserver(size_t download_count) { + return new content::DownloadTestObserverTerminal( + GetCurrentManager(), download_count, + content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); + } + + content::DownloadTestObserver* CreateInProgressDownloadObserver( + size_t download_count) { + return new content::DownloadTestObserverInProgress( + GetCurrentManager(), download_count); + } + + bool RunFunction(UIThreadExtensionFunction* function, + const std::string& args) { + scoped_refptr delete_function(function); + SetUpExtensionFunction(function); + return extension_function_test_utils::RunFunction( + function, args, browser(), GetFlags()); + } + + extension_function_test_utils::RunFunctionFlags GetFlags() { + return current_browser()->profile()->IsOffTheRecord() ? + extension_function_test_utils::INCLUDE_INCOGNITO : + extension_function_test_utils::NONE; + } + + // extension_function_test_utils::RunFunction*() only uses browser for its + // profile(), so pass it the on-record browser so that it always uses the + // on-record profile to match real-life behavior. + + base::Value* RunFunctionAndReturnResult( + scoped_refptr function, + const std::string& args) { + SetUpExtensionFunction(function.get()); + return extension_function_test_utils::RunFunctionAndReturnSingleResult( + function.get(), args, browser(), GetFlags()); + } + + std::string RunFunctionAndReturnError( + scoped_refptr function, + const std::string& args) { + SetUpExtensionFunction(function.get()); + return extension_function_test_utils::RunFunctionAndReturnError( + function.get(), args, browser(), GetFlags()); + } + + bool RunFunctionAndReturnString( + scoped_refptr function, + const std::string& args, + std::string* result_string) { + SetUpExtensionFunction(function.get()); + scoped_ptr result(RunFunctionAndReturnResult(function, args)); + EXPECT_TRUE(result.get()); + return result.get() && result->GetAsString(result_string); + } + + std::string DownloadItemIdAsArgList(const DownloadItem* download_item) { + return base::StringPrintf("[%d]", download_item->GetId()); + } + + const base::FilePath& downloads_directory() { + return downloads_directory_.path(); + } + + DownloadsEventsListener* events_listener() { return events_listener_.get(); } + + private: + void SetUpExtensionFunction(UIThreadExtensionFunction* function) { + if (extension_) { + // Recreate the tab each time for insulation. + content::WebContents* tab = chrome::AddSelectedTabWithURL( + current_browser(), + extension_->GetResourceURL("empty.html"), + content::PAGE_TRANSITION_LINK); + function->set_extension(extension_); + function->SetRenderViewHost(tab->GetRenderViewHost()); + } + } + + void CreateAndSetDownloadsDirectory() { + ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir()); + current_browser()->profile()->GetPrefs()->SetFilePath( + prefs::kDownloadDefaultDirectory, + downloads_directory_.path()); + } + + base::ScopedTempDir downloads_directory_; + const extensions::Extension* extension_; + Browser* incognito_browser_; + Browser* current_browser_; + scoped_ptr events_listener_; + + DISALLOW_COPY_AND_ASSIGN(DownloadExtensionTest); +}; + +class MockIconExtractorImpl : public DownloadFileIconExtractor { + public: + MockIconExtractorImpl(const base::FilePath& path, + IconLoader::IconSize icon_size, + const std::string& response) + : expected_path_(path), + expected_icon_size_(icon_size), + response_(response) { + } + virtual ~MockIconExtractorImpl() {} + + virtual bool ExtractIconURLForPath(const base::FilePath& path, + IconLoader::IconSize icon_size, + IconURLCallback callback) OVERRIDE { + EXPECT_STREQ(expected_path_.value().c_str(), path.value().c_str()); + EXPECT_EQ(expected_icon_size_, icon_size); + if (expected_path_ == path && + expected_icon_size_ == icon_size) { + callback_ = callback; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MockIconExtractorImpl::RunCallback, + base::Unretained(this))); + return true; + } else { + return false; + } + } + + private: + void RunCallback() { + callback_.Run(response_); + } + + base::FilePath expected_path_; + IconLoader::IconSize expected_icon_size_; + std::string response_; + IconURLCallback callback_; +}; + +bool ItemNotInProgress(DownloadItem* item) { + return item->GetState() != DownloadItem::IN_PROGRESS; +} + +// Cancels the underlying DownloadItem when the ScopedCancellingItem goes out of +// scope. Like a scoped_ptr, but for DownloadItems. +class ScopedCancellingItem { + public: + explicit ScopedCancellingItem(DownloadItem* item) : item_(item) {} + ~ScopedCancellingItem() { + item_->Cancel(true); + content::DownloadUpdatedObserver observer( + item_, base::Bind(&ItemNotInProgress)); + observer.WaitForEvent(); + } + DownloadItem* get() { return item_; } + private: + DownloadItem* item_; + DISALLOW_COPY_AND_ASSIGN(ScopedCancellingItem); +}; + +// Cancels all the underlying DownloadItems when the ScopedItemVectorCanceller +// goes out of scope. Generalization of ScopedCancellingItem to many +// DownloadItems. +class ScopedItemVectorCanceller { + public: + explicit ScopedItemVectorCanceller(DownloadManager::DownloadVector* items) + : items_(items) { + } + ~ScopedItemVectorCanceller() { + for (DownloadManager::DownloadVector::const_iterator item = items_->begin(); + item != items_->end(); ++item) { + if ((*item)->GetState() == DownloadItem::IN_PROGRESS) + (*item)->Cancel(true); + content::DownloadUpdatedObserver observer( + (*item), base::Bind(&ItemNotInProgress)); + observer.WaitForEvent(); + } + } + + private: + DownloadManager::DownloadVector* items_; + DISALLOW_COPY_AND_ASSIGN(ScopedItemVectorCanceller); +}; + +class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { + public: + explicit TestProtocolHandler( + webkit_blob::BlobStorageController* blob_storage_controller, + fileapi::FileSystemContext* file_system_context) + : blob_storage_controller_(blob_storage_controller), + file_system_context_(file_system_context) {} + + virtual ~TestProtocolHandler() {} + + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE { + return new webkit_blob::BlobURLRequestJob( + request, + network_delegate, + blob_storage_controller_->GetBlobDataFromUrl(request->url()), + file_system_context_, + base::MessageLoopProxy::current().get()); + } + + private: + webkit_blob::BlobStorageController* const blob_storage_controller_; + fileapi::FileSystemContext* const file_system_context_; + + DISALLOW_COPY_AND_ASSIGN(TestProtocolHandler); +}; + +class TestURLRequestContext : public net::URLRequestContext { + public: + explicit TestURLRequestContext( + fileapi::FileSystemContext* file_system_context) + : blob_storage_controller_(new webkit_blob::BlobStorageController) { + // Job factory owns the protocol handler. + job_factory_.SetProtocolHandler( + "blob", new TestProtocolHandler(blob_storage_controller_.get(), + file_system_context)); + set_job_factory(&job_factory_); + } + + virtual ~TestURLRequestContext() {} + + webkit_blob::BlobStorageController* blob_storage_controller() const { + return blob_storage_controller_.get(); + } + + private: + net::URLRequestJobFactoryImpl job_factory_; + scoped_ptr blob_storage_controller_; + + DISALLOW_COPY_AND_ASSIGN(TestURLRequestContext); +}; + +// Writes an HTML5 file so that it can be downloaded. +class HTML5FileWriter { + public: + HTML5FileWriter( + Profile* profile, + const std::string& filename, + const std::string& origin, + DownloadsEventsListener* events_listener, + const std::string& payload) + : profile_(profile), + filename_(filename), + origin_(origin), + events_listener_(events_listener), + blob_data_(new webkit_blob::BlobData()), + payload_(payload), + fs_(BrowserContext::GetDefaultStoragePartition(profile_)-> + GetFileSystemContext()) { + CHECK(profile_); + CHECK(events_listener_); + CHECK(fs_); + } + + ~HTML5FileWriter() { + CHECK(BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( + &HTML5FileWriter::TearDownURLRequestContext, base::Unretained(this)))); + events_listener_->WaitFor( + profile_, kURLRequestContextToreDown, std::string()); + } + + bool WriteFile() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + fs_->OpenFileSystem( + GURL(origin_), + fileapi::kFileSystemTypeTemporary, + fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&HTML5FileWriter::OpenFileSystemCallback, + base::Unretained(this))); + return events_listener_->WaitFor(profile_, kHTML5FileWritten, filename_); + } + + private: + static const char kHTML5FileWritten[]; + static const char kURLRequestContextToreDown[]; + static const bool kExclusive = true; + + GURL blob_url() const { return GURL("blob:" + filename_); } + + void OpenFileSystemCallback( + base::PlatformFileError result, + const std::string& fs_name, + const GURL& root) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + root_ = root.spec(); + CHECK(BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( + &HTML5FileWriter::CreateFile, base::Unretained(this)))); + } + + fileapi::FileSystemOperationRunner* operation_runner() { + return fs_->operation_runner(); + } + + void CreateFile() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + operation_runner()->CreateFile(fs_->CrackURL(GURL(root_ + filename_)), + kExclusive, base::Bind( + &HTML5FileWriter::CreateFileCallback, base::Unretained(this))); + } + + void CreateFileCallback(base::PlatformFileError result) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + CHECK_EQ(base::PLATFORM_FILE_OK, result); + blob_data_->AppendData(payload_); + url_request_context_.reset(new TestURLRequestContext(fs_)); + url_request_context_->blob_storage_controller() + ->AddFinishedBlob(blob_url(), blob_data_.get()); + operation_runner()->Write( + url_request_context_.get(), + fs_->CrackURL(GURL(root_ + filename_)), + blob_url(), + 0, // offset + base::Bind(&HTML5FileWriter::WriteCallback, base::Unretained(this))); + } + + void WriteCallback( + base::PlatformFileError result, + int64 bytes, + bool complete) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + CHECK_EQ(base::PLATFORM_FILE_OK, result); + CHECK(BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( + &HTML5FileWriter::NotifyWritten, base::Unretained(this)))); + } + + void NotifyWritten() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DownloadsEventsListener::DownloadsNotificationSource notification_source; + notification_source.event_name = kHTML5FileWritten; + notification_source.profile = profile_; + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, + content::Source( + ¬ification_source), + content::Details(&filename_)); + } + + void TearDownURLRequestContext() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + url_request_context_->blob_storage_controller()->RemoveBlob(blob_url()); + url_request_context_.reset(); + CHECK(BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( + &HTML5FileWriter::NotifyURLRequestContextToreDown, + base::Unretained(this)))); + } + + void NotifyURLRequestContextToreDown() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DownloadsEventsListener::DownloadsNotificationSource notification_source; + notification_source.event_name = kURLRequestContextToreDown; + notification_source.profile = profile_; + std::string empty_args; + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, + content::Source( + ¬ification_source), + content::Details(&empty_args)); + } + + Profile* profile_; + std::string filename_; + std::string origin_; + std::string root_; + DownloadsEventsListener* events_listener_; + scoped_refptr blob_data_; + std::string payload_; + scoped_ptr url_request_context_; + fileapi::FileSystemContext* fs_; + + DISALLOW_COPY_AND_ASSIGN(HTML5FileWriter); +}; + +const char HTML5FileWriter::kHTML5FileWritten[] = "html5_file_written"; +const char HTML5FileWriter::kURLRequestContextToreDown[] = + "url_request_context_tore_down"; + +// TODO(benjhayden) Merge this with the other TestObservers. +class JustInProgressDownloadObserver + : public content::DownloadTestObserverInProgress { + public: + JustInProgressDownloadObserver( + DownloadManager* download_manager, size_t wait_count) + : content::DownloadTestObserverInProgress(download_manager, wait_count) { + } + + virtual ~JustInProgressDownloadObserver() {} + + private: + virtual bool IsDownloadInFinalState(DownloadItem* item) OVERRIDE { + return item->GetState() == DownloadItem::IN_PROGRESS; + } + + DISALLOW_COPY_AND_ASSIGN(JustInProgressDownloadObserver); +}; + +bool ItemIsInterrupted(DownloadItem* item) { + return item->GetState() == DownloadItem::INTERRUPTED; +} + +content::DownloadInterruptReason InterruptReasonExtensionToContent( + api::InterruptReason error) { + switch (error) { + case api::INTERRUPT_REASON_NONE: + return content::DOWNLOAD_INTERRUPT_REASON_NONE; +#define INTERRUPT_REASON(name, value) \ + case api::INTERRUPT_REASON_##name: \ + return content::DOWNLOAD_INTERRUPT_REASON_##name; +#include "content/public/browser/download_interrupt_reason_values.h" +#undef INTERRUPT_REASON + } + NOTREACHED(); + return content::DOWNLOAD_INTERRUPT_REASON_NONE; +} + +api::InterruptReason InterruptReasonContentToExtension( + content::DownloadInterruptReason error) { + switch (error) { + case content::DOWNLOAD_INTERRUPT_REASON_NONE: + return api::INTERRUPT_REASON_NONE; +#define INTERRUPT_REASON(name, value) \ + case content::DOWNLOAD_INTERRUPT_REASON_##name: \ + return api::INTERRUPT_REASON_##name; +#include "content/public/browser/download_interrupt_reason_values.h" +#undef INTERRUPT_REASON + } + NOTREACHED(); + return api::INTERRUPT_REASON_NONE; +} + +} // namespace + +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Open) { + LoadExtension("downloads_split"); + EXPECT_STREQ(errors::kInvalidId, + RunFunctionAndReturnError( + new DownloadsOpenFunction(), + "[-42]").c_str()); + + DownloadItem* download_item = CreateSlowTestDownload(); + ASSERT_TRUE(download_item); + EXPECT_FALSE(download_item->GetOpened()); + EXPECT_FALSE(download_item->GetOpenWhenComplete()); + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"application/octet-stream\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_item->GetURL().spec().c_str()))); + EXPECT_STREQ(errors::kNotComplete, + RunFunctionAndReturnError( + new DownloadsOpenFunction(), + DownloadItemIdAsArgList(download_item)).c_str()); + + FinishPendingSlowDownloads(); + EXPECT_FALSE(download_item->GetOpened()); + EXPECT_TRUE(RunFunction(new DownloadsOpenFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_TRUE(download_item->GetOpened()); +} + +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_PauseResumeCancelErase) { + DownloadItem* download_item = CreateSlowTestDownload(); + ASSERT_TRUE(download_item); + + // Call pause(). It should succeed and the download should be paused on + // return. + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_TRUE(download_item->IsPaused()); + + // Calling pause() twice shouldn't be an error. + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_TRUE(download_item->IsPaused()); + + // Now try resuming this download. It should succeed. + EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_FALSE(download_item->IsPaused()); + + // Resume again. Resuming a download that wasn't paused is not an error. + EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_FALSE(download_item->IsPaused()); + + // Pause again. + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_TRUE(download_item->IsPaused()); + + // And now cancel. + EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState()); + + // Cancel again. Shouldn't have any effect. + EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), + DownloadItemIdAsArgList(download_item))); + EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState()); + + // Calling paused on a non-active download yields kInvalidId. + std::string error = RunFunctionAndReturnError( + new DownloadsPauseFunction(), DownloadItemIdAsArgList(download_item)); + EXPECT_STREQ(errors::kNotInProgress, error.c_str()); + + // Calling resume on a non-active download yields kInvalidId + error = RunFunctionAndReturnError( + new DownloadsResumeFunction(), DownloadItemIdAsArgList(download_item)); + EXPECT_STREQ(errors::kNotResumable, error.c_str()); + + // Calling paused on a non-existent download yields kInvalidId. + error = RunFunctionAndReturnError( + new DownloadsPauseFunction(), "[-42]"); + EXPECT_STREQ(errors::kInvalidId, error.c_str()); + + // Calling resume on a non-existent download yields kInvalidId + error = RunFunctionAndReturnError( + new DownloadsResumeFunction(), "[-42]"); + EXPECT_STREQ(errors::kInvalidId, error.c_str()); + + int id = download_item->GetId(); + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsEraseFunction(), + base::StringPrintf("[{\"id\": %d}]", id))); + DownloadManager::DownloadVector items; + GetCurrentManager()->GetAllDownloads(&items); + EXPECT_EQ(0UL, items.size()); + ASSERT_TRUE(result); + download_item = NULL; + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); + int element = -1; + ASSERT_TRUE(result_list->GetInteger(0, &element)); + EXPECT_EQ(id, element); +} + +scoped_refptr MockedGetFileIconFunction( + const base::FilePath& expected_path, + IconLoader::IconSize icon_size, + const std::string& response) { + scoped_refptr function( + new DownloadsGetFileIconFunction()); + function->SetIconExtractorForTesting(new MockIconExtractorImpl( + expected_path, icon_size, response)); + return function; +} + +// Test downloads.getFileIcon() on in-progress, finished, cancelled and deleted +// download items. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_FileIcon_Active) { + DownloadItem* download_item = CreateSlowTestDownload(); + ASSERT_TRUE(download_item); + ASSERT_FALSE(download_item->GetTargetFilePath().empty()); + std::string args32(base::StringPrintf("[%d, {\"size\": 32}]", + download_item->GetId())); + std::string result_string; + + // Get the icon for the in-progress download. This call should succeed even + // if the file type isn't registered. + // Test whether the correct path is being pased into the icon extractor. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + base::StringPrintf("[%d, {}]", download_item->GetId()), &result_string)); + + // Now try a 16x16 icon. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + download_item->GetTargetFilePath(), IconLoader::SMALL, "foo"), + base::StringPrintf("[%d, {\"size\": 16}]", download_item->GetId()), + &result_string)); + + // Explicitly asking for 32x32 should give us a 32x32 icon. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + args32, &result_string)); + + // Finish the download and try again. + FinishPendingSlowDownloads(); + EXPECT_EQ(DownloadItem::COMPLETE, download_item->GetState()); + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + args32, &result_string)); + + // Check the path passed to the icon extractor post-completion. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + args32, &result_string)); + + // Now create another download. + download_item = CreateSlowTestDownload(); + ASSERT_TRUE(download_item); + ASSERT_FALSE(download_item->GetTargetFilePath().empty()); + args32 = base::StringPrintf("[%d, {\"size\": 32}]", download_item->GetId()); + + // Cancel the download. As long as the download has a target path, we should + // be able to query the file icon. + download_item->Cancel(true); + ASSERT_FALSE(download_item->GetTargetFilePath().empty()); + // Let cleanup complete on the FILE thread. + content::RunAllPendingInMessageLoop(BrowserThread::FILE); + // Check the path passed to the icon extractor post-cancellation. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + args32, + &result_string)); + + // Simulate an error during icon load by invoking the mock with an empty + // result string. + std::string error = RunFunctionAndReturnError( + MockedGetFileIconFunction(download_item->GetTargetFilePath(), + IconLoader::NORMAL, + std::string()), + args32); + EXPECT_STREQ(errors::kIconNotFound, error.c_str()); + + // Once the download item is deleted, we should return kInvalidId. + int id = download_item->GetId(); + download_item->Remove(); + download_item = NULL; + EXPECT_EQ(static_cast(NULL), + GetCurrentManager()->GetDownload(id)); + error = RunFunctionAndReturnError(new DownloadsGetFileIconFunction(), args32); + EXPECT_STREQ(errors::kInvalidId, + error.c_str()); +} + +// Test that we can acquire file icons for history downloads regardless of +// whether they exist or not. If the file doesn't exist we should receive a +// generic icon from the OS/toolkit that may or may not be specific to the file +// type. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_FileIcon_History) { + const HistoryDownloadInfo kHistoryInfo[] = { + { FILE_PATH_LITERAL("real.txt"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, + { FILE_PATH_LITERAL("fake.txt"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } + }; + DownloadManager::DownloadVector all_downloads; + ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), + &all_downloads)); + + base::FilePath real_path = all_downloads[0]->GetTargetFilePath(); + base::FilePath fake_path = all_downloads[1]->GetTargetFilePath(); + + EXPECT_EQ(0, file_util::WriteFile(real_path, "", 0)); + ASSERT_TRUE(base::PathExists(real_path)); + ASSERT_FALSE(base::PathExists(fake_path)); + + for (DownloadManager::DownloadVector::iterator iter = all_downloads.begin(); + iter != all_downloads.end(); + ++iter) { + std::string result_string; + // Use a MockIconExtractorImpl to test if the correct path is being passed + // into the DownloadFileIconExtractor. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + (*iter)->GetTargetFilePath(), IconLoader::NORMAL, "hello"), + base::StringPrintf("[%d, {\"size\": 32}]", (*iter)->GetId()), + &result_string)); + EXPECT_STREQ("hello", result_string.c_str()); + } +} + +// Test passing the empty query to search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchEmptyQuery) { + ScopedCancellingItem item(CreateSlowTestDownload()); + ASSERT_TRUE(item.get()); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); +} + +// Test the |filenameRegex| parameter for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchFilenameRegex) { + const HistoryDownloadInfo kHistoryInfo[] = { + { FILE_PATH_LITERAL("foobar"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, + { FILE_PATH_LITERAL("baz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } + }; + DownloadManager::DownloadVector all_downloads; + ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), + &all_downloads)); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{\"filenameRegex\": \"foobar\"}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); + base::DictionaryValue* item_value = NULL; + ASSERT_TRUE(result_list->GetDictionary(0, &item_value)); + int item_id = -1; + ASSERT_TRUE(item_value->GetInteger("id", &item_id)); + ASSERT_EQ(all_downloads[0]->GetId(), static_cast(item_id)); +} + +// Test the |id| parameter for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchId) { + DownloadManager::DownloadVector items; + CreateSlowTestDownloads(2, &items); + ScopedItemVectorCanceller delete_items(&items); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), base::StringPrintf( + "[{\"id\": %u}]", items[0]->GetId()))); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); + base::DictionaryValue* item_value = NULL; + ASSERT_TRUE(result_list->GetDictionary(0, &item_value)); + int item_id = -1; + ASSERT_TRUE(item_value->GetInteger("id", &item_id)); + ASSERT_EQ(items[0]->GetId(), static_cast(item_id)); +} + +// Test specifying both the |id| and |filename| parameters for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchIdAndFilename) { + DownloadManager::DownloadVector items; + CreateSlowTestDownloads(2, &items); + ScopedItemVectorCanceller delete_items(&items); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), + "[{\"id\": 0, \"filename\": \"foobar\"}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(0UL, result_list->GetSize()); +} + +// Test a single |orderBy| parameter for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchOrderBy) { + const HistoryDownloadInfo kHistoryInfo[] = { + { FILE_PATH_LITERAL("zzz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, + { FILE_PATH_LITERAL("baz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } + }; + DownloadManager::DownloadVector items; + ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), + &items)); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{\"orderBy\": [\"filename\"]}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(2UL, result_list->GetSize()); + base::DictionaryValue* item0_value = NULL; + base::DictionaryValue* item1_value = NULL; + ASSERT_TRUE(result_list->GetDictionary(0, &item0_value)); + ASSERT_TRUE(result_list->GetDictionary(1, &item1_value)); + std::string item0_name, item1_name; + ASSERT_TRUE(item0_value->GetString("filename", &item0_name)); + ASSERT_TRUE(item1_value->GetString("filename", &item1_name)); + ASSERT_GT(items[0]->GetTargetFilePath().value(), + items[1]->GetTargetFilePath().value()); + ASSERT_LT(item0_name, item1_name); +} + +// Test specifying an empty |orderBy| parameter for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchOrderByEmpty) { + const HistoryDownloadInfo kHistoryInfo[] = { + { FILE_PATH_LITERAL("zzz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, + { FILE_PATH_LITERAL("baz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } + }; + DownloadManager::DownloadVector items; + ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), + &items)); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{\"orderBy\": []}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(2UL, result_list->GetSize()); + base::DictionaryValue* item0_value = NULL; + base::DictionaryValue* item1_value = NULL; + ASSERT_TRUE(result_list->GetDictionary(0, &item0_value)); + ASSERT_TRUE(result_list->GetDictionary(1, &item1_value)); + std::string item0_name, item1_name; + ASSERT_TRUE(item0_value->GetString("filename", &item0_name)); + ASSERT_TRUE(item1_value->GetString("filename", &item1_name)); + ASSERT_GT(items[0]->GetTargetFilePath().value(), + items[1]->GetTargetFilePath().value()); + ASSERT_GT(item0_name, item1_name); +} + +// Test the |danger| option for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchDanger) { + const HistoryDownloadInfo kHistoryInfo[] = { + { FILE_PATH_LITERAL("zzz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, + { FILE_PATH_LITERAL("baz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } + }; + DownloadManager::DownloadVector items; + ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), + &items)); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{\"danger\": \"content\"}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); +} + +// Test the |state| option for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchState) { + DownloadManager::DownloadVector items; + CreateSlowTestDownloads(2, &items); + ScopedItemVectorCanceller delete_items(&items); + + items[0]->Cancel(true); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{\"state\": \"in_progress\"}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); +} + +// Test the |limit| option for search(). +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchLimit) { + DownloadManager::DownloadVector items; + CreateSlowTestDownloads(2, &items); + ScopedItemVectorCanceller delete_items(&items); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{\"limit\": 1}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); +} + +// Test invalid search parameters. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchInvalid) { + std::string error = RunFunctionAndReturnError( + new DownloadsSearchFunction(), "[{\"filenameRegex\": \"(\"}]"); + EXPECT_STREQ(errors::kInvalidFilter, + error.c_str()); + error = RunFunctionAndReturnError( + new DownloadsSearchFunction(), "[{\"orderBy\": [\"goat\"]}]"); + EXPECT_STREQ(errors::kInvalidOrderBy, + error.c_str()); + error = RunFunctionAndReturnError( + new DownloadsSearchFunction(), "[{\"limit\": -1}]"); + EXPECT_STREQ(errors::kInvalidQueryLimit, + error.c_str()); +} + +// Test searching using multiple conditions through multiple downloads. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchPlural) { + const HistoryDownloadInfo kHistoryInfo[] = { + { FILE_PATH_LITERAL("aaa"), + DownloadItem::CANCELLED, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, + { FILE_PATH_LITERAL("zzz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, + { FILE_PATH_LITERAL("baz"), + DownloadItem::COMPLETE, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, + }; + DownloadManager::DownloadVector items; + ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), + &items)); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{" + "\"state\": \"complete\", " + "\"danger\": \"content\", " + "\"orderBy\": [\"filename\"], " + "\"limit\": 1}]")); + ASSERT_TRUE(result.get()); + base::ListValue* result_list = NULL; + ASSERT_TRUE(result->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); + base::DictionaryValue* item_value = NULL; + ASSERT_TRUE(result_list->GetDictionary(0, &item_value)); + base::FilePath::StringType item_name; + ASSERT_TRUE(item_value->GetString("filename", &item_name)); + ASSERT_EQ(items[2]->GetTargetFilePath().value(), item_name); +} + +// Test that incognito downloads are only visible in incognito contexts, and +// test that on-record downloads are visible in both incognito and on-record +// contexts, for DownloadsSearchFunction, DownloadsPauseFunction, +// DownloadsResumeFunction, and DownloadsCancelFunction. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SearchPauseResumeCancelGetFileIconIncognito) { + scoped_ptr result_value; + base::ListValue* result_list = NULL; + base::DictionaryValue* result_dict = NULL; + base::FilePath::StringType filename; + bool is_incognito = false; + std::string error; + std::string on_item_arg; + std::string off_item_arg; + std::string result_string; + + // Set up one on-record item and one off-record item. + // Set up the off-record item first because otherwise there are mysteriously 3 + // items total instead of 2. + // TODO(benjhayden): Figure out where the third item comes from. + GoOffTheRecord(); + DownloadItem* off_item = CreateSlowTestDownload(); + ASSERT_TRUE(off_item); + off_item_arg = DownloadItemIdAsArgList(off_item); + + GoOnTheRecord(); + DownloadItem* on_item = CreateSlowTestDownload(); + ASSERT_TRUE(on_item); + on_item_arg = DownloadItemIdAsArgList(on_item); + ASSERT_TRUE(on_item->GetTargetFilePath() != off_item->GetTargetFilePath()); + + // Extensions running in the incognito window should have access to both + // items because the Test extension is in spanning mode. + GoOffTheRecord(); + result_value.reset(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{}]")); + ASSERT_TRUE(result_value.get()); + ASSERT_TRUE(result_value->GetAsList(&result_list)); + ASSERT_EQ(2UL, result_list->GetSize()); + ASSERT_TRUE(result_list->GetDictionary(0, &result_dict)); + ASSERT_TRUE(result_dict->GetString("filename", &filename)); + ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito)); + EXPECT_TRUE(on_item->GetTargetFilePath() == base::FilePath(filename)); + EXPECT_FALSE(is_incognito); + ASSERT_TRUE(result_list->GetDictionary(1, &result_dict)); + ASSERT_TRUE(result_dict->GetString("filename", &filename)); + ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito)); + EXPECT_TRUE(off_item->GetTargetFilePath() == base::FilePath(filename)); + EXPECT_TRUE(is_incognito); + + // Extensions running in the on-record window should have access only to the + // on-record item. + GoOnTheRecord(); + result_value.reset(RunFunctionAndReturnResult( + new DownloadsSearchFunction(), "[{}]")); + ASSERT_TRUE(result_value.get()); + ASSERT_TRUE(result_value->GetAsList(&result_list)); + ASSERT_EQ(1UL, result_list->GetSize()); + ASSERT_TRUE(result_list->GetDictionary(0, &result_dict)); + ASSERT_TRUE(result_dict->GetString("filename", &filename)); + EXPECT_TRUE(on_item->GetTargetFilePath() == base::FilePath(filename)); + ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito)); + EXPECT_FALSE(is_incognito); + + // Pausing/Resuming the off-record item while on the record should return an + // error. Cancelling "non-existent" downloads is not an error. + error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg); + EXPECT_STREQ(errors::kInvalidId, + error.c_str()); + error = RunFunctionAndReturnError(new DownloadsResumeFunction(), + off_item_arg); + EXPECT_STREQ(errors::kInvalidId, + error.c_str()); + error = RunFunctionAndReturnError( + new DownloadsGetFileIconFunction(), + base::StringPrintf("[%d, {}]", off_item->GetId())); + EXPECT_STREQ(errors::kInvalidId, + error.c_str()); + + GoOffTheRecord(); + + // Do the FileIcon test for both the on- and off-items while off the record. + // NOTE(benjhayden): This does not include the FileIcon test from history, + // just active downloads. This shouldn't be a problem. + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + on_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + base::StringPrintf("[%d, {}]", on_item->GetId()), &result_string)); + EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( + off_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), + base::StringPrintf("[%d, {}]", off_item->GetId()), &result_string)); + + // Do the pause/resume/cancel test for both the on- and off-items while off + // the record. + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg)); + EXPECT_TRUE(on_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg)); + EXPECT_TRUE(on_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), on_item_arg)); + EXPECT_FALSE(on_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), on_item_arg)); + EXPECT_FALSE(on_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg)); + EXPECT_TRUE(on_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg)); + EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState()); + EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg)); + EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState()); + error = RunFunctionAndReturnError(new DownloadsPauseFunction(), on_item_arg); + EXPECT_STREQ(errors::kNotInProgress, error.c_str()); + error = RunFunctionAndReturnError(new DownloadsResumeFunction(), on_item_arg); + EXPECT_STREQ(errors::kNotResumable, error.c_str()); + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg)); + EXPECT_TRUE(off_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg)); + EXPECT_TRUE(off_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), off_item_arg)); + EXPECT_FALSE(off_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), off_item_arg)); + EXPECT_FALSE(off_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg)); + EXPECT_TRUE(off_item->IsPaused()); + EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg)); + EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState()); + EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg)); + EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState()); + error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg); + EXPECT_STREQ(errors::kNotInProgress, error.c_str()); + error = RunFunctionAndReturnError(new DownloadsResumeFunction(), + off_item_arg); + EXPECT_STREQ(errors::kNotResumable, error.c_str()); +} + +// Test that we can start a download and that the correct sequence of events is +// fired for it. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Basic) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + GoOnTheRecord(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Test that we can start a download from an incognito context, and that the +// download knows that it's incognito. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Incognito) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + GoOffTheRecord(); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": true," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\":%d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\":%d," + " \"state\": {" + " \"current\": \"complete\"," + " \"previous\": \"in_progress\"}}]", + result_id))); +} + +#if defined(OS_WIN) && defined(USE_AURA) +// This test is very flaky on Win Aura. http://crbug.com/248438 +#define MAYBE_DownloadExtensionTest_Download_UnsafeHeaders \ + DISABLED_DownloadExtensionTest_Download_UnsafeHeaders +#else +#define MAYBE_DownloadExtensionTest_Download_UnsafeHeaders \ + DownloadExtensionTest_Download_UnsafeHeaders +#endif + +// Test that we disallow certain headers case-insensitively. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + MAYBE_DownloadExtensionTest_Download_UnsafeHeaders) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + GoOnTheRecord(); + + static const char* kUnsafeHeaders[] = { + "Accept-chArsEt", + "accept-eNcoding", + "coNNection", + "coNteNt-leNgth", + "cooKIE", + "cOOkie2", + "coNteNt-traNsfer-eNcodiNg", + "dAtE", + "ExpEcT", + "hOsT", + "kEEp-aLivE", + "rEfErEr", + "tE", + "trAilER", + "trANsfer-eNcodiNg", + "upGRAde", + "usER-agENt", + "viA", + "pRoxY-", + "sEc-", + "pRoxY-probably-not-evil", + "sEc-probably-not-evil", + "oRiGiN", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + }; + + for (size_t index = 0; index < arraysize(kUnsafeHeaders); ++index) { + std::string download_url = test_server()->GetURL("slow?0").spec(); + EXPECT_STREQ(errors::kInvalidHeader, + RunFunctionAndReturnError(new DownloadsDownloadFunction(), + base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"unsafe-header-%d.txt\"," + " \"headers\": [{" + " \"name\": \"%s\"," + " \"value\": \"unsafe\"}]}]", + download_url.c_str(), + static_cast(index), + kUnsafeHeaders[index])).c_str()); + } +} + +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Subdirectory) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"sub/dir/ect/ory.txt\"}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("sub/dir/ect/ory.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Test that invalid filenames are disallowed. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_InvalidFilename) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + GoOnTheRecord(); + + EXPECT_STREQ(errors::kInvalidFilename, + RunFunctionAndReturnError(new DownloadsDownloadFunction(), + base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"../../../../../etc/passwd\"}]", + download_url.c_str())).c_str()); +} + +// Test that downloading invalid URLs immediately returns kInvalidURLError. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_InvalidURLs) { + LoadExtension("downloads_split"); + GoOnTheRecord(); + + static const char* kInvalidURLs[] = { + "foo bar", + "../hello", + "/hello", + "http://", + "#frag", + "foo/bar.html#frag", + "google.com/", + }; + + for (size_t index = 0; index < arraysize(kInvalidURLs); ++index) { + EXPECT_STREQ(errors::kInvalidURL, + RunFunctionAndReturnError(new DownloadsDownloadFunction(), + base::StringPrintf( + "[{\"url\": \"%s\"}]", kInvalidURLs[index])).c_str()) + << kInvalidURLs[index]; + } + + EXPECT_STREQ("net::ERR_ACCESS_DENIED", RunFunctionAndReturnError( + new DownloadsDownloadFunction(), + "[{\"url\": \"javascript:document.write(\\\"hello\\\");\"}]").c_str()); + EXPECT_STREQ("net::ERR_ACCESS_DENIED", RunFunctionAndReturnError( + new DownloadsDownloadFunction(), + "[{\"url\": \"javascript:return false;\"}]").c_str()); + EXPECT_STREQ("net::ERR_NOT_IMPLEMENTED", RunFunctionAndReturnError( + new DownloadsDownloadFunction(), + "[{\"url\": \"ftp://example.com/example.txt\"}]").c_str()); +} + +// TODO(benjhayden): Set up a test ftp server, add ftp://localhost* to +// permissions, test downloading from ftp. + +// Valid URLs plus fragments are still valid URLs. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_URLFragment) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0#fragment").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Valid data URLs are valid URLs. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_DataURL) { + LoadExtension("downloads_split"); + std::string download_url = "data:text/plain,hello"; + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"data.txt\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("data.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Valid file URLs are valid URLs. +#if defined(OS_WIN) && defined(USE_AURA) +// Disabled due to crbug.com/175711 +#define MAYBE_DownloadExtensionTest_Download_File \ + DISABLED_DownloadExtensionTest_Download_File +#else +#define MAYBE_DownloadExtensionTest_Download_File \ + DownloadExtensionTest_Download_File +#endif +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + MAYBE_DownloadExtensionTest_Download_File) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + std::string download_url = "file:///"; +#if defined(OS_WIN) + download_url += "C:/"; +#endif + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"file.txt\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/html\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("file.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Test that auth-basic-succeed would fail if the resource requires the +// Authorization header and chrome fails to propagate it back to the server. +// This tests both that testserver.py does not succeed when it should fail as +// well as how the downloads extension API exposes the failure to extensions. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_AuthBasic_Fail) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("auth-basic").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"auth-basic-fail.txt\"}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitForInterruption( + item, + content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/html\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); +} + +// Test that DownloadsDownloadFunction propagates |headers| to the URLRequest. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Headers) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("files/downloads/" + "a_zip_file.zip?expected_headers=Foo:bar&expected_headers=Qx:yo").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"headers-succeed.txt\"," + " \"headers\": [" + " {\"name\": \"Foo\", \"value\": \"bar\"}," + " {\"name\": \"Qx\", \"value\":\"yo\"}]}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"application/octet-stream\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("headers-succeed.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Test that headers-succeed would fail if the resource requires the headers and +// chrome fails to propagate them back to the server. This tests both that +// testserver.py does not succeed when it should fail as well as how the +// downloads extension api exposes the failure to extensions. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Headers_Fail) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("files/downloads/" + "a_zip_file.zip?expected_headers=Foo:bar&expected_headers=Qx:yo").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"headers-fail.txt\"}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitForInterruption( + item, + content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"bytesReceived\": 0," + " \"mime\": \"\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); +} + +// Test that DownloadsDownloadFunction propagates the Authorization header +// correctly. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_AuthBasic) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("auth-basic").spec(); + // This is just base64 of 'username:secret'. + static const char* kAuthorization = "dXNlcm5hbWU6c2VjcmV0"; + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"auth-basic-succeed.txt\"," + " \"headers\": [{" + " \"name\": \"Authorization\"," + " \"value\": \"Basic %s\"}]}]", + download_url.c_str(), kAuthorization))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/html\"," + " \"paused\": false," + " \"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", result_id))); +} + +// Test that DownloadsDownloadFunction propagates the |method| and |body| +// parameters to the URLRequest. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Post) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("files/post/downloads/" + "a_zip_file.zip?expected_body=BODY").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"filename\": \"post-succeed.txt\"," + " \"method\": \"POST\"," + " \"body\": \"BODY\"}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"application/octet-stream\"," + " \"paused\": false," + " \"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("post-succeed.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// Test that downloadPostSuccess would fail if the resource requires the POST +// method, and chrome fails to propagate the |method| parameter back to the +// server. This tests both that testserver.py does not succeed when it should +// fail, and this tests how the downloads extension api exposes the failure to +// extensions. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Post_Get) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("files/post/downloads/" + "a_zip_file.zip?expected_body=BODY").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"body\": \"BODY\"," + " \"filename\": \"post-get.txt\"}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitForInterruption( + item, + content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"\"," + " \"paused\": false," + " \"id\": %d," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); +} + +// Test that downloadPostSuccess would fail if the resource requires the POST +// method, and chrome fails to propagate the |body| parameter back to the +// server. This tests both that testserver.py does not succeed when it should +// fail, and this tests how the downloads extension api exposes the failure to +// extensions. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Post_NoBody) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("files/post/downloads/" + "a_zip_file.zip?expected_body=BODY").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"," + " \"method\": \"POST\"," + " \"filename\": \"post-nobody.txt\"}]", + download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitForInterruption( + item, + content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"\"," + " \"paused\": false," + " \"id\": %d," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); +} + +// Test that cancel()ing an in-progress download causes its state to transition +// to interrupted, and test that that state transition is detectable by an +// onChanged event listener. TODO(benjhayden): Test other sources of +// interruptions such as server death. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_Cancel) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL( + "download-known-size").spec(); + GoOnTheRecord(); + + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"application/octet-stream\"," + " \"paused\": false," + " \"id\": %d," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + item->Cancel(true); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"error\": {\"current\":\"USER_CANCELED\"}," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"interrupted\"}}]", + result_id))); +} + +// Test downloading filesystem: URLs. +// NOTE: chrome disallows creating HTML5 FileSystem Files in incognito. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_Download_FileSystemURL) { + static const char* kPayloadData = "on the record\ndata"; + GoOnTheRecord(); + LoadExtension("downloads_split"); + HTML5FileWriter html5_file_writer( + browser()->profile(), + "on_record.txt", + GetExtensionURL(), + events_listener(), + kPayloadData); + ASSERT_TRUE(html5_file_writer.WriteFile()); + + std::string download_url = "filesystem:" + GetExtensionURL() + + "temporary/on_record.txt"; + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + download_url.c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("on_record.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); + std::string disk_data; + EXPECT_TRUE(file_util::ReadFileToString(item->GetTargetFilePath(), + &disk_data)); + EXPECT_STREQ(kPayloadData, disk_data.c_str()); +} + +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_NoChange) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + // Wait for the onCreated and onDeterminingFilename events. + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + // The download should complete successfully. + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_DangerousOverride) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("overridden.swf")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"danger\": {" + " \"previous\":\"safe\"," + " \"current\":\"file\"}}]", + result_id))); + + item->ValidateDangerousDownload(); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"danger\": {" + " \"previous\":\"file\"," + " \"current\":\"accepted\"}}]", + result_id))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); + EXPECT_EQ(downloads_directory().AppendASCII("overridden.swf"), + item->GetTargetFilePath()); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_ReferencesParentInvalid) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("sneaky/../../sneaky.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_IllegalFilename) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("<")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( + "[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( + "[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_IllegalFilenameExtension) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL( + "My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}/foo")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( + "[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( + "[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_ReservedFilename) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("con.foo")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( + "[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( + "[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_CurDirInvalid) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL(".")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_ParentDirInvalid) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("..")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_AbsPathInvalid) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. Absolute paths should be rejected. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + downloads_directory().Append(FILE_PATH_LITERAL("sneaky.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_EmptyBasenameInvalid) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. Empty basenames should be rejected. + std::string error; + ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("foo/")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_STREQ(errors::kInvalidFilename, error.c_str()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_Override) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + std::string error; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("slow.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); + + // Start downloading a file. + result.reset(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller2(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + // Also test that DetermineFilename allows (chrome) extensions to set + // filenames without (filename) extensions. (Don't ask about v8 extensions or + // python extensions or kernel extensions or firefox extensions...) + error = ""; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("foo")), + api::FILENAME_CONFLICT_ACTION_OVERWRITE, + &error)); + EXPECT_EQ("", error); + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("foo").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +// TODO test precedence rules: install_time + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_RemoveFilenameDeterminer) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + GoOnTheRecord(); + LoadExtension("downloads_split"); + content::RenderProcessHost* host = AddFilenameDeterminer(); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + // Start downloading a file. + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Remove a determiner while waiting for it. + RemoveFilenameDeterminer(host); + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_IncognitoSplit) { + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + GoOnTheRecord(); + AddFilenameDeterminer(); + + GoOffTheRecord(); + AddFilenameDeterminer(); + + // Start an on-record download. + GoOnTheRecord(); + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + // Wait for the onCreated and onDeterminingFilename events. + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"incognito\": false," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename events. + std::string error; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + current_browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("42.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + // The download should complete successfully. + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("42.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); + + // Start an incognito download for comparison. + GoOffTheRecord(); + result.reset(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller2(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": true," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + // On-Record renderers should not see events for off-record items. + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"incognito\": true," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + error = ""; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + current_browser()->profile(), + false, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("5.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + // The download should complete successfully. + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("5.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + DownloadExtensionTest_OnDeterminingFilename_IncognitoSpanning) { + LoadExtension("downloads_spanning"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + std::string download_url = test_server()->GetURL("slow?0").spec(); + + GoOnTheRecord(); + AddFilenameDeterminer(); + + // There is a single extension renderer that sees both on-record and + // off-record events. The extension functions see the on-record profile with + // include_incognito=true. + + // Start an on-record download. + GoOnTheRecord(); + scoped_ptr result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + // Wait for the onCreated and onDeterminingFilename events. + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"incognito\": false," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename events. + std::string error; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + current_browser()->profile(), + true, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("42.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + // The download should complete successfully. + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("42.txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); + + // Start an incognito download for comparison. + GoOffTheRecord(); + result.reset(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), base::StringPrintf( + "[{\"url\": \"%s\"}]", download_url.c_str()))); + ASSERT_TRUE(result.get()); + result_id = -1; + ASSERT_TRUE(result->GetAsInteger(&result_id)); + item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller2(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": true," + " \"id\": %d," + " \"mime\": \"text/plain\"," + " \"paused\": false," + " \"url\": \"%s\"}]", + result_id, + download_url.c_str()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"incognito\": true," + " \"filename\":\"slow.txt\"}]", + result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Respond to the onDeterminingFilename. + error = ""; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + current_browser()->profile(), + true, + GetExtensionId(), + result_id, + base::FilePath(FILE_PATH_LITERAL("42.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)); + EXPECT_EQ("", error); + + // The download should complete successfully. + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + result_id, + GetFilename("42 (1).txt").c_str()))); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + result_id))); +} + +#if defined(OS_WIN) +// This test is very flaky on Win XP and Aura. http://crbug.com/248438 +#define MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume \ + DISABLED_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume +#else +#define MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume \ + DownloadExtensionTest_OnDeterminingFilename_InterruptedResume +#endif + +// Test download interruption while extensions determining filename. Should not +// re-dispatch onDeterminingFilename. +IN_PROC_BROWSER_TEST_F( + DownloadExtensionTest, + MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableDownloadResumption); + LoadExtension("downloads_split"); + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(test_server()->Start()); + GoOnTheRecord(); + content::RenderProcessHost* host = AddFilenameDeterminer(); + + // Start a download. + DownloadItem* item = NULL; + { + DownloadManager* manager = GetCurrentManager(); + scoped_ptr observer( + new JustInProgressDownloadObserver(manager, 1)); + ASSERT_EQ(0, manager->InProgressCount()); + // Tabs created just for a download are automatically closed, invalidating + // the download's WebContents. Downloads without WebContents cannot be + // resumed. http://crbug.com/225901 + ui_test_utils::NavigateToURLWithDisposition( + current_browser(), + GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl), + CURRENT_TAB, + ui_test_utils::BROWSER_TEST_NONE); + observer->WaitForFinished(); + EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS)); + DownloadManager::DownloadVector items; + manager->GetAllDownloads(&items); + for (DownloadManager::DownloadVector::iterator iter = items.begin(); + iter != items.end(); ++iter) { + if ((*iter)->GetState() == DownloadItem::IN_PROGRESS) { + // There should be only one IN_PROGRESS item. + EXPECT_EQ(NULL, item); + item = *iter; + } + } + ASSERT_TRUE(item); + } + ScopedCancellingItem canceller(item); + + // Wait for the onCreated and onDeterminingFilename event. + ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, + base::StringPrintf("[{\"danger\": \"safe\"," + " \"incognito\": false," + " \"id\": %d," + " \"mime\": \"application/octet-stream\"," + " \"paused\": false}]", + item->GetId()))); + ASSERT_TRUE(WaitFor( + events::kOnDownloadDeterminingFilename, + base::StringPrintf("[{\"id\": %d," + " \"incognito\": false," + " \"filename\":\"download-unknown-size\"}]", + item->GetId()))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + ClearEvents(); + ui_test_utils::NavigateToURLWithDisposition( + current_browser(), + GURL(URLRequestSlowDownloadJob::kErrorDownloadUrl), + NEW_BACKGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + // Errors caught before filename determination are delayed until after + // filename determination. + std::string error; + ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( + current_browser()->profile(), + false, + GetExtensionId(), + item->GetId(), + base::FilePath(FILE_PATH_LITERAL("42.txt")), + api::FILENAME_CONFLICT_ACTION_UNIQUIFY, + &error)) << error; + EXPECT_EQ("", error); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"filename\": {" + " \"previous\": \"\"," + " \"current\": \"%s\"}}]", + item->GetId(), + GetFilename("42.txt").c_str()))); + + content::DownloadUpdatedObserver interrupted(item, base::Bind( + ItemIsInterrupted)); + ASSERT_TRUE(interrupted.WaitForEvent()); + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"error\":{\"current\":\"NETWORK_FAILED\"}," + " \"state\":{" + " \"previous\":\"in_progress\"," + " \"current\":\"interrupted\"}}]", + item->GetId()))); + + ClearEvents(); + // Downloads that are restarted on resumption trigger another download target + // determination. + RemoveFilenameDeterminer(host); + item->Resume(); + + // Errors caught before filename determination is complete are delayed until + // after filename determination so that, on resumption, filename determination + // does not need to be re-done. So, there will not be a second + // onDeterminingFilename event. + + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"error\":{\"previous\":\"NETWORK_FAILED\"}," + " \"state\":{" + " \"previous\":\"interrupted\"," + " \"current\":\"in_progress\"}}]", + item->GetId()))); + + ClearEvents(); + FinishPendingSlowDownloads(); + + // The download should complete successfully. + ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, + base::StringPrintf("[{\"id\": %d," + " \"state\": {" + " \"previous\": \"in_progress\"," + " \"current\": \"complete\"}}]", + item->GetId()))); +} + +// TODO(benjhayden) Figure out why DisableExtension() does not fire +// OnListenerRemoved. + +// TODO(benjhayden) Test that the shelf is shown for download() both with and +// without a WebContents. + +class DownloadsApiTest : public ExtensionApiTest { + public: + DownloadsApiTest() {} + virtual ~DownloadsApiTest() {} + private: + DISALLOW_COPY_AND_ASSIGN(DownloadsApiTest); +}; + + +IN_PROC_BROWSER_TEST_F(DownloadsApiTest, DownloadsApiTest) { + ASSERT_TRUE(RunExtensionTest("downloads")) << message_; +} + + +TEST(DownloadInterruptReasonEnumsSynced, + DownloadInterruptReasonEnumsSynced) { +#define INTERRUPT_REASON(name, value) \ + EXPECT_EQ(InterruptReasonContentToExtension( \ + content::DOWNLOAD_INTERRUPT_REASON_##name), \ + api::INTERRUPT_REASON_##name); \ + EXPECT_EQ(InterruptReasonExtensionToContent( \ + api::INTERRUPT_REASON_##name), \ + content::DOWNLOAD_INTERRUPT_REASON_##name); +#include "content/public/browser/download_interrupt_reason_values.h" +#undef INTERRUPT_REASON +} diff --git a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc b/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc deleted file mode 100644 index 93a40f2..0000000 --- a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc +++ /dev/null @@ -1,3544 +0,0 @@ -// 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 - -#include "base/file_util.h" -#include "base/files/scoped_temp_dir.h" -#include "base/json/json_reader.h" -#include "base/message_loop/message_loop.h" -#include "base/prefs/pref_service.h" -#include "base/stl_util.h" -#include "base/strings/stringprintf.h" -#include "chrome/browser/chrome_notification_types.h" -#include "chrome/browser/download/download_file_icon_extractor.h" -#include "chrome/browser/download/download_service.h" -#include "chrome/browser/download/download_service_factory.h" -#include "chrome/browser/download/download_test_file_activity_observer.h" -#include "chrome/browser/extensions/api/downloads/downloads_api.h" -#include "chrome/browser/extensions/event_names.h" -#include "chrome/browser/extensions/extension_apitest.h" -#include "chrome/browser/extensions/extension_function_test_utils.h" -#include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/history/download_row.h" -#include "chrome/browser/net/url_request_mock_util.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_tabstrip.h" -#include "chrome/common/pref_names.h" -#include "chrome/test/base/in_process_browser_test.h" -#include "chrome/test/base/ui_test_utils.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/download_item.h" -#include "content/public/browser/download_manager.h" -#include "content/public/browser/notification_service.h" -#include "content/public/browser/storage_partition.h" -#include "content/public/browser/web_contents.h" -#include "content/public/common/content_switches.h" -#include "content/public/common/page_transition_types.h" -#include "content/public/test/download_test_observer.h" -#include "content/test/net/url_request_slow_download_job.h" -#include "net/base/data_url.h" -#include "net/base/net_util.h" -#include "net/url_request/url_request.h" -#include "net/url_request/url_request_context.h" -#include "net/url_request/url_request_job.h" -#include "net/url_request/url_request_job_factory.h" -#include "net/url_request/url_request_job_factory_impl.h" -#include "webkit/browser/blob/blob_storage_controller.h" -#include "webkit/browser/blob/blob_url_request_job.h" -#include "webkit/browser/fileapi/file_system_context.h" -#include "webkit/browser/fileapi/file_system_operation_runner.h" -#include "webkit/browser/fileapi/file_system_url.h" -#include "webkit/common/blob/blob_data.h" - -using content::BrowserContext; -using content::BrowserThread; -using content::DownloadItem; -using content::DownloadManager; -using content::URLRequestSlowDownloadJob; - -namespace events = extensions::event_names; - -namespace { - -// Comparator that orders download items by their ID. Can be used with -// std::sort. -struct DownloadIdComparator { - bool operator() (DownloadItem* first, DownloadItem* second) { - return first->GetId() < second->GetId(); - } -}; - -class DownloadsEventsListener : public content::NotificationObserver { - public: - DownloadsEventsListener() - : waiting_(false) { - registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, - content::NotificationService::AllSources()); - } - - virtual ~DownloadsEventsListener() { - registrar_.Remove(this, chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, - content::NotificationService::AllSources()); - STLDeleteElements(&events_); - } - - void ClearEvents() { - STLDeleteElements(&events_); - events_.clear(); - } - - class Event { - public: - Event(Profile* profile, - const std::string& event_name, - const std::string& json_args, - base::Time caught) - : profile_(profile), - event_name_(event_name), - json_args_(json_args), - args_(base::JSONReader::Read(json_args)), - caught_(caught) { - } - - const base::Time& caught() { return caught_; } - - bool Satisfies(const Event& other) const { - return other.SatisfiedBy(*this); - } - - bool SatisfiedBy(const Event& other) const { - if ((profile_ != other.profile_) || - (event_name_ != other.event_name_)) - return false; - if (((event_name_ == events::kOnDownloadDeterminingFilename) || - (event_name_ == events::kOnDownloadCreated) || - (event_name_ == events::kOnDownloadChanged)) && - args_.get() && - other.args_.get()) { - base::ListValue* left_list = NULL; - base::DictionaryValue* left_dict = NULL; - base::ListValue* right_list = NULL; - base::DictionaryValue* right_dict = NULL; - if (!args_->GetAsList(&left_list) || - !other.args_->GetAsList(&right_list) || - !left_list->GetDictionary(0, &left_dict) || - !right_list->GetDictionary(0, &right_dict)) - return false; - for (base::DictionaryValue::Iterator iter(*left_dict); - !iter.IsAtEnd(); iter.Advance()) { - base::Value* right_value = NULL; - if (!right_dict->HasKey(iter.key()) || - (right_dict->Get(iter.key(), &right_value) && - !iter.value().Equals(right_value))) { - return false; - } - } - return true; - } else if ((event_name_ == events::kOnDownloadErased) && - args_.get() && - other.args_.get()) { - int my_id = -1, other_id = -1; - return (args_->GetAsInteger(&my_id) && - other.args_->GetAsInteger(&other_id) && - my_id == other_id); - } - return json_args_ == other.json_args_; - } - - std::string Debug() { - return base::StringPrintf("Event(%p, %s, %s, %f)", - profile_, - event_name_.c_str(), - json_args_.c_str(), - caught_.ToJsTime()); - } - - private: - Profile* profile_; - std::string event_name_; - std::string json_args_; - scoped_ptr args_; - base::Time caught_; - - DISALLOW_COPY_AND_ASSIGN(Event); - }; - - typedef ExtensionDownloadsEventRouter::DownloadsNotificationSource - DownloadsNotificationSource; - - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE { - switch (type) { - case chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT: - { - DownloadsNotificationSource* dns = - content::Source(source).ptr(); - Event* new_event = new Event( - dns->profile, - dns->event_name, - *content::Details(details).ptr(), base::Time::Now()); - events_.push_back(new_event); - if (waiting_ && - waiting_for_.get() && - new_event->Satisfies(*waiting_for_)) { - waiting_ = false; - base::MessageLoopForUI::current()->Quit(); - } - break; - } - default: - NOTREACHED(); - } - } - - bool WaitFor(Profile* profile, - const std::string& event_name, - const std::string& json_args) { - waiting_for_.reset(new Event(profile, event_name, json_args, base::Time())); - for (std::deque::const_iterator iter = events_.begin(); - iter != events_.end(); ++iter) { - if ((*iter)->Satisfies(*waiting_for_.get())) { - return true; - } - } - waiting_ = true; - content::RunMessageLoop(); - bool success = !waiting_; - if (waiting_) { - // Print the events that were caught since the last WaitFor() call to help - // find the erroneous event. - // TODO(benjhayden) Fuzzy-match and highlight the erroneous event. - for (std::deque::const_iterator iter = events_.begin(); - iter != events_.end(); ++iter) { - if ((*iter)->caught() > last_wait_) { - LOG(INFO) << "Caught " << (*iter)->Debug(); - } - } - if (waiting_for_.get()) { - LOG(INFO) << "Timed out waiting for " << waiting_for_->Debug(); - } - waiting_ = false; - } - waiting_for_.reset(); - last_wait_ = base::Time::Now(); - return success; - } - - private: - bool waiting_; - base::Time last_wait_; - scoped_ptr waiting_for_; - content::NotificationRegistrar registrar_; - std::deque events_; - - DISALLOW_COPY_AND_ASSIGN(DownloadsEventsListener); -}; - -class DownloadExtensionTest : public ExtensionApiTest { - public: - DownloadExtensionTest() - : extension_(NULL), - incognito_browser_(NULL), - current_browser_(NULL) { - } - - protected: - // Used with CreateHistoryDownloads - struct HistoryDownloadInfo { - // Filename to use. CreateHistoryDownloads will append this filename to the - // temporary downloads directory specified by downloads_directory(). - const base::FilePath::CharType* filename; - - // State for the download. Note that IN_PROGRESS downloads will be created - // as CANCELLED. - DownloadItem::DownloadState state; - - // Danger type for the download. Only use DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS - // and DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT. - content::DownloadDangerType danger_type; - }; - - void LoadExtension(const char* name) { - // Store the created Extension object so that we can attach it to - // ExtensionFunctions. Also load the extension in incognito profiles for - // testing incognito. - extension_ = LoadExtensionIncognito(test_data_dir_.AppendASCII(name)); - CHECK(extension_); - content::WebContents* tab = chrome::AddSelectedTabWithURL( - current_browser(), - extension_->GetResourceURL("empty.html"), - content::PAGE_TRANSITION_LINK); - extensions::ExtensionSystem::Get(current_browser()->profile())-> - event_router()->AddEventListener( - extensions::event_names::kOnDownloadCreated, - tab->GetRenderProcessHost(), - GetExtensionId()); - extensions::ExtensionSystem::Get(current_browser()->profile())-> - event_router()->AddEventListener( - extensions::event_names::kOnDownloadChanged, - tab->GetRenderProcessHost(), - GetExtensionId()); - extensions::ExtensionSystem::Get(current_browser()->profile())-> - event_router()->AddEventListener( - extensions::event_names::kOnDownloadErased, - tab->GetRenderProcessHost(), - GetExtensionId()); - } - - content::RenderProcessHost* AddFilenameDeterminer() { - content::WebContents* tab = chrome::AddSelectedTabWithURL( - current_browser(), - extension_->GetResourceURL("empty.html"), - content::PAGE_TRANSITION_LINK); - extensions::ExtensionSystem::Get(current_browser()->profile())-> - event_router()->AddEventListener( - extensions::event_names::kOnDownloadDeterminingFilename, - tab->GetRenderProcessHost(), - GetExtensionId()); - return tab->GetRenderProcessHost(); - } - - void RemoveFilenameDeterminer(content::RenderProcessHost* host) { - extensions::ExtensionSystem::Get(current_browser()->profile())-> - event_router()->RemoveEventListener( - extensions::event_names::kOnDownloadDeterminingFilename, - host, - GetExtensionId()); - } - - Browser* current_browser() { return current_browser_; } - - // InProcessBrowserTest - virtual void SetUpOnMainThread() OVERRIDE { - ExtensionApiTest::SetUpOnMainThread(); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); - InProcessBrowserTest::SetUpOnMainThread(); - GoOnTheRecord(); - CreateAndSetDownloadsDirectory(); - current_browser()->profile()->GetPrefs()->SetBoolean( - prefs::kPromptForDownload, false); - GetOnRecordManager()->RemoveAllDownloads(); - events_listener_.reset(new DownloadsEventsListener()); - // Disable file chooser for current profile. - DownloadTestFileActivityObserver observer(current_browser()->profile()); - observer.EnableFileChooser(false); - } - - void GoOnTheRecord() { current_browser_ = browser(); } - - void GoOffTheRecord() { - if (!incognito_browser_) { - incognito_browser_ = CreateIncognitoBrowser(); - GetOffRecordManager()->RemoveAllDownloads(); - // Disable file chooser for incognito profile. - DownloadTestFileActivityObserver observer(incognito_browser_->profile()); - observer.EnableFileChooser(false); - } - current_browser_ = incognito_browser_; - } - - bool WaitFor(const std::string& event_name, const std::string& json_args) { - return events_listener_->WaitFor( - current_browser()->profile(), event_name, json_args); - } - - bool WaitForInterruption(DownloadItem* item, int expected_error, - const std::string& on_created_event) { - if (!WaitFor(events::kOnDownloadCreated, on_created_event)) - return false; - // Now, onCreated is always fired before interruption. - return WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"error\": {\"current\": %d}," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"interrupted\"}}]", - item->GetId(), - expected_error)); - } - - void ClearEvents() { - events_listener_->ClearEvents(); - } - - std::string GetExtensionURL() { - return extension_->url().spec(); - } - std::string GetExtensionId() { - return extension_->id(); - } - - std::string GetFilename(const char* path) { - std::string result = - downloads_directory_.path().AppendASCII(path).AsUTF8Unsafe(); -#if defined(OS_WIN) - for (std::string::size_type next = result.find("\\"); - next != std::string::npos; - next = result.find("\\", next)) { - result.replace(next, 1, "\\\\"); - next += 2; - } -#endif - return result; - } - - DownloadManager* GetOnRecordManager() { - return BrowserContext::GetDownloadManager(browser()->profile()); - } - DownloadManager* GetOffRecordManager() { - return BrowserContext::GetDownloadManager( - browser()->profile()->GetOffTheRecordProfile()); - } - DownloadManager* GetCurrentManager() { - return (current_browser_ == incognito_browser_) ? - GetOffRecordManager() : GetOnRecordManager(); - } - - // Creates a set of history downloads based on the provided |history_info| - // array. |count| is the number of elements in |history_info|. On success, - // |items| will contain |count| DownloadItems in the order that they were - // specified in |history_info|. Returns true on success and false otherwise. - bool CreateHistoryDownloads(const HistoryDownloadInfo* history_info, - size_t count, - DownloadManager::DownloadVector* items) { - DownloadIdComparator download_id_comparator; - base::Time current = base::Time::Now(); - items->clear(); - GetOnRecordManager()->GetAllDownloads(items); - CHECK_EQ(0, static_cast(items->size())); - std::vector url_chain; - url_chain.push_back(GURL()); - for (size_t i = 0; i < count; ++i) { - DownloadItem* item = GetOnRecordManager()->CreateDownloadItem( - content::DownloadItem::kInvalidId + 1 + i, - downloads_directory().Append(history_info[i].filename), - downloads_directory().Append(history_info[i].filename), - url_chain, GURL(), // URL Chain, referrer - current, current, // start_time, end_time - 1, 1, // received_bytes, total_bytes - history_info[i].state, // state - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - content::DOWNLOAD_INTERRUPT_REASON_NONE, - false); // opened - items->push_back(item); - } - - // Order by ID so that they are in the order that we created them. - std::sort(items->begin(), items->end(), download_id_comparator); - // Set the danger type if necessary. - for (size_t i = 0; i < count; ++i) { - if (history_info[i].danger_type != - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) { - EXPECT_EQ(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT, - history_info[i].danger_type); - items->at(i)->OnContentCheckCompleted(history_info[i].danger_type); - } - } - return true; - } - - void CreateSlowTestDownloads( - size_t count, DownloadManager::DownloadVector* items) { - for (size_t i = 0; i < count; ++i) { - scoped_ptr observer( - CreateInProgressDownloadObserver(1)); - GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl); - ui_test_utils::NavigateToURLWithDisposition( - current_browser(), slow_download_url, CURRENT_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - observer->WaitForFinished(); - EXPECT_EQ( - 1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS)); - } - GetCurrentManager()->GetAllDownloads(items); - ASSERT_EQ(count, items->size()); - } - - DownloadItem* CreateSlowTestDownload() { - scoped_ptr observer( - CreateInProgressDownloadObserver(1)); - GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl); - DownloadManager* manager = GetCurrentManager(); - - EXPECT_EQ(0, manager->InProgressCount()); - if (manager->InProgressCount() != 0) - return NULL; - - ui_test_utils::NavigateToURLWithDisposition( - current_browser(), slow_download_url, CURRENT_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - - observer->WaitForFinished(); - EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS)); - - DownloadManager::DownloadVector items; - manager->GetAllDownloads(&items); - - DownloadItem* new_item = NULL; - for (DownloadManager::DownloadVector::iterator iter = items.begin(); - iter != items.end(); ++iter) { - if ((*iter)->GetState() == DownloadItem::IN_PROGRESS) { - // There should be only one IN_PROGRESS item. - EXPECT_EQ(NULL, new_item); - new_item = *iter; - } - } - return new_item; - } - - void FinishPendingSlowDownloads() { - scoped_ptr observer( - CreateDownloadObserver(1)); - GURL finish_url(URLRequestSlowDownloadJob::kFinishDownloadUrl); - ui_test_utils::NavigateToURLWithDisposition( - current_browser(), finish_url, NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - observer->WaitForFinished(); - EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::COMPLETE)); - } - - content::DownloadTestObserver* CreateDownloadObserver(size_t download_count) { - return new content::DownloadTestObserverTerminal( - GetCurrentManager(), download_count, - content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); - } - - content::DownloadTestObserver* CreateInProgressDownloadObserver( - size_t download_count) { - return new content::DownloadTestObserverInProgress( - GetCurrentManager(), download_count); - } - - bool RunFunction(UIThreadExtensionFunction* function, - const std::string& args) { - scoped_refptr delete_function(function); - SetUpExtensionFunction(function); - return extension_function_test_utils::RunFunction( - function, args, browser(), GetFlags()); - } - - extension_function_test_utils::RunFunctionFlags GetFlags() { - return current_browser()->profile()->IsOffTheRecord() ? - extension_function_test_utils::INCLUDE_INCOGNITO : - extension_function_test_utils::NONE; - } - - // extension_function_test_utils::RunFunction*() only uses browser for its - // profile(), so pass it the on-record browser so that it always uses the - // on-record profile to match real-life behavior. - - base::Value* RunFunctionAndReturnResult( - scoped_refptr function, - const std::string& args) { - SetUpExtensionFunction(function.get()); - return extension_function_test_utils::RunFunctionAndReturnSingleResult( - function.get(), args, browser(), GetFlags()); - } - - std::string RunFunctionAndReturnError( - scoped_refptr function, - const std::string& args) { - SetUpExtensionFunction(function.get()); - return extension_function_test_utils::RunFunctionAndReturnError( - function.get(), args, browser(), GetFlags()); - } - - bool RunFunctionAndReturnString( - scoped_refptr function, - const std::string& args, - std::string* result_string) { - SetUpExtensionFunction(function.get()); - scoped_ptr result(RunFunctionAndReturnResult(function, args)); - EXPECT_TRUE(result.get()); - return result.get() && result->GetAsString(result_string); - } - - std::string DownloadItemIdAsArgList(const DownloadItem* download_item) { - return base::StringPrintf("[%d]", download_item->GetId()); - } - - const base::FilePath& downloads_directory() { - return downloads_directory_.path(); - } - - DownloadsEventsListener* events_listener() { return events_listener_.get(); } - - private: - void SetUpExtensionFunction(UIThreadExtensionFunction* function) { - if (extension_) { - // Recreate the tab each time for insulation. - content::WebContents* tab = chrome::AddSelectedTabWithURL( - current_browser(), - extension_->GetResourceURL("empty.html"), - content::PAGE_TRANSITION_LINK); - function->set_extension(extension_); - function->SetRenderViewHost(tab->GetRenderViewHost()); - } - } - - void CreateAndSetDownloadsDirectory() { - ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir()); - current_browser()->profile()->GetPrefs()->SetFilePath( - prefs::kDownloadDefaultDirectory, - downloads_directory_.path()); - } - - base::ScopedTempDir downloads_directory_; - const extensions::Extension* extension_; - Browser* incognito_browser_; - Browser* current_browser_; - scoped_ptr events_listener_; - - DISALLOW_COPY_AND_ASSIGN(DownloadExtensionTest); -}; - -class MockIconExtractorImpl : public DownloadFileIconExtractor { - public: - MockIconExtractorImpl(const base::FilePath& path, - IconLoader::IconSize icon_size, - const std::string& response) - : expected_path_(path), - expected_icon_size_(icon_size), - response_(response) { - } - virtual ~MockIconExtractorImpl() {} - - virtual bool ExtractIconURLForPath(const base::FilePath& path, - IconLoader::IconSize icon_size, - IconURLCallback callback) OVERRIDE { - EXPECT_STREQ(expected_path_.value().c_str(), path.value().c_str()); - EXPECT_EQ(expected_icon_size_, icon_size); - if (expected_path_ == path && - expected_icon_size_ == icon_size) { - callback_ = callback; - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&MockIconExtractorImpl::RunCallback, - base::Unretained(this))); - return true; - } else { - return false; - } - } - - private: - void RunCallback() { - callback_.Run(response_); - } - - base::FilePath expected_path_; - IconLoader::IconSize expected_icon_size_; - std::string response_; - IconURLCallback callback_; -}; - -bool ItemNotInProgress(DownloadItem* item) { - return item->GetState() != DownloadItem::IN_PROGRESS; -} - -// Cancels the underlying DownloadItem when the ScopedCancellingItem goes out of -// scope. Like a scoped_ptr, but for DownloadItems. -class ScopedCancellingItem { - public: - explicit ScopedCancellingItem(DownloadItem* item) : item_(item) {} - ~ScopedCancellingItem() { - item_->Cancel(true); - content::DownloadUpdatedObserver observer( - item_, base::Bind(&ItemNotInProgress)); - observer.WaitForEvent(); - } - DownloadItem* get() { return item_; } - private: - DownloadItem* item_; - DISALLOW_COPY_AND_ASSIGN(ScopedCancellingItem); -}; - -// Cancels all the underlying DownloadItems when the ScopedItemVectorCanceller -// goes out of scope. Generalization of ScopedCancellingItem to many -// DownloadItems. -class ScopedItemVectorCanceller { - public: - explicit ScopedItemVectorCanceller(DownloadManager::DownloadVector* items) - : items_(items) { - } - ~ScopedItemVectorCanceller() { - for (DownloadManager::DownloadVector::const_iterator item = items_->begin(); - item != items_->end(); ++item) { - if ((*item)->GetState() == DownloadItem::IN_PROGRESS) - (*item)->Cancel(true); - content::DownloadUpdatedObserver observer( - (*item), base::Bind(&ItemNotInProgress)); - observer.WaitForEvent(); - } - } - - private: - DownloadManager::DownloadVector* items_; - DISALLOW_COPY_AND_ASSIGN(ScopedItemVectorCanceller); -}; - -class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { - public: - explicit TestProtocolHandler( - webkit_blob::BlobStorageController* blob_storage_controller, - fileapi::FileSystemContext* file_system_context) - : blob_storage_controller_(blob_storage_controller), - file_system_context_(file_system_context) {} - - virtual ~TestProtocolHandler() {} - - virtual net::URLRequestJob* MaybeCreateJob( - net::URLRequest* request, - net::NetworkDelegate* network_delegate) const OVERRIDE { - return new webkit_blob::BlobURLRequestJob( - request, - network_delegate, - blob_storage_controller_->GetBlobDataFromUrl(request->url()), - file_system_context_, - base::MessageLoopProxy::current().get()); - } - - private: - webkit_blob::BlobStorageController* const blob_storage_controller_; - fileapi::FileSystemContext* const file_system_context_; - - DISALLOW_COPY_AND_ASSIGN(TestProtocolHandler); -}; - -class TestURLRequestContext : public net::URLRequestContext { - public: - explicit TestURLRequestContext( - fileapi::FileSystemContext* file_system_context) - : blob_storage_controller_(new webkit_blob::BlobStorageController) { - // Job factory owns the protocol handler. - job_factory_.SetProtocolHandler( - "blob", new TestProtocolHandler(blob_storage_controller_.get(), - file_system_context)); - set_job_factory(&job_factory_); - } - - virtual ~TestURLRequestContext() {} - - webkit_blob::BlobStorageController* blob_storage_controller() const { - return blob_storage_controller_.get(); - } - - private: - net::URLRequestJobFactoryImpl job_factory_; - scoped_ptr blob_storage_controller_; - - DISALLOW_COPY_AND_ASSIGN(TestURLRequestContext); -}; - -// Writes an HTML5 file so that it can be downloaded. -class HTML5FileWriter { - public: - HTML5FileWriter( - Profile* profile, - const std::string& filename, - const std::string& origin, - DownloadsEventsListener* events_listener, - const std::string& payload) - : profile_(profile), - filename_(filename), - origin_(origin), - events_listener_(events_listener), - blob_data_(new webkit_blob::BlobData()), - payload_(payload), - fs_(BrowserContext::GetDefaultStoragePartition(profile_)-> - GetFileSystemContext()) { - CHECK(profile_); - CHECK(events_listener_); - CHECK(fs_); - } - - ~HTML5FileWriter() { - CHECK(BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( - &HTML5FileWriter::TearDownURLRequestContext, base::Unretained(this)))); - events_listener_->WaitFor( - profile_, kURLRequestContextToreDown, std::string()); - } - - bool WriteFile() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - fs_->OpenFileSystem( - GURL(origin_), - fileapi::kFileSystemTypeTemporary, - fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, - base::Bind(&HTML5FileWriter::OpenFileSystemCallback, - base::Unretained(this))); - return events_listener_->WaitFor(profile_, kHTML5FileWritten, filename_); - } - - private: - static const char kHTML5FileWritten[]; - static const char kURLRequestContextToreDown[]; - static const bool kExclusive = true; - - GURL blob_url() const { return GURL("blob:" + filename_); } - - void OpenFileSystemCallback( - base::PlatformFileError result, - const std::string& fs_name, - const GURL& root) { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - root_ = root.spec(); - CHECK(BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( - &HTML5FileWriter::CreateFile, base::Unretained(this)))); - } - - fileapi::FileSystemOperationRunner* operation_runner() { - return fs_->operation_runner(); - } - - void CreateFile() { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - operation_runner()->CreateFile(fs_->CrackURL(GURL(root_ + filename_)), - kExclusive, base::Bind( - &HTML5FileWriter::CreateFileCallback, base::Unretained(this))); - } - - void CreateFileCallback(base::PlatformFileError result) { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - CHECK_EQ(base::PLATFORM_FILE_OK, result); - blob_data_->AppendData(payload_); - url_request_context_.reset(new TestURLRequestContext(fs_)); - url_request_context_->blob_storage_controller() - ->AddFinishedBlob(blob_url(), blob_data_.get()); - operation_runner()->Write( - url_request_context_.get(), - fs_->CrackURL(GURL(root_ + filename_)), - blob_url(), - 0, // offset - base::Bind(&HTML5FileWriter::WriteCallback, base::Unretained(this))); - } - - void WriteCallback( - base::PlatformFileError result, - int64 bytes, - bool complete) { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - CHECK_EQ(base::PLATFORM_FILE_OK, result); - CHECK(BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &HTML5FileWriter::NotifyWritten, base::Unretained(this)))); - } - - void NotifyWritten() { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DownloadsEventsListener::DownloadsNotificationSource notification_source; - notification_source.event_name = kHTML5FileWritten; - notification_source.profile = profile_; - content::NotificationService::current()->Notify( - chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, - content::Source( - ¬ification_source), - content::Details(&filename_)); - } - - void TearDownURLRequestContext() { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - url_request_context_->blob_storage_controller()->RemoveBlob(blob_url()); - url_request_context_.reset(); - CHECK(BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &HTML5FileWriter::NotifyURLRequestContextToreDown, - base::Unretained(this)))); - } - - void NotifyURLRequestContextToreDown() { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DownloadsEventsListener::DownloadsNotificationSource notification_source; - notification_source.event_name = kURLRequestContextToreDown; - notification_source.profile = profile_; - std::string empty_args; - content::NotificationService::current()->Notify( - chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, - content::Source( - ¬ification_source), - content::Details(&empty_args)); - } - - Profile* profile_; - std::string filename_; - std::string origin_; - std::string root_; - DownloadsEventsListener* events_listener_; - scoped_refptr blob_data_; - std::string payload_; - scoped_ptr url_request_context_; - fileapi::FileSystemContext* fs_; - - DISALLOW_COPY_AND_ASSIGN(HTML5FileWriter); -}; - -const char HTML5FileWriter::kHTML5FileWritten[] = "html5_file_written"; -const char HTML5FileWriter::kURLRequestContextToreDown[] = - "url_request_context_tore_down"; - -// TODO(benjhayden) Merge this with the other TestObservers. -class JustInProgressDownloadObserver - : public content::DownloadTestObserverInProgress { - public: - JustInProgressDownloadObserver( - DownloadManager* download_manager, size_t wait_count) - : content::DownloadTestObserverInProgress(download_manager, wait_count) { - } - - virtual ~JustInProgressDownloadObserver() {} - - private: - virtual bool IsDownloadInFinalState(DownloadItem* item) OVERRIDE { - return item->GetState() == DownloadItem::IN_PROGRESS; - } - - DISALLOW_COPY_AND_ASSIGN(JustInProgressDownloadObserver); -}; - -bool ItemIsInterrupted(DownloadItem* item) { - return item->GetState() == DownloadItem::INTERRUPTED; -} - -} // namespace - -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Open) { - LoadExtension("downloads_split"); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - RunFunctionAndReturnError( - new DownloadsOpenFunction(), - "[-42]").c_str()); - - DownloadItem* download_item = CreateSlowTestDownload(); - ASSERT_TRUE(download_item); - EXPECT_FALSE(download_item->GetOpened()); - EXPECT_FALSE(download_item->GetOpenWhenComplete()); - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"application/octet-stream\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_item->GetURL().spec().c_str()))); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - RunFunctionAndReturnError( - new DownloadsOpenFunction(), - DownloadItemIdAsArgList(download_item)).c_str()); - - FinishPendingSlowDownloads(); - EXPECT_FALSE(download_item->GetOpened()); - EXPECT_TRUE(RunFunction(new DownloadsOpenFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_TRUE(download_item->GetOpened()); -} - -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_PauseResumeCancelErase) { - DownloadItem* download_item = CreateSlowTestDownload(); - ASSERT_TRUE(download_item); - - // Call pause(). It should succeed and the download should be paused on - // return. - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_TRUE(download_item->IsPaused()); - - // Calling pause() twice shouldn't be an error. - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_TRUE(download_item->IsPaused()); - - // Now try resuming this download. It should succeed. - EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_FALSE(download_item->IsPaused()); - - // Resume again. Resuming a download that wasn't paused is not an error. - EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_FALSE(download_item->IsPaused()); - - // Pause again. - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_TRUE(download_item->IsPaused()); - - // And now cancel. - EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState()); - - // Cancel again. Shouldn't have any effect. - EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), - DownloadItemIdAsArgList(download_item))); - EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState()); - - // Calling paused on a non-active download yields kInvalidOperationError. - std::string error = RunFunctionAndReturnError( - new DownloadsPauseFunction(), DownloadItemIdAsArgList(download_item)); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - - // Calling resume on a non-active download yields kInvalidOperationError - error = RunFunctionAndReturnError( - new DownloadsResumeFunction(), DownloadItemIdAsArgList(download_item)); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - - // Calling paused on a non-existent download yields kInvalidOperationError. - error = RunFunctionAndReturnError( - new DownloadsPauseFunction(), "[-42]"); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - - // Calling resume on a non-existent download yields kInvalidOperationError - error = RunFunctionAndReturnError( - new DownloadsResumeFunction(), "[-42]"); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - - int id = download_item->GetId(); - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsEraseFunction(), - base::StringPrintf("[{\"id\": %d}]", id))); - DownloadManager::DownloadVector items; - GetCurrentManager()->GetAllDownloads(&items); - EXPECT_EQ(0UL, items.size()); - ASSERT_TRUE(result); - download_item = NULL; - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); - int element = -1; - ASSERT_TRUE(result_list->GetInteger(0, &element)); - EXPECT_EQ(id, element); -} - -scoped_refptr MockedGetFileIconFunction( - const base::FilePath& expected_path, - IconLoader::IconSize icon_size, - const std::string& response) { - scoped_refptr function( - new DownloadsGetFileIconFunction()); - function->SetIconExtractorForTesting(new MockIconExtractorImpl( - expected_path, icon_size, response)); - return function; -} - -// Test downloads.getFileIcon() on in-progress, finished, cancelled and deleted -// download items. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_FileIcon_Active) { - DownloadItem* download_item = CreateSlowTestDownload(); - ASSERT_TRUE(download_item); - ASSERT_FALSE(download_item->GetTargetFilePath().empty()); - std::string args32(base::StringPrintf("[%d, {\"size\": 32}]", - download_item->GetId())); - std::string result_string; - - // Get the icon for the in-progress download. This call should succeed even - // if the file type isn't registered. - // Test whether the correct path is being pased into the icon extractor. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - base::StringPrintf("[%d, {}]", download_item->GetId()), &result_string)); - - // Now try a 16x16 icon. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - download_item->GetTargetFilePath(), IconLoader::SMALL, "foo"), - base::StringPrintf("[%d, {\"size\": 16}]", download_item->GetId()), - &result_string)); - - // Explicitly asking for 32x32 should give us a 32x32 icon. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - args32, &result_string)); - - // Finish the download and try again. - FinishPendingSlowDownloads(); - EXPECT_EQ(DownloadItem::COMPLETE, download_item->GetState()); - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - args32, &result_string)); - - // Check the path passed to the icon extractor post-completion. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - args32, &result_string)); - - // Now create another download. - download_item = CreateSlowTestDownload(); - ASSERT_TRUE(download_item); - ASSERT_FALSE(download_item->GetTargetFilePath().empty()); - args32 = base::StringPrintf("[%d, {\"size\": 32}]", download_item->GetId()); - - // Cancel the download. As long as the download has a target path, we should - // be able to query the file icon. - download_item->Cancel(true); - ASSERT_FALSE(download_item->GetTargetFilePath().empty()); - // Let cleanup complete on the FILE thread. - content::RunAllPendingInMessageLoop(BrowserThread::FILE); - // Check the path passed to the icon extractor post-cancellation. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - args32, - &result_string)); - - // Simulate an error during icon load by invoking the mock with an empty - // result string. - std::string error = RunFunctionAndReturnError( - MockedGetFileIconFunction(download_item->GetTargetFilePath(), - IconLoader::NORMAL, - std::string()), - args32); - EXPECT_STREQ(download_extension_errors::kIconNotFoundError, error.c_str()); - - // Once the download item is deleted, we should return kInvalidOperationError. - int id = download_item->GetId(); - download_item->Remove(); - download_item = NULL; - EXPECT_EQ(static_cast(NULL), - GetCurrentManager()->GetDownload(id)); - error = RunFunctionAndReturnError(new DownloadsGetFileIconFunction(), args32); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); -} - -// Test that we can acquire file icons for history downloads regardless of -// whether they exist or not. If the file doesn't exist we should receive a -// generic icon from the OS/toolkit that may or may not be specific to the file -// type. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_FileIcon_History) { - const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("real.txt"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("fake.txt"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; - DownloadManager::DownloadVector all_downloads; - ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), - &all_downloads)); - - base::FilePath real_path = all_downloads[0]->GetTargetFilePath(); - base::FilePath fake_path = all_downloads[1]->GetTargetFilePath(); - - EXPECT_EQ(0, file_util::WriteFile(real_path, "", 0)); - ASSERT_TRUE(base::PathExists(real_path)); - ASSERT_FALSE(base::PathExists(fake_path)); - - for (DownloadManager::DownloadVector::iterator iter = all_downloads.begin(); - iter != all_downloads.end(); - ++iter) { - std::string result_string; - // Use a MockIconExtractorImpl to test if the correct path is being passed - // into the DownloadFileIconExtractor. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - (*iter)->GetTargetFilePath(), IconLoader::NORMAL, "hello"), - base::StringPrintf("[%d, {\"size\": 32}]", (*iter)->GetId()), - &result_string)); - EXPECT_STREQ("hello", result_string.c_str()); - } - - // The temporary files should be cleaned up when the base::ScopedTempDir is removed. -} - -// Test passing the empty query to search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchEmptyQuery) { - ScopedCancellingItem item(CreateSlowTestDownload()); - ASSERT_TRUE(item.get()); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); -} - -// Test the |filenameRegex| parameter for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchFilenameRegex) { - const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("foobar"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; - DownloadManager::DownloadVector all_downloads; - ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), - &all_downloads)); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{\"filenameRegex\": \"foobar\"}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); - base::DictionaryValue* item_value = NULL; - ASSERT_TRUE(result_list->GetDictionary(0, &item_value)); - int item_id = -1; - ASSERT_TRUE(item_value->GetInteger("id", &item_id)); - ASSERT_EQ(all_downloads[0]->GetId(), static_cast(item_id)); -} - -// Test the |id| parameter for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchId) { - DownloadManager::DownloadVector items; - CreateSlowTestDownloads(2, &items); - ScopedItemVectorCanceller delete_items(&items); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), base::StringPrintf( - "[{\"id\": %u}]", items[0]->GetId()))); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); - base::DictionaryValue* item_value = NULL; - ASSERT_TRUE(result_list->GetDictionary(0, &item_value)); - int item_id = -1; - ASSERT_TRUE(item_value->GetInteger("id", &item_id)); - ASSERT_EQ(items[0]->GetId(), static_cast(item_id)); -} - -// Test specifying both the |id| and |filename| parameters for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchIdAndFilename) { - DownloadManager::DownloadVector items; - CreateSlowTestDownloads(2, &items); - ScopedItemVectorCanceller delete_items(&items); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), - "[{\"id\": 0, \"filename\": \"foobar\"}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(0UL, result_list->GetSize()); -} - -// Test a single |orderBy| parameter for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchOrderBy) { - const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; - DownloadManager::DownloadVector items; - ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), - &items)); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{\"orderBy\": \"filename\"}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(2UL, result_list->GetSize()); - base::DictionaryValue* item0_value = NULL; - base::DictionaryValue* item1_value = NULL; - ASSERT_TRUE(result_list->GetDictionary(0, &item0_value)); - ASSERT_TRUE(result_list->GetDictionary(1, &item1_value)); - std::string item0_name, item1_name; - ASSERT_TRUE(item0_value->GetString("filename", &item0_name)); - ASSERT_TRUE(item1_value->GetString("filename", &item1_name)); - ASSERT_GT(items[0]->GetTargetFilePath().value(), - items[1]->GetTargetFilePath().value()); - ASSERT_LT(item0_name, item1_name); -} - -// Test specifying an empty |orderBy| parameter for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchOrderByEmpty) { - const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; - DownloadManager::DownloadVector items; - ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), - &items)); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{\"orderBy\": \"\"}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(2UL, result_list->GetSize()); - base::DictionaryValue* item0_value = NULL; - base::DictionaryValue* item1_value = NULL; - ASSERT_TRUE(result_list->GetDictionary(0, &item0_value)); - ASSERT_TRUE(result_list->GetDictionary(1, &item1_value)); - std::string item0_name, item1_name; - ASSERT_TRUE(item0_value->GetString("filename", &item0_name)); - ASSERT_TRUE(item1_value->GetString("filename", &item1_name)); - ASSERT_GT(items[0]->GetTargetFilePath().value(), - items[1]->GetTargetFilePath().value()); - ASSERT_GT(item0_name, item1_name); -} - -// Test the |danger| option for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchDanger) { - const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; - DownloadManager::DownloadVector items; - ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), - &items)); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{\"danger\": \"content\"}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); -} - -// Test the |state| option for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchState) { - DownloadManager::DownloadVector items; - CreateSlowTestDownloads(2, &items); - ScopedItemVectorCanceller delete_items(&items); - - items[0]->Cancel(true); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{\"state\": \"in_progress\"}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); -} - -// Test the |limit| option for search(). -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchLimit) { - DownloadManager::DownloadVector items; - CreateSlowTestDownloads(2, &items); - ScopedItemVectorCanceller delete_items(&items); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{\"limit\": 1}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); -} - -// Test invalid search parameters. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchInvalid) { - std::string error = RunFunctionAndReturnError( - new DownloadsSearchFunction(), "[{\"filenameRegex\": \"(\"}]"); - EXPECT_STREQ(download_extension_errors::kInvalidFilterError, - error.c_str()); - error = RunFunctionAndReturnError( - new DownloadsSearchFunction(), "[{\"orderBy\": \"goat\"}]"); - EXPECT_STREQ(download_extension_errors::kInvalidOrderByError, - error.c_str()); - error = RunFunctionAndReturnError( - new DownloadsSearchFunction(), "[{\"limit\": -1}]"); - EXPECT_STREQ(download_extension_errors::kInvalidQueryLimit, - error.c_str()); -} - -// Test searching using multiple conditions through multiple downloads. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchPlural) { - const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("aaa"), - DownloadItem::CANCELLED, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, - }; - DownloadManager::DownloadVector items; - ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), - &items)); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{" - "\"state\": \"complete\", " - "\"danger\": \"content\", " - "\"orderBy\": \"filename\", " - "\"limit\": 1}]")); - ASSERT_TRUE(result.get()); - base::ListValue* result_list = NULL; - ASSERT_TRUE(result->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); - base::DictionaryValue* item_value = NULL; - ASSERT_TRUE(result_list->GetDictionary(0, &item_value)); - base::FilePath::StringType item_name; - ASSERT_TRUE(item_value->GetString("filename", &item_name)); - ASSERT_EQ(items[2]->GetTargetFilePath().value(), item_name); -} - -// Test that incognito downloads are only visible in incognito contexts, and -// test that on-record downloads are visible in both incognito and on-record -// contexts, for DownloadsSearchFunction, DownloadsPauseFunction, -// DownloadsResumeFunction, and DownloadsCancelFunction. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_SearchPauseResumeCancelGetFileIconIncognito) { - scoped_ptr result_value; - base::ListValue* result_list = NULL; - base::DictionaryValue* result_dict = NULL; - base::FilePath::StringType filename; - bool is_incognito = false; - std::string error; - std::string on_item_arg; - std::string off_item_arg; - std::string result_string; - - // Set up one on-record item and one off-record item. - // Set up the off-record item first because otherwise there are mysteriously 3 - // items total instead of 2. - // TODO(benjhayden): Figure out where the third item comes from. - GoOffTheRecord(); - DownloadItem* off_item = CreateSlowTestDownload(); - ASSERT_TRUE(off_item); - off_item_arg = DownloadItemIdAsArgList(off_item); - - GoOnTheRecord(); - DownloadItem* on_item = CreateSlowTestDownload(); - ASSERT_TRUE(on_item); - on_item_arg = DownloadItemIdAsArgList(on_item); - ASSERT_TRUE(on_item->GetTargetFilePath() != off_item->GetTargetFilePath()); - - // Extensions running in the incognito window should have access to both - // items because the Test extension is in spanning mode. - GoOffTheRecord(); - result_value.reset(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{}]")); - ASSERT_TRUE(result_value.get()); - ASSERT_TRUE(result_value->GetAsList(&result_list)); - ASSERT_EQ(2UL, result_list->GetSize()); - ASSERT_TRUE(result_list->GetDictionary(0, &result_dict)); - ASSERT_TRUE(result_dict->GetString("filename", &filename)); - ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito)); - EXPECT_TRUE(on_item->GetTargetFilePath() == base::FilePath(filename)); - EXPECT_FALSE(is_incognito); - ASSERT_TRUE(result_list->GetDictionary(1, &result_dict)); - ASSERT_TRUE(result_dict->GetString("filename", &filename)); - ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito)); - EXPECT_TRUE(off_item->GetTargetFilePath() == base::FilePath(filename)); - EXPECT_TRUE(is_incognito); - - // Extensions running in the on-record window should have access only to the - // on-record item. - GoOnTheRecord(); - result_value.reset(RunFunctionAndReturnResult( - new DownloadsSearchFunction(), "[{}]")); - ASSERT_TRUE(result_value.get()); - ASSERT_TRUE(result_value->GetAsList(&result_list)); - ASSERT_EQ(1UL, result_list->GetSize()); - ASSERT_TRUE(result_list->GetDictionary(0, &result_dict)); - ASSERT_TRUE(result_dict->GetString("filename", &filename)); - EXPECT_TRUE(on_item->GetTargetFilePath() == base::FilePath(filename)); - ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito)); - EXPECT_FALSE(is_incognito); - - // Pausing/Resuming the off-record item while on the record should return an - // error. Cancelling "non-existent" downloads is not an error. - error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - error = RunFunctionAndReturnError(new DownloadsResumeFunction(), - off_item_arg); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - error = RunFunctionAndReturnError( - new DownloadsGetFileIconFunction(), - base::StringPrintf("[%d, {}]", off_item->GetId())); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - - GoOffTheRecord(); - - // Do the FileIcon test for both the on- and off-items while off the record. - // NOTE(benjhayden): This does not include the FileIcon test from history, - // just active downloads. This shouldn't be a problem. - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - on_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - base::StringPrintf("[%d, {}]", on_item->GetId()), &result_string)); - EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction( - off_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"), - base::StringPrintf("[%d, {}]", off_item->GetId()), &result_string)); - - // Do the pause/resume/cancel test for both the on- and off-items while off - // the record. - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg)); - EXPECT_TRUE(on_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg)); - EXPECT_TRUE(on_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), on_item_arg)); - EXPECT_FALSE(on_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), on_item_arg)); - EXPECT_FALSE(on_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg)); - EXPECT_TRUE(on_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg)); - EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState()); - EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg)); - EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState()); - error = RunFunctionAndReturnError(new DownloadsPauseFunction(), on_item_arg); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - error = RunFunctionAndReturnError(new DownloadsResumeFunction(), on_item_arg); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg)); - EXPECT_TRUE(off_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg)); - EXPECT_TRUE(off_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), off_item_arg)); - EXPECT_FALSE(off_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), off_item_arg)); - EXPECT_FALSE(off_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg)); - EXPECT_TRUE(off_item->IsPaused()); - EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg)); - EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState()); - EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg)); - EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState()); - error = RunFunctionAndReturnError(new DownloadsPauseFunction(), - off_item_arg); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); - error = RunFunctionAndReturnError(new DownloadsResumeFunction(), - off_item_arg); - EXPECT_STREQ(download_extension_errors::kInvalidOperationError, - error.c_str()); -} - -// Test that we can start a download and that the correct sequence of events is -// fired for it. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Basic) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - GoOnTheRecord(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// Test that we can start a download from an incognito context, and that the -// download knows that it's incognito. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Incognito) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - GoOffTheRecord(); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": true," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\":%d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\":%d," - " \"state\": {" - " \"current\": \"complete\"," - " \"previous\": \"in_progress\"}}]", - result_id))); -} - -#if defined(OS_WIN) && defined(USE_AURA) -// This test is very flaky on Win Aura. http://crbug.com/248438 -#define MAYBE_DownloadExtensionTest_Download_UnsafeHeaders \ - DISABLED_DownloadExtensionTest_Download_UnsafeHeaders -#else -#define MAYBE_DownloadExtensionTest_Download_UnsafeHeaders \ - DownloadExtensionTest_Download_UnsafeHeaders -#endif - -// Test that we disallow certain headers case-insensitively. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - MAYBE_DownloadExtensionTest_Download_UnsafeHeaders) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - GoOnTheRecord(); - - static const char* kUnsafeHeaders[] = { - "Accept-chArsEt", - "accept-eNcoding", - "coNNection", - "coNteNt-leNgth", - "cooKIE", - "cOOkie2", - "coNteNt-traNsfer-eNcodiNg", - "dAtE", - "ExpEcT", - "hOsT", - "kEEp-aLivE", - "rEfErEr", - "tE", - "trAilER", - "trANsfer-eNcodiNg", - "upGRAde", - "usER-agENt", - "viA", - "pRoxY-", - "sEc-", - "pRoxY-probably-not-evil", - "sEc-probably-not-evil", - "oRiGiN", - "Access-Control-Request-Headers", - "Access-Control-Request-Method", - }; - - for (size_t index = 0; index < arraysize(kUnsafeHeaders); ++index) { - std::string download_url = test_server()->GetURL("slow?0").spec(); - EXPECT_STREQ(download_extension_errors::kGenericError, - RunFunctionAndReturnError(new DownloadsDownloadFunction(), - base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"unsafe-header-%d.txt\"," - " \"headers\": [{" - " \"name\": \"%s\"," - " \"value\": \"unsafe\"}]}]", - download_url.c_str(), - static_cast(index), - kUnsafeHeaders[index])).c_str()); - } -} - -// Test that subdirectories (slashes) are disallowed in filenames. -// TODO(benjhayden) Update this when subdirectories are supported. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Subdirectory) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - GoOnTheRecord(); - - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, - RunFunctionAndReturnError(new DownloadsDownloadFunction(), - base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"sub/dir/ect/ory.txt\"}]", - download_url.c_str())).c_str()); -} - -// Test that invalid filenames are disallowed. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_InvalidFilename) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - GoOnTheRecord(); - - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, - RunFunctionAndReturnError(new DownloadsDownloadFunction(), - base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"../../../../../etc/passwd\"}]", - download_url.c_str())).c_str()); -} - -// Test that downloading invalid URLs immediately returns kInvalidURLError. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_InvalidURLs) { - LoadExtension("downloads_split"); - GoOnTheRecord(); - - static const char* kInvalidURLs[] = { - "foo bar", - "../hello", - "/hello", - "google.com/", - "http://", - "#frag", - "foo/bar.html#frag", - }; - - for (size_t index = 0; index < arraysize(kInvalidURLs); ++index) { - EXPECT_STREQ(download_extension_errors::kInvalidURLError, - RunFunctionAndReturnError(new DownloadsDownloadFunction(), - base::StringPrintf( - "[{\"url\": \"%s\"}]", kInvalidURLs[index])).c_str()) - << kInvalidURLs[index]; - } - - EXPECT_STREQ("net::ERR_ACCESS_DENIED", RunFunctionAndReturnError( - new DownloadsDownloadFunction(), - "[{\"url\": \"javascript:document.write(\\\"hello\\\");\"}]").c_str()); - EXPECT_STREQ("net::ERR_ACCESS_DENIED", RunFunctionAndReturnError( - new DownloadsDownloadFunction(), - "[{\"url\": \"javascript:return false;\"}]").c_str()); - EXPECT_STREQ("net::ERR_NOT_IMPLEMENTED", RunFunctionAndReturnError( - new DownloadsDownloadFunction(), - "[{\"url\": \"ftp://example.com/example.txt\"}]").c_str()); -} - -// TODO(benjhayden): Set up a test ftp server, add ftp://localhost* to -// permissions, test downloading from ftp. - -// Valid URLs plus fragments are still valid URLs. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_URLFragment) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0#fragment").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// Valid data URLs are valid URLs. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_DataURL) { - LoadExtension("downloads_split"); - std::string download_url = "data:text/plain,hello"; - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"data.txt\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("data.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// Valid file URLs are valid URLs. -#if defined(OS_WIN) && defined(USE_AURA) -// Disabled due to crbug.com/175711 -#define MAYBE_DownloadExtensionTest_Download_File \ - DISABLED_DownloadExtensionTest_Download_File -#else -#define MAYBE_DownloadExtensionTest_Download_File \ - DownloadExtensionTest_Download_File -#endif -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - MAYBE_DownloadExtensionTest_Download_File) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - std::string download_url = "file:///"; -#if defined(OS_WIN) - download_url += "C:/"; -#endif - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"file.txt\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/html\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("file.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// Test that auth-basic-succeed would fail if the resource requires the -// Authorization header and chrome fails to propagate it back to the server. -// This tests both that testserver.py does not succeed when it should fail as -// well as how the downloads extension API exposes the failure to extensions. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_AuthBasic_Fail) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("auth-basic").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"auth-basic-fail.txt\"}]", - download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitForInterruption(item, 30, base::StringPrintf( - "[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/html\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); -} - -// Test that DownloadsDownloadFunction propagates |headers| to the URLRequest. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Headers) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("files/downloads/" - "a_zip_file.zip?expected_headers=Foo:bar&expected_headers=Qx:yo").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"headers-succeed.txt\"," - " \"headers\": [" - " {\"name\": \"Foo\", \"value\": \"bar\"}," - " {\"name\": \"Qx\", \"value\":\"yo\"}]}]", - download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"application/octet-stream\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("headers-succeed.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// Test that headers-succeed would fail if the resource requires the headers and -// chrome fails to propagate them back to the server. This tests both that -// testserver.py does not succeed when it should fail as well as how the -// downloads extension api exposes the failure to extensions. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Headers_Fail) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("files/downloads/" - "a_zip_file.zip?expected_headers=Foo:bar&expected_headers=Qx:yo").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"headers-fail.txt\"}]", - download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitForInterruption(item, 33, base::StringPrintf( - "[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"bytesReceived\": 0," - " \"mime\": \"\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); -} - -// Test that DownloadsDownloadFunction propagates the Authorization header -// correctly. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_AuthBasic) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("auth-basic").spec(); - // This is just base64 of 'username:secret'. - static const char* kAuthorization = "dXNlcm5hbWU6c2VjcmV0"; - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"auth-basic-succeed.txt\"," - " \"headers\": [{" - " \"name\": \"Authorization\"," - " \"value\": \"Basic %s\"}]}]", - download_url.c_str(), kAuthorization))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/html\"," - " \"paused\": false," - " \"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", result_id))); -} - -// Test that DownloadsDownloadFunction propagates the |method| and |body| -// parameters to the URLRequest. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Post) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("files/post/downloads/" - "a_zip_file.zip?expected_body=BODY").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"filename\": \"post-succeed.txt\"," - " \"method\": \"POST\"," - " \"body\": \"BODY\"}]", - download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"application/octet-stream\"," - " \"paused\": false," - " \"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("post-succeed.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// Test that downloadPostSuccess would fail if the resource requires the POST -// method, and chrome fails to propagate the |method| parameter back to the -// server. This tests both that testserver.py does not succeed when it should -// fail, and this tests how the downloads extension api exposes the failure to -// extensions. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Post_Get) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("files/post/downloads/" - "a_zip_file.zip?expected_body=BODY").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"body\": \"BODY\"," - " \"filename\": \"post-get.txt\"}]", - download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitForInterruption(item, 33, base::StringPrintf( - "[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"\"," - " \"paused\": false," - " \"id\": %d," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); -} - -// Test that downloadPostSuccess would fail if the resource requires the POST -// method, and chrome fails to propagate the |body| parameter back to the -// server. This tests both that testserver.py does not succeed when it should -// fail, and this tests how the downloads extension api exposes the failure to -// extensions. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Post_NoBody) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("files/post/downloads/" - "a_zip_file.zip?expected_body=BODY").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"," - " \"method\": \"POST\"," - " \"filename\": \"post-nobody.txt\"}]", - download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitForInterruption(item, 33, base::StringPrintf( - "[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"\"," - " \"paused\": false," - " \"id\": %d," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); -} - -// Test that cancel()ing an in-progress download causes its state to transition -// to interrupted, and test that that state transition is detectable by an -// onChanged event listener. TODO(benjhayden): Test other sources of -// interruptions such as server death. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_Cancel) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL( - "download-known-size").spec(); - GoOnTheRecord(); - - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"application/octet-stream\"," - " \"paused\": false," - " \"id\": %d," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - item->Cancel(true); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"error\": {\"current\": 40}," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"interrupted\"}}]", - result_id))); -} - -// Test downloading filesystem: URLs. -// NOTE: chrome disallows creating HTML5 FileSystem Files in incognito. -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_Download_FileSystemURL) { - static const char* kPayloadData = "on the record\ndata"; - GoOnTheRecord(); - LoadExtension("downloads_split"); - HTML5FileWriter html5_file_writer( - browser()->profile(), - "on_record.txt", - GetExtensionURL(), - events_listener(), - kPayloadData); - ASSERT_TRUE(html5_file_writer.WriteFile()); - - std::string download_url = "filesystem:" + GetExtensionURL() + - "temporary/on_record.txt"; - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - download_url.c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("on_record.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); - std::string disk_data; - EXPECT_TRUE(file_util::ReadFileToString(item->GetTargetFilePath(), - &disk_data)); - EXPECT_STREQ(kPayloadData, disk_data.c_str()); -} - -IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_NoChange) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - // Wait for the onCreated and onDeterminingFilename events. - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - // The download should complete successfully. - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_DangerousOverride) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("overridden.swf")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"danger\": {" - " \"previous\":\"safe\"," - " \"current\":\"file\"}," - " \"dangerAccepted\": {" - " \"current\":false}}]", - result_id))); - - item->ValidateDangerousDownload(); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"dangerAccepted\": {" - " \"previous\":false," - " \"current\":true}}]", - result_id))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); - EXPECT_EQ(downloads_directory().AppendASCII("overridden.swf"), - item->GetTargetFilePath()); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_ReferencesParentInvalid) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("sneaky/../../sneaky.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_IllegalFilename) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("<")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( - "[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( - "[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_IllegalFilenameExtension) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL( - "My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}/foo")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( - "[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( - "[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_ReservedFilename) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("con.foo")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( - "[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf( - "[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_CurDirInvalid) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL(".")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_ParentDirInvalid) { - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("..")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_AbsPathInvalid) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. Absolute paths should be rejected. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - downloads_directory().Append(FILE_PATH_LITERAL("sneaky.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_EmptyBasenameInvalid) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. Empty basenames should be rejected. - std::string error; - ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("foo/")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_Override) { - GoOnTheRecord(); - LoadExtension("downloads_split"); - AddFilenameDeterminer(); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - std::string error; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("slow.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); - - // Start downloading a file. - result.reset(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller2(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - // Also test that DetermineFilename allows (chrome) extensions to set - // filenames without (filename) extensions. (Don't ask about v8 extensions or - // python extensions or kernel extensions or firefox extensions...) - error = ""; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("foo")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE, - &error)); - EXPECT_EQ("", error); - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("foo").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -// TODO test precedence rules: install_time - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_RemoveFilenameDeterminer) { - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - GoOnTheRecord(); - LoadExtension("downloads_split"); - content::RenderProcessHost* host = AddFilenameDeterminer(); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - // Start downloading a file. - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Remove a determiner while waiting for it. - RemoveFilenameDeterminer(host); - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_IncognitoSplit) { - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - GoOnTheRecord(); - AddFilenameDeterminer(); - - GoOffTheRecord(); - AddFilenameDeterminer(); - - // Start an on-record download. - GoOnTheRecord(); - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - // Wait for the onCreated and onDeterminingFilename events. - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"incognito\": false," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename events. - std::string error; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - current_browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("42.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - // The download should complete successfully. - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("42.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); - - // Start an incognito download for comparison. - GoOffTheRecord(); - result.reset(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller2(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": true," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - // On-Record renderers should not see events for off-record items. - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"incognito\": true," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - error = ""; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - current_browser()->profile(), - false, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("5.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - // The download should complete successfully. - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("5.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - DownloadExtensionTest_OnDeterminingFilename_IncognitoSpanning) { - LoadExtension("downloads_spanning"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - std::string download_url = test_server()->GetURL("slow?0").spec(); - - GoOnTheRecord(); - AddFilenameDeterminer(); - - // There is a single extension renderer that sees both on-record and - // off-record events. The extension functions see the on-record profile with - // include_incognito=true. - - // Start an on-record download. - GoOnTheRecord(); - scoped_ptr result(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - int result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - DownloadItem* item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - // Wait for the onCreated and onDeterminingFilename events. - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"incognito\": false," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename events. - std::string error; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - current_browser()->profile(), - true, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("42.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - // The download should complete successfully. - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("42.txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); - - // Start an incognito download for comparison. - GoOffTheRecord(); - result.reset(RunFunctionAndReturnResult( - new DownloadsDownloadFunction(), base::StringPrintf( - "[{\"url\": \"%s\"}]", download_url.c_str()))); - ASSERT_TRUE(result.get()); - result_id = -1; - ASSERT_TRUE(result->GetAsInteger(&result_id)); - item = GetCurrentManager()->GetDownload(result_id); - ASSERT_TRUE(item); - ScopedCancellingItem canceller2(item); - ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); - - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": true," - " \"id\": %d," - " \"mime\": \"text/plain\"," - " \"paused\": false," - " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"incognito\": true," - " \"filename\":\"slow.txt\"}]", - result_id))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - // Respond to the onDeterminingFilename. - error = ""; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - current_browser()->profile(), - true, - GetExtensionId(), - result_id, - base::FilePath(FILE_PATH_LITERAL("42.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)); - EXPECT_EQ("", error); - - // The download should complete successfully. - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - result_id, - GetFilename("42 (1).txt").c_str()))); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - result_id))); -} - -#if defined(OS_WIN) -// This test is very flaky on Win XP and Aura. http://crbug.com/248438 -#define MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume \ - DISABLED_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume -#else -#define MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume \ - DownloadExtensionTest_OnDeterminingFilename_InterruptedResume -#endif - -// Test download interruption while extensions determining filename. Should not -// re-dispatch onDeterminingFilename. -IN_PROC_BROWSER_TEST_F( - DownloadExtensionTest, - MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableDownloadResumption); - LoadExtension("downloads_split"); - ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(test_server()->Start()); - GoOnTheRecord(); - content::RenderProcessHost* host = AddFilenameDeterminer(); - - // Start a download. - DownloadItem* item = NULL; - { - DownloadManager* manager = GetCurrentManager(); - scoped_ptr observer( - new JustInProgressDownloadObserver(manager, 1)); - ASSERT_EQ(0, manager->InProgressCount()); - // Tabs created just for a download are automatically closed, invalidating - // the download's WebContents. Downloads without WebContents cannot be - // resumed. http://crbug.com/225901 - ui_test_utils::NavigateToURLWithDisposition( - current_browser(), - GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl), - CURRENT_TAB, - ui_test_utils::BROWSER_TEST_NONE); - observer->WaitForFinished(); - EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS)); - DownloadManager::DownloadVector items; - manager->GetAllDownloads(&items); - for (DownloadManager::DownloadVector::iterator iter = items.begin(); - iter != items.end(); ++iter) { - if ((*iter)->GetState() == DownloadItem::IN_PROGRESS) { - // There should be only one IN_PROGRESS item. - EXPECT_EQ(NULL, item); - item = *iter; - } - } - ASSERT_TRUE(item); - } - ScopedCancellingItem canceller(item); - - // Wait for the onCreated and onDeterminingFilename event. - ASSERT_TRUE(WaitFor(events::kOnDownloadCreated, - base::StringPrintf("[{\"danger\": \"safe\"," - " \"incognito\": false," - " \"id\": %d," - " \"mime\": \"application/octet-stream\"," - " \"paused\": false}]", - item->GetId()))); - ASSERT_TRUE(WaitFor( - events::kOnDownloadDeterminingFilename, - base::StringPrintf("[{\"id\": %d," - " \"incognito\": false," - " \"filename\":\"download-unknown-size\"}]", - item->GetId()))); - ASSERT_TRUE(item->GetTargetFilePath().empty()); - ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - - ClearEvents(); - ui_test_utils::NavigateToURLWithDisposition( - current_browser(), - GURL(URLRequestSlowDownloadJob::kErrorDownloadUrl), - NEW_BACKGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - - // Errors caught before filename determination are delayed until after - // filename determination. - std::string error; - ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename( - current_browser()->profile(), - false, - GetExtensionId(), - item->GetId(), - base::FilePath(FILE_PATH_LITERAL("42.txt")), - extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY, - &error)) << error; - EXPECT_EQ("", error); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"filename\": {" - " \"previous\": \"\"," - " \"current\": \"%s\"}}]", - item->GetId(), - GetFilename("42.txt").c_str()))); - - content::DownloadUpdatedObserver interrupted(item, base::Bind( - ItemIsInterrupted)); - ASSERT_TRUE(interrupted.WaitForEvent()); - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"error\":{\"current\":20}," - " \"state\":{" - " \"previous\":\"in_progress\"," - " \"current\":\"interrupted\"}}]", - item->GetId()))); - - ClearEvents(); - // Downloads that are restarted on resumption trigger another download target - // determination. - RemoveFilenameDeterminer(host); - item->Resume(); - - // Errors caught before filename determination is complete are delayed until - // after filename determination so that, on resumption, filename determination - // does not need to be re-done. So, there will not be a second - // onDeterminingFilename event. - - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"error\":{\"previous\":20}," - " \"state\":{" - " \"previous\":\"interrupted\"," - " \"current\":\"in_progress\"}}]", - item->GetId()))); - - ClearEvents(); - FinishPendingSlowDownloads(); - - // The download should complete successfully. - ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, - base::StringPrintf("[{\"id\": %d," - " \"state\": {" - " \"previous\": \"in_progress\"," - " \"current\": \"complete\"}}]", - item->GetId()))); -} - -// TODO(benjhayden) Figure out why DisableExtension() does not fire -// OnListenerRemoved. - -// TODO(benjhayden) Test that the shelf is shown for download() both with and -// without a WebContents. - -class DownloadsApiTest : public ExtensionApiTest { - public: - DownloadsApiTest() {} - virtual ~DownloadsApiTest() {} - private: - DISALLOW_COPY_AND_ASSIGN(DownloadsApiTest); -}; - - -IN_PROC_BROWSER_TEST_F(DownloadsApiTest, DownloadsApiTest) { - ASSERT_TRUE(RunExtensionTest("downloads")) << message_; -} diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index 1723089..d3a6e2e 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -578,6 +578,8 @@ enum HistogramValue { SYSTEM_STORAGE_REMOVEAVAILABLECAPACITYWATCH, SYSTEM_STORAGE_GETALLAVAILABLECAPACITYWATCHES, SYSTEM_STORAGE_REMOVEALLAVAILABLECAPACITYWATCHES, + DOWNLOADS_REMOVEFILE, + DOWNLOADS_SHOWDEFAULTFOLDER, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/ui/webui/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads_dom_handler.cc index 72245e1..d31964b 100644 --- a/chrome/browser/ui/webui/downloads_dom_handler.cc +++ b/chrome/browser/ui/webui/downloads_dom_handler.cc @@ -518,14 +518,16 @@ void DownloadsDOMHandler::ShowDangerPrompt( dangerous_item, GetWebUIWebContents(), false, - base::Bind(&DownloadsDOMHandler::DangerPromptAccepted, - weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId()), - base::Closure()); + base::Bind(&DownloadsDOMHandler::DangerPromptDone, + weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId())); // danger_prompt will delete itself. DCHECK(danger_prompt); } -void DownloadsDOMHandler::DangerPromptAccepted(int download_id) { +void DownloadsDOMHandler::DangerPromptDone( + int download_id, DownloadDangerPrompt::Action action) { + if (action != DownloadDangerPrompt::ACCEPT) + return; content::DownloadItem* item = NULL; if (main_notifier_.GetManager()) item = main_notifier_.GetManager()->GetDownload(download_id); diff --git a/chrome/browser/ui/webui/downloads_dom_handler.h b/chrome/browser/ui/webui/downloads_dom_handler.h index 2d3daa2..aee7ed1 100644 --- a/chrome/browser/ui/webui/downloads_dom_handler.h +++ b/chrome/browser/ui/webui/downloads_dom_handler.h @@ -11,6 +11,7 @@ #include "base/compiler_specific.h" #include "base/memory/weak_ptr.h" #include "chrome/browser/download/all_download_item_notifier.h" +#include "chrome/browser/download/download_danger_prompt.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_ui_message_handler.h" @@ -120,7 +121,7 @@ class DownloadsDOMHandler : public content::WebUIMessageHandler, // Conveys danger acceptance from the DownloadDangerPrompt to the // DownloadItem. - void DangerPromptAccepted(int download_id); + void DangerPromptDone(int download_id, DownloadDangerPrompt::Action action); // Returns true if the records of any downloaded items are allowed (and able) // to be deleted. diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index dad04a8..36c9090 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1290,7 +1290,7 @@ 'browser/extensions/api/dns/dns_apitest.cc', 'browser/extensions/api/dns/mock_host_resolver_creator.cc', 'browser/extensions/api/dns/mock_host_resolver_creator.h', - 'browser/extensions/api/downloads/downloads_api_unittest.cc', + 'browser/extensions/api/downloads/downloads_api_browsertest.cc', 'browser/extensions/api/extension_action/browser_action_apitest.cc', 'browser/extensions/api/extension_action/page_action_apitest.cc', 'browser/extensions/api/extension_action/page_as_browser_action_apitest.cc', diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index 6228593..b6152bb 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json @@ -190,7 +190,7 @@ "downloads": { "channel": "beta", "extension_types": [ - "extension", "packaged_app" + "extension" ] }, "downloads.open": { diff --git a/chrome/common/extensions/api/downloads.idl b/chrome/common/extensions/api/downloads.idl index c427026..ee8b012 100644 --- a/chrome/common/extensions/api/downloads.idl +++ b/chrome/common/extensions/api/downloads.idl @@ -22,7 +22,7 @@ namespace downloads { //
prompt
//
The user will be prompted with a file chooser dialog.
// - [inline_doc] enum FilenameConflictAction {uniquify, overwrite, prompt}; + enum FilenameConflictAction {uniquify, overwrite, prompt}; [inline_doc] dictionary FilenameSuggestion { // The $ref:DownloadItem's new target $ref:DownloadItem.filename, as a path @@ -32,23 +32,50 @@ namespace downloads { DOMString filename; // The action to take if filename already exists. - FilenameConflictAction? conflict_action; + FilenameConflictAction? conflictAction; }; [inline_doc] enum HttpMethod {GET, POST}; + enum InterruptReason { + FILE_FAILED, + FILE_ACCESS_DENIED, + FILE_NO_SPACE, + FILE_NAME_TOO_LONG, + FILE_TOO_LARGE, + FILE_VIRUS_INFECTED, + FILE_TRANSIENT_ERROR, + FILE_BLOCKED, + FILE_SECURITY_CHECK_FAILED, + FILE_TOO_SHORT, + NETWORK_FAILED, + NETWORK_TIMEOUT, + NETWORK_DISCONNECTED, + NETWORK_SERVER_DOWN, + SERVER_FAILED, + SERVER_NO_RANGE, + SERVER_PRECONDITION, + SERVER_BAD_CONTENT, + USER_CANCELED, + USER_SHUTDOWN, + CRASH}; + [inline_doc] dictionary DownloadOptions { // The URL to download. DOMString url; // A file path relative to the Downloads directory to contain the downloaded - // file. Cannot yet contain subdirectories; support for subdirectories will - // be implemented before this API is released to the stable channel. See - // $ref:onDeterminingFilename for how to dynamically suggest a filename - // after the file's MIME type and a tentative filename have been determined. + // file, possibly containing subdirectories. Absolute paths, empty paths, + // and paths containing back-references ".." will cause an error. + // $ref:onDeterminingFilename allows suggesting a filename after the file's + // MIME type and a tentative filename have been determined. DOMString? filename; - // Use a file-chooser to allow the user to select a filename. + // The action to take if filename already exists. + FilenameConflictAction? conflictAction; + + // Use a file-chooser to allow the user to select a filename regardless of + // whether filename is set or already exists. boolean? saveAs; // The HTTP method to use if the URL uses the HTTP[S] protocol. @@ -103,6 +130,9 @@ namespace downloads { // Absolute URL. DOMString url; + // Absolute URL. + DOMString referrer; + // Absolute local path. DOMString filename; @@ -114,9 +144,6 @@ namespace downloads { // suspicious. DangerType danger; - // True if the user has accepted the download's danger. - boolean? dangerAccepted; - // The file's MIME type. DOMString mime; @@ -132,6 +159,13 @@ namespace downloads { // console.log(new Date(item.endTime))})}) DOMString? endTime; + // Estimated time when the download will complete in ISO 8601 format. May be + // passed directly to the Date constructor: + // chrome.downloads.search({}, + // function(items){items.forEach(function(item){if (item.estimatedEndTime) + // console.log(new Date(item.estimatedEndTime))})}) + DOMString? estimatedEndTime; + // Indicates whether the download is progressing, interrupted, or complete. State state; @@ -139,8 +173,17 @@ namespace downloads { // connection open. boolean paused; - // Number indicating why a download was interrupted. - long? error; + // True if the download is in progress and paused, or else if it is + // interrupted and can be resumed starting from where it was interrupted. + boolean canResume; + + // Why the download was interrupted. Several kinds of HTTP errors may be + // grouped under one of the errors beginning with SERVER_. + // Errors relating to the network begin with NETWORK_, errors + // relating to the process of writing the file to the file system begin with + // FILE_, and interruptions initiated by the user begin with + // USER_. + InterruptReason? error; // Number of bytes received so far from the host, without considering file // compression. @@ -166,12 +209,11 @@ namespace downloads { }; [inline_doc] dictionary DownloadQuery { - // This space-separated string of search terms that may be grouped using - // quotation marks limits results to - // $ref:DownloadItem whose filename - // or url contain all of the search terms that do not begin with a dash '-' - // and none of the search terms that do begin with a dash. - DOMString? query; + // This array of search terms limits results to $ref:DownloadItem whose + // filename or url contain all of the search terms + // that do not begin with a dash '-' and none of the search terms that do + // begin with a dash. + DOMString[]? query; // Limits results to $ref:DownloadItem that // started before the given ms since the epoch. @@ -205,18 +247,17 @@ namespace downloads { // url matches the given regular expression. DOMString? urlRegex; - // Setting this integer limits the number of results. Otherwise, all - // matching $ref:DownloadItem will be returned. + // The maximum number of matching $ref:DownloadItem returned. Defaults to + // 1000. Set to 0 in order to return all matching $ref:DownloadItem. See + // $ref:search for how to page through results. long? limit; - // Setting this string to a $ref:DownloadItem - // property sorts the $ref:DownloadItem prior - // to applying the above filters. For example, setting - // orderBy='startTime' sorts the - // $ref:DownloadItem by their start time in - // ascending order. To specify descending order, prefix orderBy - // with a hyphen: '-startTime'. - DOMString? orderBy; + // Set elements of this array to $ref:DownloadItem properties in order to + // sort search results. For example, setting + // orderBy=['startTime'] sorts the $ref:DownloadItem by their + // start time in ascending order. To specify descending order, prefix with a + // hyphen: '-startTime'. + DOMString[]? orderBy; // The id of the $ref:DownloadItem to query. long? id; @@ -231,9 +272,6 @@ namespace downloads { // suspicious. DangerType? danger; - // True if the user has accepted the download's danger. - boolean? dangerAccepted; - // The file's MIME type. DOMString? mime; @@ -250,8 +288,8 @@ namespace downloads { // connection open. boolean? paused; - // Number indicating why a download was interrupted. - long? error; + // Why a download was interrupted. + InterruptReason? error; // Number of bytes received so far from the host, without considering file // compression. @@ -298,9 +336,6 @@ namespace downloads { // The change in danger, if any. StringDelta? danger; - // The change in dangerAccepted, if any. - BooleanDelta? dangerAccepted; - // The change in mime, if any. StringDelta? mime; @@ -313,11 +348,14 @@ namespace downloads { // The change in state, if any. StringDelta? state; + // The change in canResume, if any. + BooleanDelta? canResume; + // The change in paused, if any. BooleanDelta? paused; // The change in error, if any. - LongDelta? error; + StringDelta? error; // The change in totalBytes, if any. LongDelta? totalBytes; @@ -360,27 +398,24 @@ namespace downloads { static void download(DownloadOptions options, optional DownloadCallback callback); - // Find $ref:DownloadItem. Set - // query to the empty object to get all - // $ref:DownloadItem. To get a specific - // $ref:DownloadItem, set only the id - // field. + // Find $ref:DownloadItem. Set query to the empty object to get + // all $ref:DownloadItem. To get a specific $ref:DownloadItem, set only the + // id field. To page through a large number of items, set + // orderBy: ['-startTime'], set limit to the + // number of items per page, and set startedAfter to the + // startTime of the last item from the last page. static void search(DownloadQuery query, SearchCallback callback); // Pause the download. If the request was successful the download is in a - // paused state. Otherwise - // $ref:runtime.lastError - // contains an error message. The request will fail if the download is not - // active. + // paused state. Otherwise $ref:runtime.lastError contains an error message. + // The request will fail if the download is not active. // |downloadId|: The id of the download to pause. // |callback|: Called when the pause request is completed. static void pause(long downloadId, optional NullCallback callback); // Resume a paused download. If the request was successful the download is - // in progress and unpaused. Otherwise - // $ref:runtime.lastError - // contains an error message. The request will fail if the download is not - // active. + // in progress and unpaused. Otherwise $ref:runtime.lastError contains an + // error message. The request will fail if the download is not active. // |downloadId|: The id of the download to resume. // |callback|: Called when the resume request is completed. static void resume(long downloadId, optional NullCallback callback); @@ -392,16 +427,14 @@ namespace downloads { static void cancel(long downloadId, optional NullCallback callback); // Retrieve an icon for the specified download. For new downloads, file - // icons are available after the $ref:onCreated - // event has been received. The image returned by this function while a - // download is in progress may be different from the image returned after - // the download is complete. Icon retrieval is done by querying the - // underlying operating system or toolkit depending on the platform. The - // icon that is returned will therefore depend on a number of factors - // including state of the download, platform, registered file types and - // visual theme. If a file icon cannot be determined, - // $ref:runtime.lastError - // will contain an error message. + // icons are available after the $ref:onCreated event has been received. The + // image returned by this function while a download is in progress may be + // different from the image returned after the download is complete. Icon + // retrieval is done by querying the underlying operating system or toolkit + // depending on the platform. The icon that is returned will therefore + // depend on a number of factors including state of the download, platform, + // registered file types and visual theme. If a file icon cannot be + // determined, $ref:runtime.lastError will contain an error message. // |downloadId|: The identifier for the download. // |callback|: A URL to an image that represents the download. static void getFileIcon(long downloadId, @@ -409,7 +442,7 @@ namespace downloads { GetFileIconCallback callback); // Open the downloaded file now if the $ref:DownloadItem is complete; - // returns an error through $ref:runtime.lastError otherwise. Requires the + // otherwise returns an error through $ref:runtime.lastError. Requires the // "downloads.open" permission in addition to the // "downloads" permission. An $ref:onChanged event will fire // when the item is opened for the first time. @@ -420,11 +453,19 @@ namespace downloads { // |downloadId|: The identifier for the downloaded file. static void show(long downloadId); - // Erase matching $ref:DownloadItem from history. An $ref:onErased event - // will fire for each $ref:DownloadItem that matches query, - // then callback will be called. + // Show the default Downloads folder in a file manager. + static void showDefaultFolder(); + + // Erase matching $ref:DownloadItem from history without deleting the + // downloaded file. An $ref:onErased event will fire for each + // $ref:DownloadItem that matches query, then + // callback will be called. static void erase(DownloadQuery query, optional EraseCallback callback); + // Remove the downloaded file if it exists and the $ref:DownloadItem is + // complete; otherwise return an error through $ref:runtime.lastError. + static void removeFile(long downloadId, optional NullCallback callback); + // Prompt the user to accept a dangerous download. Does not automatically // accept dangerous downloads. If the download is accepted, then an // $ref:onChanged event will fire, otherwise nothing will happen. When all @@ -433,15 +474,17 @@ namespace downloads { // renamed to the target filename, the |state| changes to 'complete', and // $ref:onChanged fires. // |downloadId|: The identifier for the $ref:DownloadItem. - static void acceptDanger(long downloadId); + // |callback|: Called when the danger prompt dialog closes. + static void acceptDanger(long downloadId, optional NullCallback callback); - // Initiate dragging the downloaded file to another application. + // Initiate dragging the downloaded file to another application. Call in a + // javascript ondragstart handler. static void drag(long downloadId); }; interface Events { - // This event fires with the $ref:DownloadItem - // object when a download begins. + // This event fires with the $ref:DownloadItem object when a download + // begins. static void onCreated(DownloadItem downloadItem); // Fires with the downloadId when a download is erased from @@ -451,9 +494,9 @@ namespace downloads { static void onErased(long downloadId); // When any of a $ref:DownloadItem's properties except - // bytesReceived changes, this event fires with the - // downloadId and an object containing the properties that - // changed. + // bytesReceived and estimatedEndTime changes, + // this event fires with the downloadId and an object + // containing the properties that changed. static void onChanged(DownloadDelta downloadDelta); // During the filename determination process, extensions will be given the diff --git a/chrome/renderer/resources/extensions/downloads_custom_bindings.js b/chrome/renderer/resources/extensions/downloads_custom_bindings.js index be7d1c4..88efbac 100644 --- a/chrome/renderer/resources/extensions/downloads_custom_bindings.js +++ b/chrome/renderer/resources/extensions/downloads_custom_bindings.js @@ -16,19 +16,36 @@ eventBindings.registerArgumentMassager( // Copy the id so that extensions can't change it. var downloadId = downloadItem.id; var suggestable = true; + function isValidResult(result) { + if (result === undefined) + return false; + if (typeof(result) != 'object') { + console.error('Error: Invocation of form suggest(' + typeof(result) + + ') doesn\'t match definition suggest({filename: string, ' + + 'conflictAction: string})'); + return false; + } else if ((typeof(result.filename) != 'string') || + (result.filename.length == 0)) { + console.error('Error: "filename" parameter to suggest() must be a ' + + 'non-empty string'); + return false; + } else if ([undefined, 'uniquify', 'overwrite', 'prompt'].indexOf( + result.conflictAction) < 0) { + console.error('Error: "conflictAction" parameter to suggest() must be ' + + 'one of undefined, "uniquify", "overwrite", "prompt"'); + return false; + } + return true; + } function suggestCallback(result) { if (!suggestable) { console.error('suggestCallback may not be called more than once.'); return; } suggestable = false; - if ((typeof(result) == 'object') && - result.filename && - (typeof(result.filename) == 'string') && - ((result.conflict_action == undefined) || - (typeof(result.conflict_action) == 'string'))) { + if (isValidResult(result)) { downloadsInternal.determineFilename( - downloadId, result.filename, result.conflict_action || ""); + downloadId, result.filename, result.conflictAction || ""); } else { downloadsInternal.determineFilename(downloadId, "", ""); } diff --git a/content/browser/download/download_item_impl.cc b/content/browser/download/download_item_impl.cc index 5471df2..b49a256 100644 --- a/content/browser/download/download_item_impl.cc +++ b/content/browser/download/download_item_impl.cc @@ -608,6 +608,20 @@ bool DownloadItemImpl::GetFileExternallyRemoved() const { return file_externally_removed_; } +void DownloadItemImpl::DeleteFile() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if ((GetState() != DownloadItem::COMPLETE) || + file_externally_removed_) { + return; + } + BrowserThread::PostTaskAndReply( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DeleteDownloadedFile, current_path_), + base::Bind(&DownloadItemImpl::OnDownloadedFileRemoved, + weak_ptr_factory_.GetWeakPtr())); + current_path_.clear(); +} + bool DownloadItemImpl::IsDangerous() const { #if defined(OS_WIN) // TODO(noelutz): At this point only the windows views UI supports diff --git a/content/browser/download/download_item_impl.h b/content/browser/download/download_item_impl.h index b57bae0..ac260b1 100644 --- a/content/browser/download/download_item_impl.h +++ b/content/browser/download/download_item_impl.h @@ -127,6 +127,7 @@ class CONTENT_EXPORT DownloadItemImpl virtual const std::string& GetHash() const OVERRIDE; virtual const std::string& GetHashState() const OVERRIDE; virtual bool GetFileExternallyRemoved() const OVERRIDE; + virtual void DeleteFile() OVERRIDE; virtual bool IsDangerous() const OVERRIDE; virtual DownloadDangerType GetDangerType() const OVERRIDE; virtual bool TimeRemaining(base::TimeDelta* remaining) const OVERRIDE; diff --git a/content/public/browser/download_item.h b/content/public/browser/download_item.h index ec29c94..5ef9597 100644 --- a/content/public/browser/download_item.h +++ b/content/public/browser/download_item.h @@ -235,6 +235,12 @@ class CONTENT_EXPORT DownloadItem : public base::SupportsUserData { // external action. virtual bool GetFileExternallyRemoved() const = 0; + // If the file is successfully deleted, then GetFileExternallyRemoved() will + // become true and DownloadItem::OnDownloadUpdated() will be called. Does + // nothing if GetState() == COMPLETE or GetFileExternallyRemoved() is already + // true. + virtual void DeleteFile() = 0; + // True if the file that will be written by the download is dangerous // and we will require a call to ValidateDangerousDownload() to complete. // False if the download is safe or that function has been called. diff --git a/content/public/test/mock_download_item.h b/content/public/test/mock_download_item.h index 8578f5e..6dfbf02 100644 --- a/content/public/test/mock_download_item.h +++ b/content/public/test/mock_download_item.h @@ -60,6 +60,7 @@ class MockDownloadItem : public DownloadItem { MOCK_CONST_METHOD0(GetHash, const std::string&()); MOCK_CONST_METHOD0(GetHashState, const std::string&()); MOCK_CONST_METHOD0(GetFileExternallyRemoved, bool()); + MOCK_METHOD0(DeleteFile, void()); MOCK_CONST_METHOD0(IsDangerous, bool()); MOCK_CONST_METHOD0(GetDangerType, DownloadDangerType()); MOCK_CONST_METHOD1(TimeRemaining, bool(base::TimeDelta*)); -- cgit v1.1