File: download_request_limiter.h

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (342 lines) | stat: -rw-r--r-- 14,698 bytes parent folder | download | duplicates (4)
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_