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