1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_
#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_
#include <stddef.h>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "url/origin.h"
namespace content {
class WebContents;
}
// DownloadRequestLimiter is responsible for determining whether a download
// should be allowed or not. It is designed to keep pages from downloading
// multiple files without user interaction. DownloadRequestLimiter is invoked
// from ResourceDispatcherHost any time a download begins
// (CanDownload). The request is processed on the UI thread, and the
// request is notified (back on the IO thread) as to whether the download should
// be allowed or denied.
//
// Invoking CanDownload notifies the callback and may update the
// download status. The following details the various states:
// . Each NavigationController initially starts out allowing a download
// (ALLOW_ONE_DOWNLOAD).
// . The first time CanDownload is invoked the download is allowed and
// the state changes to PROMPT_BEFORE_DOWNLOAD.
// . If the state is PROMPT_BEFORE_DOWNLOAD and the user clicks the mouse,
// presses enter, the space bar or navigates to another page the state is
// reset to ALLOW_ONE_DOWNLOAD.
// . If a download is attempted and the state is PROMPT_BEFORE_DOWNLOAD the user
// is prompted as to whether the download is allowed or disallowed. The users
// choice stays until the user navigates to a different host. For example, if
// the user allowed the download, multiple downloads are allowed without any
// user intervention until the user navigates to a different host.
//
// The DownloadUiStatus indicates whether omnibox UI should be shown for the
// current download status. We do not show UI if there has not yet been
// a download attempt on the page regardless of the internal download status.
class DownloadRequestLimiter
: public base::RefCountedThreadSafe<DownloadRequestLimiter> {
public:
// Download status for a particular page. See class description for details.
enum DownloadStatus {
ALLOW_ONE_DOWNLOAD,
PROMPT_BEFORE_DOWNLOAD,
ALLOW_ALL_DOWNLOADS,
DOWNLOADS_NOT_ALLOWED
};
// Download UI state given the current download status for a page.
enum DownloadUiStatus {
DOWNLOAD_UI_DEFAULT,
DOWNLOAD_UI_ALLOWED,
DOWNLOAD_UI_BLOCKED
};
// Max number of downloads before a "Prompt Before Download" Dialog is shown.
static const size_t kMaxDownloadsAtOnce = 50;
// The callback from CanDownload. This is invoked on the IO thread.
// The boolean parameter indicates whether or not the download is allowed.
using Callback = base::OnceCallback<void(bool /*allow*/)>;
// TabDownloadState maintains the download state for a particular tab.
// TabDownloadState prompts the user with an infobar as necessary.
// TabDownloadState deletes itself (by invoking
// DownloadRequestLimiter::Remove) as necessary.
// TODO(gbillock): just make this class implement
// permissions::PermissionRequest.
class TabDownloadState : public content_settings::Observer,
public content::WebContentsObserver {
public:
// Creates a new TabDownloadState. |host| is DownloadRequestLimiter object
// that owns this object. This object will listen to all the navigations
// and downloads happening on the |web_contents| to determine the new
// download status.
TabDownloadState(DownloadRequestLimiter* host,
content::WebContents* web_contents);
TabDownloadState(const TabDownloadState&) = delete;
TabDownloadState& operator=(const TabDownloadState&) = delete;
~TabDownloadState() override;
// Sets the current limiter state and the underlying automatic downloads
// content setting. Sends a notification that the content setting has been
// changed (if it has changed).
void SetDownloadStatusAndNotify(const url::Origin& request_origin,
DownloadStatus status);
// Status of the download.
DownloadStatus download_status() const { return status_; }
// The omnibox UI to be showing (or none if we shouldn't show any).
DownloadUiStatus download_ui_status() const { return ui_status_; }
// Number of "ALLOWED" downloads.
void increment_download_count() {
download_count_++;
}
size_t download_count() const {
return download_count_;
}
const url::Origin& origin() const { return origin_; }
bool download_seen() const { return download_seen_; }
void set_download_seen() { download_seen_ = true; }
// content::WebContentsObserver overrides.
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void DidGetUserInteraction(const blink::WebInputEvent& event) override;
void WebContentsDestroyed() override;
// Asks the user if they really want to allow the download.
// See description above CanDownload for details on lifetime of callback.
void PromptUserForDownload(DownloadRequestLimiter::Callback callback,
const url::Origin& request_origin);
// Invoked from DownloadRequestDialogDelegate. Notifies the delegates and
// changes the status appropriately. Virtual for testing.
virtual void Cancel(const url::Origin& request_origin);
virtual void CancelOnce(const url::Origin& request_origin);
virtual void Accept(const url::Origin& request_origin);
DownloadStatus GetDownloadStatus(const url::Origin& request_origin);
protected:
// Used for testing.
TabDownloadState();
private:
// Are we showing a prompt to the user? Determined by whether
// we have an outstanding weak pointer--weak pointers are only
// given to the info bar delegate or permission bubble request.
bool is_showing_prompt() const;
// This may result in invoking Remove on DownloadRequestLimiter.
void OnUserInteraction();
// content_settings::Observer overrides.
void OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsTypeSet content_type_set) override;
// Remember to either block or allow automatic downloads from
// |request_origin|.
void SetContentSetting(ContentSetting setting,
const url::Origin& request_origin);
// Notifies the callbacks as to whether the download is allowed or not.
// Returns false if it didn't notify all callbacks.
bool NotifyCallbacks(bool allow);
// Set the download limiter state and notify if it has changed. Callers must
// guarantee that |status| and |setting| correspond to each other.
void SetDownloadStatusAndNotifyImpl(const url::Origin& request_origin,
DownloadStatus status,
ContentSetting setting);
// Check if the navigation should clear the download state. If an origin is
// in a limited state, history forward/backward shouldn't clear the download
// state.
bool shouldClearDownloadState(content::NavigationHandle* navigation_handle);
raw_ptr<content::WebContents> web_contents_;
raw_ptr<DownloadRequestLimiter> host_;
// Current tab status and UI status. Renderer initiated navigations will
// not change these values if the current tab state is restricted.
DownloadStatus status_;
DownloadUiStatus ui_status_;
// Origin for initiating the current download. The value was kept for
// updating the omnibox decoration.
url::Origin origin_;
size_t download_count_;
// True if a download has been seen on the current page load.
bool download_seen_;
// Callbacks we need to notify. This is only non-empty if we're showing a
// dialog.
// See description above CanDownload for details on lifetime of callbacks.
std::vector<DownloadRequestLimiter::Callback> callbacks_;
// Origins that have non-default download state.
using DownloadStatusMap = std::map<url::Origin, DownloadStatus>;
DownloadStatusMap download_status_map_;
base::ScopedObservation<HostContentSettingsMap, content_settings::Observer>
observation_{this};
// Weak pointer factory for generating a weak pointer to pass to the
// infobar. User responses to the throttling prompt will be returned
// through this channel, and it can be revoked if the user prompt result
// becomes moot.
base::WeakPtrFactory<DownloadRequestLimiter::TabDownloadState> factory_{
this};
};
DownloadRequestLimiter();
DownloadRequestLimiter(const DownloadRequestLimiter&) = delete;
DownloadRequestLimiter& operator=(const DownloadRequestLimiter&) = delete;
// Returns the download status for a page. This does not change the state in
// anyway.
DownloadStatus GetDownloadStatus(content::WebContents* web_contents);
// Returns the download UI status for a page for the purposes of showing an
// omnibox decoration.
DownloadUiStatus GetDownloadUiStatus(content::WebContents* web_contents);
// Returns the download origin that is associated with the current UI status
// for the purposes of showing an omnibox decoration.
GURL GetDownloadOrigin(content::WebContents* web_contents);
// Check if download can proceed and notifies the callback on UI thread.
void CanDownload(const content::WebContents::Getter& web_contents_getter,
const GURL& url,
const std::string& request_method,
std::optional<url::Origin> request_initiator,
bool from_download_cross_origin_redirect,
Callback callback);
private:
FRIEND_TEST_ALL_PREFIXES(DownloadTest, DownloadResourceThrottleCancels);
FRIEND_TEST_ALL_PREFIXES(DownloadTest,
DownloadRequestLimiterDisallowsAnchorDownloadTag);
FRIEND_TEST_ALL_PREFIXES(DownloadTest,
CrossOriginRedirectDownloadFromAnchorDownload);
FRIEND_TEST_ALL_PREFIXES(
DownloadTest,
MultipleCrossOriginRedirectDownloadsFromAnchorDownload);
FRIEND_TEST_ALL_PREFIXES(DownloadTest, MultipleDownloadsFromIframeSrcdoc);
FRIEND_TEST_ALL_PREFIXES(ContentSettingBubbleControllerTest, Init);
FRIEND_TEST_ALL_PREFIXES(ContentSettingImageModelBrowserTest,
CreateBubbleModel);
FRIEND_TEST_ALL_PREFIXES(PrerenderDownloadTest,
DownloadRequestLimiterIsUnaffectedByPrerendering);
FRIEND_TEST_ALL_PREFIXES(FencedFrameDownloadTest,
DownloadRequestLimiterIsUnaffectedByFencedFrame);
friend class base::RefCountedThreadSafe<DownloadRequestLimiter>;
friend class BackgroundFetchBrowserTest;
friend class ContentSettingBubbleDialogTest;
friend class DownloadRequestLimiterTest;
friend class TabDownloadState;
~DownloadRequestLimiter();
// Gets the download state for the specified controller. If the
// TabDownloadState does not exist and |create| is true, one is created.
// See TabDownloadState's constructor description for details on the two
// controllers.
//
// The returned TabDownloadState is owned by the DownloadRequestLimiter and
// deleted when no longer needed (the Remove method is invoked).
TabDownloadState* GetDownloadState(content::WebContents* web_contents,
bool create);
// Does the work of updating the download status on the UI thread and
// potentially prompting the user.
void CanDownloadImpl(const GURL& url,
content::WebContents* originating_contents,
const std::string& request_method,
std::optional<url::Origin> request_initiator,
bool from_download_cross_origin_redirect,
Callback callback);
// Invoked when decision to download has been made.
void OnCanDownloadDecided(
const GURL& url,
const content::WebContents::Getter& web_contents_getter,
const std::string& request_method,
std::optional<url::Origin> request_initiator,
bool from_download_cross_origin_redirect,
Callback orig_callback,
bool allow);
// Removes the specified TabDownloadState from the internal map and deletes
// it. This has the effect of resetting the status for the tab to
// ALLOW_ONE_DOWNLOAD.
void Remove(TabDownloadState* state, content::WebContents* contents);
static HostContentSettingsMap* GetContentSettings(
content::WebContents* contents);
// Gets the content setting for a particular request initiator.
static ContentSetting GetAutoDownloadContentSetting(
content::WebContents* contents,
const GURL& request_initiator);
// Sets the callback for tests to know the result of OnCanDownloadDecided().
using CanDownloadDecidedCallback =
base::RepeatingCallback<void(bool /*allow*/)>;
void SetOnCanDownloadDecidedCallbackForTesting(
CanDownloadDecidedCallback callback);
// TODO(bauerb): Change this to use WebContentsUserData.
// Maps from tab to download state. The download state for a tab only exists
// if the state is other than ALLOW_ONE_DOWNLOAD. Similarly once the state
// transitions from anything but ALLOW_ONE_DOWNLOAD back to ALLOW_ONE_DOWNLOAD
// the TabDownloadState is removed and deleted (by way of Remove).
typedef std::map<content::WebContents*,
raw_ptr<TabDownloadState, CtnExperimental>>
StateMap;
StateMap state_map_;
CanDownloadDecidedCallback on_can_download_decided_callback_;
// Weak ptr factory used when |CanDownload| asks the delegate asynchronously
// about the download.
base::WeakPtrFactory<DownloadRequestLimiter> factory_{this};
};
#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_
|