Resource requests from Save-Page-As should go through CanRequestURL checks.

This CL:

- Added checks to ResourceDispatcherHostImpl::BeginSaveFile to verify if
  the renderer process is authorized to access a given resource.

- Removed separate code path for file: URIs that used to be implemented
  in SaveFileManager::SaveLocalFile.  Avoiding a separate code path
  helps consolidate all authorization checks in one place.

BUG=616429

Review-Url: https://codereview.chromium.org/2075273002
Cr-Commit-Position: refs/heads/master@{#408235}
diff --git a/chrome/browser/download/save_page_browsertest.cc b/chrome/browser/download/save_page_browsertest.cc
index 544d499..322a15e 100644
--- a/chrome/browser/download/save_page_browsertest.cc
+++ b/chrome/browser/download/save_page_browsertest.cc
@@ -18,6 +18,7 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/test_file_util.h"
 #include "build/build_config.h"
@@ -811,6 +812,41 @@
   EXPECT_TRUE(base::PathExists(dir.AppendASCII("no-such-file.html")));
 }
 
+// Test that file: URI won't be saved when referred to from an HTTP page.
+// See also https://crbug.com/616429.
+IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveUnauthorizedResource) {
+  GURL url = NavigateToMockURL("unauthorized-access");
+
+  // Create a test file (that the web page should not have access to).
+  base::ScopedTempDir temp_dir2;
+  ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
+  base::FilePath file_path =
+      temp_dir2.path().Append(FILE_PATH_LITERAL("should-not-save.jpg"));
+  std::string file_content("fake-jpg");
+  ASSERT_LT(
+      0, base::WriteFile(file_path, file_content.data(), file_content.size()));
+
+  // Refer to the test file from the test page.
+  GURL file_url = net::FilePathToFileURL(file_path);
+  ASSERT_TRUE(ExecuteScript(
+      browser()->tab_strip_model()->GetWebContentsAt(0),
+      base::StringPrintf("document.getElementById('resource1').src = '%s';",
+                         file_url.spec().data())));
+
+  // Save the current page.
+  base::FilePath full_file_name, dir;
+  SaveCurrentTab(url, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML,
+                 "unauthorized-access", 2, &dir, &full_file_name);
+
+  // We should not save resource that the web page didn't have access to.
+  // (because executing a resource request can have side effects - for example
+  // after https://crbug.com/590714 a website from the internet should not be
+  // able to issue a resource request to an intranet website and trigger
+  // server-side actions in the internet;  this test uses a file: URI as a
+  // canary for detecting whether a website can access restricted resources).
+  EXPECT_FALSE(base::PathExists(dir.AppendASCII("should-not-save.jpg")));
+}
+
 // Test suite that allows testing --site-per-process against cross-site frames.
 // See http://dev.chromium.org/developers/design-documents/site-isolation.
 class SavePageSitePerProcessBrowserTest : public SavePageBrowserTest {
@@ -1021,6 +1057,38 @@
                                       expected_substrings);
   }
 
+  // Helper method to deduplicate some code across 2 tests.
+  void RunObjectElementsTest(GURL url) {
+    content::SavePageType save_page_type = GetParam();
+
+    // 7 comes from:
+    // - main frame (frames-objects.htm)
+    // - object with frame-nested.htm + 2 subframes (frames-nested2.htm + b.htm)
+    // - iframe with a.htm
+    // - object with svg.svg
+    // - object with text.txt
+    // (pdf and png objects do not get a separate frame)
+    int expected_number_of_frames = 7;
+
+    std::string arr[] = {
+        "frames-objects.htm: 8da13db4-a512-4d9b-b1c5-dc1c134234b9",
+        "a.htm: 1b8aae2b-e164-462f-bd5b-98aa366205f2",
+        "b.htm: 3a35f7fa-96a9-4487-9f18-4470263907fa",
+        "frames-nested.htm: 4388232f-8d45-4d2e-9807-721b381be153",
+        "frames-nested2.htm: 6d23dc47-f283-4977-96ec-66bcf72301a4",
+        "text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2",
+    };
+    std::vector<std::string> expected_substrings(std::begin(arr),
+                                                 std::end(arr));
+
+    // TODO(lukasza): crbug.com/553478: Enable <object> testing of MHTML.
+    if (save_page_type == content::SAVE_PAGE_TYPE_AS_MHTML)
+      return;
+
+    TestOriginalVsSavedPage(save_page_type, url, expected_number_of_frames,
+                            expected_substrings);
+  }
+
  private:
   void AssertExpectationsAboutCurrentTab(
       int expected_number_of_frames,
@@ -1090,31 +1158,24 @@
 
 // Test compares original-vs-saved for a page with <object> elements.
 // (see crbug.com/553478).
-IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, ObjectElements) {
-  content::SavePageType save_page_type = GetParam();
-
-  // 4 = main frame + iframe + object w/ html doc + object w/ pdf doc
-  // (svg and png objects do not get a separate frame)
-  int expected_number_of_frames = 6;
-
-  std::string arr[] = {
-      "frames-objects.htm: 8da13db4-a512-4d9b-b1c5-dc1c134234b9",
-      "a.htm: 1b8aae2b-e164-462f-bd5b-98aa366205f2",
-      "b.htm: 3a35f7fa-96a9-4487-9f18-4470263907fa",
-      "frames-nested.htm: 4388232f-8d45-4d2e-9807-721b381be153",
-      "frames-nested2.htm: 6d23dc47-f283-4977-96ec-66bcf72301a4",
-  };
-  std::vector<std::string> expected_substrings(std::begin(arr), std::end(arr));
-
+IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
+                       ObjectElementsViaHttp) {
   GURL url(
       embedded_test_server()->GetURL("a.com", "/save_page/frames-objects.htm"));
 
-  // TODO(lukasza): crbug.com/553478: Enable <object> testing of MHTML.
-  if (save_page_type == content::SAVE_PAGE_TYPE_AS_MHTML)
-    return;
+  RunObjectElementsTest(url);
+}
 
-  TestOriginalVsSavedPage(save_page_type, url, expected_number_of_frames,
-                          expected_substrings);
+// Tests that saving a page from file: URI works.
+IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
+                       ObjectElementsViaFile) {
+  base::FilePath test_data_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
+  GURL url(net::FilePathToFileURL(
+      test_data_dir.Append(FILE_PATH_LITERAL("save_page/frames-objects.htm"))));
+  EXPECT_TRUE(url.SchemeIsFile());
+
+  RunObjectElementsTest(url);
 }
 
 // Test compares original-vs-saved for a page with frames at about:blank uri.
diff --git a/chrome/test/data/save_page/frames-objects.htm b/chrome/test/data/save_page/frames-objects.htm
index 974bf8e..2ec98c1 100644
--- a/chrome/test/data/save_page/frames-objects.htm
+++ b/chrome/test/data/save_page/frames-objects.htm
@@ -37,5 +37,11 @@
     <br>
     <br>
 
+    Plain text file:
+    <br>
+    <object data="text.txt" border="1">err_no_object_data</object>
+    <br>
+    <br>
+
   </body>
 </html>
diff --git a/chrome/test/data/save_page/text.txt b/chrome/test/data/save_page/text.txt
new file mode 100644
index 0000000..1d68473
--- /dev/null
+++ b/chrome/test/data/save_page/text.txt
@@ -0,0 +1 @@
+text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2
diff --git a/chrome/test/data/save_page/unauthorized-access.htm b/chrome/test/data/save_page/unauthorized-access.htm
new file mode 100644
index 0000000..2826319
--- /dev/null
+++ b/chrome/test/data/save_page/unauthorized-access.htm
@@ -0,0 +1,16 @@
+<html>
+  <head>
+    <title>
+      Test page for saving page feature
+    </title>
+  </head>
+  <body>
+    When this page is loaded over HTTP (i.e. via embedded_test_server() from a
+    browser test), then it should be denied access to local resources, like the
+    file: URI from the img tag below.  This test verifies that access to the
+    file: URI will also be forbidden when saving all resources of the page
+    during Save-Page-As-Complete-HTML.
+    <img id="resource1"
+         src="file:///path-to-be-set-at-test-runtime/should-not-save.txt"></img>
+  </body>
+</html>
diff --git a/content/browser/download/docs/save-page-as.md b/content/browser/download/docs/save-page-as.md
index dba234c..a4b6fd24 100644
--- a/content/browser/download/docs/save-page-as.md
+++ b/content/browser/download/docs/save-page-as.md
@@ -16,10 +16,9 @@
     * UI-thread object
 
 * SaveFileCreateInfo::SaveFileSource enum
-    * classifies `SaveItem` and `SaveFile` processing into 3 flavours:
+    * classifies `SaveItem` and `SaveFile` processing into 2 flavours:
         * `SAVE_FILE_FROM_NET` (see `SaveFileResourceHandler`)
         * `SAVE_FILE_FROM_DOM` (see "Complete HTML" section below)
-        * `SAVE_FILE_FROM_FILE` (see `SaveFileManager::SaveLocalFile`)
 
 * SaveItem class
     * tracks saving a single file
@@ -68,8 +67,8 @@
 * Step 1: `SavePackage` asks all frames for "savable resources"
           and creates `SaveItem` for each of files that need to be saved
 
-* Step 2: `SavePackage` first processes `SAVE_FILE_FROM_NET` and
-          `SAVE_FILE_FROM_FILE` `SaveItem`s and asks `SaveFileManager` to save
+* Step 2: `SavePackage` first processes `SAVE_FILE_FROM_NET`
+          `SaveItem`s and asks `SaveFileManager` to save
           them.
 
 * Step 3: `SavePackage` handles remaining `SAVE_FILE_FROM_DOM` `SaveItem`s and
@@ -109,8 +108,8 @@
 
 Very high-level flow of saving a page as "HTML Only":
 
-* `SavePackage` creates only a single `SaveItem` (either `SAVE_FILE_FROM_NET` or
-  `SAVE_FILE_FROM_FILE`) and asks `SaveFileManager` to process it
+* `SavePackage` creates only a single `SaveItem` (always `SAVE_FILE_FROM_NET`)
+  and asks `SaveFileManager` to process it
   (as in the Complete HTML individual SaveItem handling above.).
 
 
diff --git a/content/browser/download/save_file_manager.cc b/content/browser/download/save_file_manager.cc
index 09e97b5..dc2e1a31c 100644
--- a/content/browser/download/save_file_manager.cc
+++ b/content/browser/download/save_file_manager.cc
@@ -19,7 +19,6 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
-#include "net/base/filename_util.h"
 #include "net/base/io_buffer.h"
 #include "url/gurl.h"
 
@@ -322,41 +321,6 @@
   }
 }
 
-// It is possible that SaveItem which has specified save_item_id has been
-// canceled
-// before this function runs. So if we can not find corresponding SaveFile by
-// using specified save_item_id, just return.
-void SaveFileManager::SaveLocalFile(const GURL& original_file_url,
-                                    SaveItemId save_item_id,
-                                    SavePackageId save_package_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
-  SaveFile* save_file = LookupSaveFile(save_item_id);
-  if (!save_file)
-    return;
-  // If it has finished, just return.
-  if (!save_file->InProgress())
-    return;
-
-  // Close the save file before the copy operation.
-  save_file->Finish();
-  save_file->Detach();
-
-  DCHECK(original_file_url.SchemeIsFile());
-  base::FilePath file_path;
-  net::FileURLToFilePath(original_file_url, &file_path);
-  // If we can not get valid file path from original URL, treat it as
-  // disk error.
-  if (file_path.empty())
-    SaveFinished(save_item_id, save_package_id, false);
-
-  // Copy the local file to the temporary file. It will be renamed to its
-  // final name later.
-  bool success = base::CopyFile(file_path, save_file->FullPath());
-  if (!success)
-    base::DeleteFile(save_file->FullPath(), false);
-  SaveFinished(save_item_id, save_package_id, success);
-}
-
 void SaveFileManager::OnDeleteDirectoryOrFile(const base::FilePath& full_path,
                                               bool is_dir) {
   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
diff --git a/content/browser/download/save_file_resource_handler.cc b/content/browser/download/save_file_resource_handler.cc
index 969e2db..6a42c5e 100644
--- a/content/browser/download/save_file_resource_handler.cc
+++ b/content/browser/download/save_file_resource_handler.cc
@@ -16,13 +16,15 @@
 
 namespace content {
 
-SaveFileResourceHandler::SaveFileResourceHandler(net::URLRequest* request,
-                                                 SaveItemId save_item_id,
-                                                 SavePackageId save_package_id,
-                                                 int render_process_host_id,
-                                                 int render_frame_routing_id,
-                                                 const GURL& url,
-                                                 SaveFileManager* manager)
+SaveFileResourceHandler::SaveFileResourceHandler(
+    net::URLRequest* request,
+    SaveItemId save_item_id,
+    SavePackageId save_package_id,
+    int render_process_host_id,
+    int render_frame_routing_id,
+    const GURL& url,
+    SaveFileManager* manager,
+    AuthorizationState authorization_state)
     : ResourceHandler(request),
       save_item_id_(save_item_id),
       save_package_id_(save_package_id),
@@ -30,7 +32,8 @@
       render_frame_routing_id_(render_frame_routing_id),
       url_(url),
       content_length_(0),
-      save_manager_(manager) {}
+      save_manager_(manager),
+      authorization_state_(authorization_state) {}
 
 SaveFileResourceHandler::~SaveFileResourceHandler() {
 }
@@ -57,12 +60,13 @@
 }
 
 bool SaveFileResourceHandler::OnWillStart(const GURL& url, bool* defer) {
-  return true;
+  return authorization_state_ == AuthorizationState::AUTHORIZED;
 }
 
 bool SaveFileResourceHandler::OnWillRead(scoped_refptr<net::IOBuffer>* buf,
                                          int* buf_size,
                                          int min_size) {
+  DCHECK_EQ(AuthorizationState::AUTHORIZED, authorization_state_);
   DCHECK(buf && buf_size);
   if (!read_buffer_.get()) {
     *buf_size = min_size < 0 ? kReadBufSize : min_size;
@@ -73,6 +77,7 @@
 }
 
 bool SaveFileResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
+  DCHECK_EQ(AuthorizationState::AUTHORIZED, authorization_state_);
   DCHECK(read_buffer_.get());
   // We are passing ownership of this buffer to the save file manager.
   scoped_refptr<net::IOBuffer> buffer;
@@ -88,6 +93,9 @@
     const net::URLRequestStatus& status,
     const std::string& security_info,
     bool* defer) {
+  if (authorization_state_ != AuthorizationState::AUTHORIZED)
+    DCHECK(!status.is_success());
+
   BrowserThread::PostTask(
       BrowserThread::FILE, FROM_HERE,
       base::Bind(&SaveFileManager::SaveFinished, save_manager_, save_item_id_,
diff --git a/content/browser/download/save_file_resource_handler.h b/content/browser/download/save_file_resource_handler.h
index 2ab8b2f..d518a0a 100644
--- a/content/browser/download/save_file_resource_handler.h
+++ b/content/browser/download/save_file_resource_handler.h
@@ -25,13 +25,26 @@
 // Forwards data to the save thread.
 class SaveFileResourceHandler : public ResourceHandler {
  public:
+  // Unauthorized requests are cancelled from OnWillStart callback.
+  //
+  // This way of handling unauthorized requests allows unified handling of all
+  // SaveFile requests - communicating the failure to OnResponseCompleted
+  // happens in a generic, typical way, reusing common infrastructure code
+  // (rather than forcing an ad-hoc, Save-File-specific call to
+  // OnResponseCompleted from ResourceDispatcherHostImpl::BeginSaveFile).
+  enum class AuthorizationState {
+    AUTHORIZED,
+    NOT_AUTHORIZED,
+  };
+
   SaveFileResourceHandler(net::URLRequest* request,
                           SaveItemId save_item_id,
                           SavePackageId save_package_id,
                           int render_process_host_id,
                           int render_frame_routing_id,
                           const GURL& url,
-                          SaveFileManager* manager);
+                          SaveFileManager* manager,
+                          AuthorizationState authorization_state);
   ~SaveFileResourceHandler() override;
 
   // ResourceHandler Implementation:
@@ -85,6 +98,8 @@
   int64_t content_length_;
   SaveFileManager* save_manager_;
 
+  AuthorizationState authorization_state_;
+
   static const int kReadBufSize = 32768;  // bytes
 
   DISALLOW_COPY_AND_ASSIGN(SaveFileResourceHandler);
diff --git a/content/browser/download/save_item.cc b/content/browser/download/save_item.cc
index 820a27e..b1e3a8a 100644
--- a/content/browser/download/save_item.cc
+++ b/content/browser/download/save_item.cc
@@ -28,11 +28,13 @@
                    const Referrer& referrer,
                    SavePackage* package,
                    SaveFileCreateInfo::SaveFileSource save_source,
-                   int frame_tree_node_id)
+                   int frame_tree_node_id,
+                   int container_frame_tree_node_id)
     : save_item_id_(GetNextSaveItemId()),
       url_(url),
       referrer_(referrer),
       frame_tree_node_id_(frame_tree_node_id),
+      container_frame_tree_node_id_(container_frame_tree_node_id),
       total_bytes_(0),
       received_bytes_(0),
       state_(WAIT_START),
diff --git a/content/browser/download/save_item.h b/content/browser/download/save_item.h
index 15b5495..049c7f39 100644
--- a/content/browser/download/save_item.h
+++ b/content/browser/download/save_item.h
@@ -31,7 +31,8 @@
            const Referrer& referrer,
            SavePackage* package,
            SaveFileCreateInfo::SaveFileSource save_source,
-           int frame_tree_node_id);
+           int frame_tree_node_id,
+           int container_frame_tree_node_id);
 
   ~SaveItem();
 
@@ -58,6 +59,9 @@
   const GURL& url() const { return url_; }
   const Referrer& referrer() const { return referrer_; }
   int frame_tree_node_id() const { return frame_tree_node_id_; }
+  int container_frame_tree_node_id() const {
+    return container_frame_tree_node_id_;
+  }
   int64_t received_bytes() const { return received_bytes_; }
   bool has_final_name() const { return !full_path_.empty(); }
   bool success() const { return is_success_; }
@@ -80,9 +84,14 @@
   Referrer referrer_;
 
   // Frame tree node id, if this save item represents a frame
-  // (otherwise FrameTreeNode::kFrameTreeNodeInvalidID).
+  // (otherwise FrameTreeNode::kFrameTreeNodeInvalidId).
   int frame_tree_node_id_;
 
+  // Frame tree node id of the frame containing this save item.
+  // (FrameTreeNode::kFrameTreeNodeInvalidId if this save item represents the
+  // main frame, which obviously doesn't have a containing/parent frame).
+  int container_frame_tree_node_id_;
+
   // Total bytes expected.
   int64_t total_bytes_;
 
diff --git a/content/browser/download/save_package.cc b/content/browser/download/save_package.cc
index 974c140..53b86b5 100644
--- a/content/browser/download/save_package.cc
+++ b/content/browser/download/save_package.cc
@@ -301,13 +301,11 @@
   } else {
     DCHECK_EQ(SAVE_PAGE_TYPE_AS_ONLY_HTML, save_type_);
     wait_state_ = NET_FILES;
-    SaveFileCreateInfo::SaveFileSource save_source = page_url_.SchemeIsFile() ?
-        SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
-        SaveFileCreateInfo::SAVE_FILE_FROM_NET;
     // Add this item to waiting list.
-    waiting_item_queue_.push_back(
-        new SaveItem(page_url_, Referrer(), this, save_source,
-                     FrameTreeNode::kFrameTreeNodeInvalidId));
+    waiting_item_queue_.push_back(new SaveItem(
+        page_url_, Referrer(), this, SaveFileCreateInfo::SAVE_FILE_FROM_NET,
+        FrameTreeNode::kFrameTreeNodeInvalidId,
+        web_contents()->GetMainFrame()->GetFrameTreeNodeId()));
     all_save_items_count_ = 1;
     download_->SetTotalBytes(1);
 
@@ -546,16 +544,6 @@
     save_item->SetTargetPath(saved_main_file_path_);
   }
 
-  // If the save source is from file system, inform SaveFileManager to copy
-  // corresponding file to the file path which this SaveItem specifies.
-  if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) {
-    BrowserThread::PostTask(
-        BrowserThread::FILE, FROM_HERE,
-        base::Bind(&SaveFileManager::SaveLocalFile, file_manager_,
-                   save_item->url(), save_item->id(), id()));
-    return;
-  }
-
   // Check whether we begin to require serialized HTML data.
   if (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML &&
       wait_state_ == HTML_DATA) {
@@ -798,11 +786,30 @@
     DCHECK(!ContainsKey(in_progress_items_, save_item->id()));
     in_progress_items_[save_item->id()] = save_item;
     save_item->Start();
+
+    // Find the frame responsible for making the network request below - it will
+    // be used in security checks made later by ResourceDispatcherHostImpl.
+    int requester_frame_tree_node_id =
+        save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_NET
+            ? save_item->container_frame_tree_node_id()
+            : save_item->frame_tree_node_id();
+    DCHECK_NE(FrameTreeNode::kFrameTreeNodeInvalidId,
+              requester_frame_tree_node_id);
+    FrameTreeNode* requester_frame_tree_node =
+        FrameTreeNode::GloballyFindByID(requester_frame_tree_node_id);
+    if (!requester_frame_tree_node) {
+      save_item->Finish(0, /* is_success = */ false);
+      continue;
+    }
+    RenderFrameHostImpl* requester_frame =
+        requester_frame_tree_node->current_frame_host();
+
     file_manager_->SaveURL(
         save_item->id(), save_item->url(), save_item->referrer(),
-        web_contents()->GetRenderProcessHost()->GetID(), routing_id(),
-        web_contents()->GetMainFrame()->GetRoutingID(),
-        save_item->save_source(), save_item->full_path(),
+        requester_frame->GetProcess()->GetID(),
+        requester_frame->render_view_host()->GetRoutingID(),
+        requester_frame->routing_id(), save_item->save_source(),
+        save_item->full_path(),
         web_contents()->GetBrowserContext()->GetResourceContext(), this);
   } while (process_all_remaining_items && !waiting_item_queue_.empty());
 }
@@ -1125,8 +1132,9 @@
     SaveFileCreateInfo::SaveFileSource save_source) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   Referrer sanitized_referrer = Referrer::SanitizeForRequest(url, referrer);
-  SaveItem* save_item = new SaveItem(url, sanitized_referrer, this, save_source,
-                                     save_item_frame_tree_node_id);
+  SaveItem* save_item =
+      new SaveItem(url, sanitized_referrer, this, save_source,
+                   save_item_frame_tree_node_id, container_frame_tree_node_id);
   waiting_item_queue_.push_back(save_item);
 
   frame_tree_node_id_to_contained_save_items_[container_frame_tree_node_id]
@@ -1169,12 +1177,9 @@
   if (!url.is_valid())
     return;
 
-  SaveFileCreateInfo::SaveFileSource save_source =
-      url.SchemeIsFile() ? SaveFileCreateInfo::SAVE_FILE_FROM_FILE
-                         : SaveFileCreateInfo::SAVE_FILE_FROM_NET;
   CreatePendingSaveItemDeduplicatingByUrl(
       container_frame_tree_node_id, FrameTreeNode::kFrameTreeNodeInvalidId, url,
-      referrer, save_source);
+      referrer, SaveFileCreateInfo::SAVE_FILE_FROM_NET);
 }
 
 void SavePackage::EnqueueFrame(int container_frame_tree_node_id,
diff --git a/content/browser/download/save_types.h b/content/browser/download/save_types.h
index c973269..e8076d2 100644
--- a/content/browser/download/save_types.h
+++ b/content/browser/download/save_types.h
@@ -39,9 +39,6 @@
     // This type indicates the save item needs to be retrieved from serializing
     // DOM.
     SAVE_FILE_FROM_DOM,
-    // This type indicates the save item needs to be retrieved from local file
-    // system.
-    SAVE_FILE_FROM_FILE
   };
 
   // Constructor for SAVE_FILE_FROM_DOM and/or SAVE_FILE_FROM_FILE.
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index d2d0bc1..bc591bd 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -1887,9 +1887,22 @@
                         render_frame_route_id, false, context);
   extra_info->AssociateWithRequest(request.get());  // Request takes ownership.
 
-  std::unique_ptr<ResourceHandler> handler(new SaveFileResourceHandler(
+  // Check if the renderer is permitted to request the requested URL.
+  using AuthorizationState = SaveFileResourceHandler::AuthorizationState;
+  AuthorizationState authorization_state = AuthorizationState::AUTHORIZED;
+  if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(child_id,
+                                                                    url)) {
+    DVLOG(1) << "Denying unauthorized save of " << url.possibly_invalid_spec();
+    authorization_state = AuthorizationState::NOT_AUTHORIZED;
+    // No need to return here (i.e. okay to begin processing the request below),
+    // because NOT_AUTHORIZED will cause the request to be cancelled.  See also
+    // doc comments for AuthorizationState enum.
+  }
+
+  std::unique_ptr<SaveFileResourceHandler> handler(new SaveFileResourceHandler(
       request.get(), save_item_id, save_package_id, child_id,
-      render_frame_route_id, url, save_file_manager_.get()));
+      render_frame_route_id, url, save_file_manager_.get(),
+      authorization_state));
 
   BeginRequestInternal(std::move(request), std::move(handler));
 }