| // 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 "content/browser/download/download_resource_handler.h" |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/post_task.h" |
| #include "components/download/public/common/download_create_info.h" |
| #include "components/download/public/common/download_interrupt_reasons.h" |
| #include "components/download/public/common/download_interrupt_reasons_utils.h" |
| #include "components/download/public/common/download_task_runner.h" |
| #include "components/download/public/common/download_ukm_helper.h" |
| #include "content/browser/byte_stream.h" |
| #include "content/browser/download/byte_stream_input_stream.h" |
| #include "content/browser/download/download_manager_impl.h" |
| #include "content/browser/download/download_request_handle.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/browser/loader/resource_controller.h" |
| #include "content/browser/loader/resource_dispatcher_host_impl.h" |
| #include "content/browser/loader/resource_request_info_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/download_request_utils.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "services/network/public/cpp/resource_response.h" |
| |
| namespace content { |
| |
| struct DownloadResourceHandler::DownloadTabInfo { |
| GURL tab_url; |
| GURL tab_referrer_url; |
| ukm::SourceId ukm_source_id; |
| }; |
| |
| namespace { |
| |
| // Static function in order to prevent any accidental accesses to |
| // DownloadResourceHandler members from the UI thread. |
| static void StartOnUIThread( |
| std::unique_ptr<download::DownloadCreateInfo> info, |
| std::unique_ptr<DownloadResourceHandler::DownloadTabInfo> tab_info, |
| std::unique_ptr<ByteStreamReader> stream, |
| int render_process_id, |
| int render_frame_id, |
| int frame_tree_node_id, |
| const download::DownloadUrlParameters::OnStartedCallback& started_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHost* frame_host = |
| RenderFrameHost::FromID(render_process_id, render_frame_id); |
| |
| // Navigations don't have associated RenderFrameHosts. Get the SiteInstance |
| // from the FrameTreeNode. |
| if (!frame_host) { |
| FrameTreeNode* frame_tree_node = |
| FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
| if (frame_tree_node) |
| frame_host = frame_tree_node->current_frame_host(); |
| } |
| |
| DownloadManager* download_manager = nullptr; |
| if (frame_host) { |
| download_manager = BrowserContext::GetDownloadManager( |
| frame_host->GetProcess()->GetBrowserContext()); |
| } |
| |
| if (!download_manager || !frame_host) { |
| // NULL in unittests or if the page closed right after starting the |
| // download. |
| if (!started_cb.is_null()) |
| started_cb.Run(nullptr, |
| download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); |
| |
| if (stream) |
| download::GetDownloadTaskRunner()->DeleteSoon(FROM_HERE, |
| stream.release()); |
| return; |
| } |
| |
| info->tab_url = tab_info->tab_url; |
| info->tab_referrer_url = tab_info->tab_referrer_url; |
| info->ukm_source_id = tab_info->ukm_source_id; |
| info->site_url = frame_host->GetSiteInstance()->GetSiteURL(); |
| info->render_process_id = frame_host->GetProcess()->GetID(); |
| info->render_frame_id = frame_host->GetRoutingID(); |
| |
| download_manager->StartDownload( |
| std::move(info), |
| std::make_unique<ByteStreamInputStream>(std::move(stream)), nullptr, |
| started_cb); |
| } |
| |
| void InitializeDownloadTabInfoOnUIThread( |
| const DownloadRequestHandle& request_handle, |
| DownloadResourceHandler::DownloadTabInfo* tab_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| WebContents* web_contents = request_handle.GetWebContents(); |
| if (web_contents) { |
| NavigationEntry* entry = web_contents->GetController().GetVisibleEntry(); |
| if (entry) { |
| tab_info->tab_url = entry->GetURL(); |
| tab_info->tab_referrer_url = entry->GetReferrer().url; |
| |
| tab_info->ukm_source_id = static_cast<WebContentsImpl*>(web_contents) |
| ->GetUkmSourceIdForLastCommittedSource(); |
| } |
| } |
| } |
| |
| void DeleteOnUIThread( |
| std::unique_ptr<DownloadResourceHandler::DownloadTabInfo> tab_info) {} |
| |
| void NavigateOnUIThread( |
| const GURL& url, |
| const std::vector<GURL> url_chain, |
| const Referrer& referrer, |
| bool has_user_gesture, |
| const ResourceRequestInfo::WebContentsGetter& wc_getter) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| WebContents* web_contents = wc_getter.Run(); |
| if (web_contents) { |
| NavigationController::LoadURLParams params(url); |
| params.has_user_gesture = has_user_gesture; |
| params.referrer = referrer; |
| params.redirect_chain = url_chain; |
| web_contents->GetController().LoadURLWithParams(params); |
| } |
| } |
| |
| } // namespace |
| |
| DownloadResourceHandler::DownloadResourceHandler( |
| net::URLRequest* request, |
| const std::string& request_origin, |
| download::DownloadSource download_source, |
| bool follow_cross_origin_redirects) |
| : ResourceHandler(request), |
| tab_info_(new DownloadTabInfo()), |
| follow_cross_origin_redirects_(follow_cross_origin_redirects), |
| first_origin_(url::Origin::Create(request->url())), |
| core_(request, this, false, request_origin, download_source) { |
| // Do UI thread initialization for tab_info_ asap after |
| // DownloadResourceHandler creation since the tab could be navigated |
| // before StartOnUIThread gets called. This is safe because deletion |
| // will occur via PostTask() as well, which will serialized behind this |
| // PostTask() |
| const ResourceRequestInfoImpl* request_info = GetRequestInfo(); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce( |
| &InitializeDownloadTabInfoOnUIThread, |
| DownloadRequestHandle(AsWeakPtr(), |
| request_info->GetWebContentsGetterForRequest()), |
| tab_info_.get())); |
| } |
| |
| DownloadResourceHandler::~DownloadResourceHandler() { |
| if (tab_info_) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&DeleteOnUIThread, std::move(tab_info_))); |
| } |
| } |
| |
| // static |
| std::unique_ptr<ResourceHandler> DownloadResourceHandler::Create( |
| net::URLRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::unique_ptr<ResourceHandler> handler(new DownloadResourceHandler( |
| request, std::string(), download::DownloadSource::NAVIGATION, true)); |
| return handler; |
| } |
| |
| // static |
| std::unique_ptr<ResourceHandler> DownloadResourceHandler::CreateForNewRequest( |
| net::URLRequest* request, |
| const std::string& request_origin, |
| download::DownloadSource download_source, |
| bool follow_cross_origin_redirects) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::unique_ptr<ResourceHandler> handler(new DownloadResourceHandler( |
| request, request_origin, download_source, follow_cross_origin_redirects)); |
| return handler; |
| } |
| |
| void DownloadResourceHandler::OnRequestRedirected( |
| const net::RedirectInfo& redirect_info, |
| network::ResourceResponse* response, |
| std::unique_ptr<ResourceController> controller) { |
| url::Origin new_origin(url::Origin::Create(redirect_info.new_url)); |
| if (!follow_cross_origin_redirects_ && |
| !first_origin_.IsSameOriginWith(new_origin)) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce( |
| &NavigateOnUIThread, redirect_info.new_url, request()->url_chain(), |
| Referrer(GURL(redirect_info.new_referrer), |
| Referrer::NetReferrerPolicyToBlinkReferrerPolicy( |
| redirect_info.new_referrer_policy)), |
| GetRequestInfo()->HasUserGesture(), |
| GetRequestInfo()->GetWebContentsGetterForRequest())); |
| controller->Cancel(); |
| return; |
| } |
| if (core_.OnRequestRedirected()) { |
| controller->Resume(); |
| } else { |
| controller->Cancel(); |
| } |
| } |
| |
| // Send the download creation information to the download thread. |
| void DownloadResourceHandler::OnResponseStarted( |
| network::ResourceResponse* response, |
| std::unique_ptr<ResourceController> controller) { |
| // The MIME type in ResourceResponse is the product of |
| // MimeTypeResourceHandler. |
| if (core_.OnResponseStarted(response->head.mime_type)) { |
| controller->Resume(); |
| } else { |
| controller->Cancel(); |
| } |
| } |
| |
| void DownloadResourceHandler::OnWillStart( |
| const GURL& url, |
| std::unique_ptr<ResourceController> controller) { |
| controller->Resume(); |
| } |
| |
| // Create a new buffer, which will be handed to the download thread for file |
| // writing and deletion. |
| void DownloadResourceHandler::OnWillRead( |
| scoped_refptr<net::IOBuffer>* buf, |
| int* buf_size, |
| std::unique_ptr<ResourceController> controller) { |
| if (!core_.OnWillRead(buf, buf_size)) { |
| controller->Cancel(); |
| return; |
| } |
| |
| controller->Resume(); |
| } |
| |
| // Pass the buffer to the download file writer. |
| void DownloadResourceHandler::OnReadCompleted( |
| int bytes_read, |
| std::unique_ptr<ResourceController> controller) { |
| DCHECK(!has_controller()); |
| |
| bool defer = false; |
| if (!core_.OnReadCompleted(bytes_read, &defer)) { |
| controller->Cancel(); |
| return; |
| } |
| |
| if (defer) { |
| HoldController(std::move(controller)); |
| } else { |
| controller->Resume(); |
| } |
| } |
| |
| void DownloadResourceHandler::OnResponseCompleted( |
| const net::URLRequestStatus& status, |
| std::unique_ptr<ResourceController> controller) { |
| core_.OnResponseCompleted(status); |
| controller->Resume(); |
| } |
| |
| void DownloadResourceHandler::PauseRequest() { |
| core_.PauseRequest(); |
| } |
| |
| void DownloadResourceHandler::ResumeRequest() { |
| core_.ResumeRequest(); |
| } |
| |
| void DownloadResourceHandler::OnStart( |
| std::unique_ptr<download::DownloadCreateInfo> create_info, |
| std::unique_ptr<ByteStreamReader> stream_reader, |
| const download::DownloadUrlParameters::OnStartedCallback& callback) { |
| // If the user cancels the download, then don't call start. Instead ignore the |
| // download entirely. |
| if (create_info->result == |
| download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED && |
| create_info->is_new_download) { |
| if (!callback.is_null()) |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(callback, nullptr, create_info->result)); |
| return; |
| } |
| |
| const ResourceRequestInfoImpl* request_info = GetRequestInfo(); |
| create_info->has_user_gesture = request_info->HasUserGesture(); |
| create_info->transition_type = request_info->GetPageTransition(); |
| |
| create_info->request_handle.reset(new DownloadRequestHandle( |
| AsWeakPtr(), request_info->GetWebContentsGetterForRequest())); |
| |
| int render_process_id = -1; |
| int render_frame_id = -1; |
| request_info->GetAssociatedRenderFrame(&render_process_id, &render_frame_id); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&StartOnUIThread, std::move(create_info), |
| std::move(tab_info_), std::move(stream_reader), |
| render_process_id, render_frame_id, |
| request_info->frame_tree_node_id(), callback)); |
| } |
| |
| void DownloadResourceHandler::OnReadyToRead() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| Resume(); |
| } |
| |
| void DownloadResourceHandler::CancelRequest() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const ResourceRequestInfoImpl* info = GetRequestInfo(); |
| ResourceDispatcherHostImpl::Get()->CancelRequest( |
| info->GetChildID(), |
| info->GetRequestID()); |
| // This object has been deleted. |
| } |
| |
| std::string DownloadResourceHandler::DebugString() const { |
| const ResourceRequestInfoImpl* info = GetRequestInfo(); |
| return base::StringPrintf("{" |
| " url_ = " "\"%s\"" |
| " info = {" |
| " child_id = " "%d" |
| " request_id = " "%d" |
| " route_id = " "%d" |
| " }" |
| " }", |
| request() ? |
| request()->url().spec().c_str() : |
| "<NULL request>", |
| info->GetChildID(), |
| info->GetRequestID(), |
| info->GetRouteID()); |
| } |
| |
| } // namespace content |