| // Copyright 2014 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/frame_host/navigation_request.h" | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "content/browser/devtools/render_frame_devtools_agent_host.h" | 
 | #include "content/browser/frame_host/frame_tree.h" | 
 | #include "content/browser/frame_host/frame_tree_node.h" | 
 | #include "content/browser/frame_host/navigation_controller_impl.h" | 
 | #include "content/browser/frame_host/navigation_handle_impl.h" | 
 | #include "content/browser/frame_host/navigation_request_info.h" | 
 | #include "content/browser/frame_host/navigator.h" | 
 | #include "content/browser/frame_host/navigator_impl.h" | 
 | #include "content/browser/loader/navigation_url_loader.h" | 
 | #include "content/browser/service_worker/service_worker_context_wrapper.h" | 
 | #include "content/browser/site_instance_impl.h" | 
 | #include "content/common/resource_request_body_impl.h" | 
 | #include "content/public/browser/browser_context.h" | 
 | #include "content/public/browser/navigation_controller.h" | 
 | #include "content/public/browser/navigation_data.h" | 
 | #include "content/public/browser/storage_partition.h" | 
 | #include "content/public/browser/stream_handle.h" | 
 | #include "content/public/common/content_client.h" | 
 | #include "content/public/common/request_context_type.h" | 
 | #include "content/public/common/resource_response.h" | 
 | #include "net/base/load_flags.h" | 
 | #include "net/http/http_request_headers.h" | 
 | #include "net/url_request/redirect_info.h" | 
 | #include "third_party/WebKit/public/web/WebSandboxFlags.h" | 
 |  | 
 | namespace content { | 
 |  | 
 | namespace { | 
 |  | 
 | // Returns the net load flags to use based on the navigation type. | 
 | // TODO(clamy): unify the code with what is happening on the renderer side. | 
 | int LoadFlagFromNavigationType(FrameMsg_Navigate_Type::Value navigation_type) { | 
 |   int load_flags = net::LOAD_NORMAL; | 
 |   switch (navigation_type) { | 
 |     case FrameMsg_Navigate_Type::RELOAD: | 
 |     case FrameMsg_Navigate_Type::RELOAD_MAIN_RESOURCE: | 
 |     case FrameMsg_Navigate_Type::RELOAD_ORIGINAL_REQUEST_URL: | 
 |       load_flags |= net::LOAD_VALIDATE_CACHE; | 
 |       break; | 
 |     case FrameMsg_Navigate_Type::RELOAD_BYPASSING_CACHE: | 
 |       load_flags |= net::LOAD_BYPASS_CACHE; | 
 |       break; | 
 |     case FrameMsg_Navigate_Type::RESTORE: | 
 |       load_flags |= net::LOAD_PREFERRING_CACHE; | 
 |       break; | 
 |     case FrameMsg_Navigate_Type::RESTORE_WITH_POST: | 
 |       load_flags |= net::LOAD_ONLY_FROM_CACHE; | 
 |       break; | 
 |     case FrameMsg_Navigate_Type::NORMAL: | 
 |     default: | 
 |       break; | 
 |   } | 
 |   return load_flags; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | std::unique_ptr<NavigationRequest> NavigationRequest::CreateBrowserInitiated( | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const GURL& dest_url, | 
 |     const Referrer& dest_referrer, | 
 |     const FrameNavigationEntry& frame_entry, | 
 |     const NavigationEntryImpl& entry, | 
 |     FrameMsg_Navigate_Type::Value navigation_type, | 
 |     LoFiState lofi_state, | 
 |     bool is_same_document_history_load, | 
 |     bool is_history_navigation_in_new_child, | 
 |     const base::TimeTicks& navigation_start, | 
 |     NavigationControllerImpl* controller) { | 
 |   // Copy existing headers and add necessary headers that may not be present | 
 |   // in the RequestNavigationParams. | 
 |   net::HttpRequestHeaders headers; | 
 |   headers.AddHeadersFromString(entry.extra_headers()); | 
 |   headers.SetHeaderIfMissing(net::HttpRequestHeaders::kUserAgent, | 
 |                              GetContentClient()->GetUserAgent()); | 
 |  | 
 |   // Fill POST data in the request body. | 
 |   scoped_refptr<ResourceRequestBodyImpl> request_body; | 
 |   if (frame_entry.method() == "POST") | 
 |     request_body = frame_entry.GetPostData(); | 
 |  | 
 |   std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest( | 
 |       frame_tree_node, entry.ConstructCommonNavigationParams( | 
 |                            frame_entry, request_body, dest_url, dest_referrer, | 
 |                            navigation_type, lofi_state, navigation_start), | 
 |       BeginNavigationParams(headers.ToString(), | 
 |                             LoadFlagFromNavigationType(navigation_type), | 
 |                             false,  // has_user_gestures | 
 |                             false,  // skip_service_worker | 
 |                             REQUEST_CONTEXT_TYPE_LOCATION), | 
 |       entry.ConstructRequestNavigationParams( | 
 |           frame_entry, is_same_document_history_load, | 
 |           is_history_navigation_in_new_child, | 
 |           frame_tree_node->has_committed_real_load(), | 
 |           controller->GetPendingEntryIndex() == -1, | 
 |           controller->GetIndexOfEntry(&entry), | 
 |           controller->GetLastCommittedEntryIndex(), | 
 |           controller->GetEntryCount()), | 
 |       true, &frame_entry, &entry)); | 
 |   return navigation_request; | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated( | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const CommonNavigationParams& common_params, | 
 |     const BeginNavigationParams& begin_params, | 
 |     int current_history_list_offset, | 
 |     int current_history_list_length) { | 
 |   // TODO(clamy): Check if some PageState should be provided here. | 
 |   // TODO(clamy): See how we should handle override of the user agent when the | 
 |   // navigation may start in a renderer and commit in another one. | 
 |   // TODO(clamy): See if the navigation start time should be measured in the | 
 |   // renderer and sent to the browser instead of being measured here. | 
 |   // TODO(clamy): The pending history list offset should be properly set. | 
 |   RequestNavigationParams request_params( | 
 |       false,                   // is_overriding_user_agent | 
 |       std::vector<GURL>(),     // redirects | 
 |       false,                   // can_load_local_resources | 
 |       base::Time::Now(),       // request_time | 
 |       PageState(),             // page_state | 
 |       -1,                      // page_id | 
 |       0,                       // nav_entry_id | 
 |       false,                   // is_same_document_history_load | 
 |       false,                   // is_history_navigation_in_new_child | 
 |       frame_tree_node->has_committed_real_load(), | 
 |       false,                   // intended_as_new_entry | 
 |       -1,                      // pending_history_list_offset | 
 |       current_history_list_offset, current_history_list_length, | 
 |       false,                   // is_view_source | 
 |       false);                  // should_clear_history_list | 
 |   std::unique_ptr<NavigationRequest> navigation_request( | 
 |       new NavigationRequest(frame_tree_node, common_params, begin_params, | 
 |                             request_params, false, nullptr, nullptr)); | 
 |   return navigation_request; | 
 | } | 
 |  | 
 | NavigationRequest::NavigationRequest( | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const CommonNavigationParams& common_params, | 
 |     const BeginNavigationParams& begin_params, | 
 |     const RequestNavigationParams& request_params, | 
 |     bool browser_initiated, | 
 |     const FrameNavigationEntry* frame_entry, | 
 |     const NavigationEntryImpl* entry) | 
 |     : frame_tree_node_(frame_tree_node), | 
 |       common_params_(common_params), | 
 |       begin_params_(begin_params), | 
 |       request_params_(request_params), | 
 |       browser_initiated_(browser_initiated), | 
 |       state_(NOT_STARTED), | 
 |       restore_type_(RestoreType::NONE), | 
 |       is_view_source_(false), | 
 |       bindings_(NavigationEntryImpl::kInvalidBindings), | 
 |       associated_site_instance_type_(AssociatedSiteInstanceType::NONE) { | 
 |   DCHECK(!browser_initiated || (entry != nullptr && frame_entry != nullptr)); | 
 |   if (browser_initiated) { | 
 |     FrameNavigationEntry* frame_entry = entry->GetFrameEntry(frame_tree_node); | 
 |     if (frame_entry) { | 
 |       source_site_instance_ = frame_entry->source_site_instance(); | 
 |       dest_site_instance_ = frame_entry->site_instance(); | 
 |     } | 
 |  | 
 |     restore_type_ = entry->restore_type(); | 
 |     is_view_source_ = entry->IsViewSourceMode(); | 
 |     bindings_ = entry->bindings(); | 
 |   } else { | 
 |     // This is needed to have about:blank and data URLs commit in the same | 
 |     // SiteInstance as the initiating renderer. | 
 |     source_site_instance_ = | 
 |         frame_tree_node->current_frame_host()->GetSiteInstance(); | 
 |   } | 
 |  | 
 |   // TODO(mkwst): This is incorrect. It ought to use the definition from | 
 |   // 'Document::firstPartyForCookies()' in Blink, which walks the ancestor tree | 
 |   // and verifies that all origins are PSL-matches (and special-cases extension | 
 |   // URLs). | 
 |   const GURL& first_party_for_cookies = | 
 |       frame_tree_node->IsMainFrame() | 
 |           ? common_params.url | 
 |           : frame_tree_node->frame_tree()->root()->current_url(); | 
 |   bool parent_is_main_frame = !frame_tree_node->parent() ? | 
 |       false : frame_tree_node->parent()->IsMainFrame(); | 
 |   info_.reset(new NavigationRequestInfo( | 
 |       common_params, begin_params, first_party_for_cookies, | 
 |       frame_tree_node->current_origin(), frame_tree_node->IsMainFrame(), | 
 |       parent_is_main_frame, frame_tree_node->frame_tree_node_id())); | 
 | } | 
 |  | 
 | NavigationRequest::~NavigationRequest() { | 
 | } | 
 |  | 
 | void NavigationRequest::BeginNavigation() { | 
 |   DCHECK(!loader_); | 
 |   DCHECK(state_ == NOT_STARTED || state_ == WAITING_FOR_RENDERER_RESPONSE); | 
 |   state_ = STARTED; | 
 |   RenderFrameDevToolsAgentHost::OnBeforeNavigation(navigation_handle_.get()); | 
 |  | 
 |   if (ShouldMakeNetworkRequestForURL(common_params_.url)) { | 
 |     // It's safe to use base::Unretained because this NavigationRequest owns | 
 |     // the NavigationHandle where the callback will be stored. | 
 |     // TODO(clamy): pass the real value for |is_external_protocol| if needed. | 
 |     // TODO(clamy): pass the method to the NavigationHandle instead of a | 
 |     // boolean. | 
 |     navigation_handle_->WillStartRequest( | 
 |         common_params_.method, common_params_.post_data, | 
 |         Referrer::SanitizeForRequest(common_params_.url, | 
 |                                      common_params_.referrer), | 
 |         begin_params_.has_user_gesture, common_params_.transition, false, | 
 |         begin_params_.request_context_type, | 
 |         base::Bind(&NavigationRequest::OnStartChecksComplete, | 
 |                    base::Unretained(this))); | 
 |     return; | 
 |   } | 
 |  | 
 |   // There is no need to make a network request for this navigation, so commit | 
 |   // it immediately. | 
 |   state_ = RESPONSE_STARTED; | 
 |  | 
 |   // Select an appropriate RenderFrameHost. | 
 |   RenderFrameHostImpl* render_frame_host = | 
 |       frame_tree_node_->render_manager()->GetFrameHostForNavigation(*this); | 
 |   NavigatorImpl::CheckWebUIRendererDoesNotDisplayNormalURL(render_frame_host, | 
 |                                                            common_params_.url); | 
 |  | 
 |   // Inform the NavigationHandle that the navigation will commit. | 
 |   navigation_handle_->ReadyToCommitNavigation(render_frame_host); | 
 |  | 
 |   CommitNavigation(); | 
 | } | 
 |  | 
 | void NavigationRequest::CreateNavigationHandle(int pending_nav_entry_id) { | 
 |   // TODO(nasko): Update the NavigationHandle creation to ensure that the | 
 |   // proper values are specified for is_synchronous and is_srcdoc. | 
 |   navigation_handle_ = NavigationHandleImpl::Create( | 
 |       common_params_.url, frame_tree_node_, | 
 |       !browser_initiated_, | 
 |       false,  // is_synchronous | 
 |       false,  // is_srcdoc | 
 |       common_params_.navigation_start, pending_nav_entry_id); | 
 | } | 
 |  | 
 | void NavigationRequest::TransferNavigationHandleOwnership( | 
 |     RenderFrameHostImpl* render_frame_host) { | 
 |   render_frame_host->SetNavigationHandle(std::move(navigation_handle_)); | 
 | } | 
 |  | 
 | void NavigationRequest::OnRequestRedirected( | 
 |     const net::RedirectInfo& redirect_info, | 
 |     const scoped_refptr<ResourceResponse>& response) { | 
 |   // If the navigation is no longer a POST, the POST data should be reset. | 
 |   if (redirect_info.new_method != "POST") | 
 |     common_params_.post_data = nullptr; | 
 |  | 
 |   // Mark time for the Navigation Timing API. | 
 |   if (request_params_.navigation_timing.redirect_start.is_null()) { | 
 |     request_params_.navigation_timing.redirect_start = | 
 |         request_params_.navigation_timing.fetch_start; | 
 |   } | 
 |   request_params_.navigation_timing.redirect_end = base::TimeTicks::Now(); | 
 |   request_params_.navigation_timing.fetch_start = base::TimeTicks::Now(); | 
 |  | 
 |   request_params_.redirects.push_back(common_params_.url); | 
 |   common_params_.url = redirect_info.new_url; | 
 |   common_params_.method = redirect_info.new_method; | 
 |   common_params_.referrer.url = GURL(redirect_info.new_referrer); | 
 |  | 
 |   // TODO(clamy): Have CSP + security upgrade checks here. | 
 |   // TODO(clamy): Kill the renderer if FilterURL fails? | 
 |  | 
 |   // It's safe to use base::Unretained because this NavigationRequest owns the | 
 |   // NavigationHandle where the callback will be stored. | 
 |   // TODO(clamy): pass the real value for |is_external_protocol| if needed. | 
 |   navigation_handle_->WillRedirectRequest( | 
 |       common_params_.url, common_params_.method, common_params_.referrer.url, | 
 |       false, response->head.headers, | 
 |       base::Bind(&NavigationRequest::OnRedirectChecksComplete, | 
 |                  base::Unretained(this))); | 
 | } | 
 |  | 
 | void NavigationRequest::OnResponseStarted( | 
 |     const scoped_refptr<ResourceResponse>& response, | 
 |     std::unique_ptr<StreamHandle> body, | 
 |     const SSLStatus& ssl_status, | 
 |     std::unique_ptr<NavigationData> navigation_data) { | 
 |   DCHECK(state_ == STARTED); | 
 |   state_ = RESPONSE_STARTED; | 
 |  | 
 |   // HTTP 204 (No Content) and HTTP 205 (Reset Content) responses should not | 
 |   // commit; they leave the frame showing the previous page. | 
 |   DCHECK(response); | 
 |   if (response->head.headers.get() && | 
 |       (response->head.headers->response_code() == 204 || | 
 |        response->head.headers->response_code() == 205)) { | 
 |     frame_tree_node_->ResetNavigationRequest(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Update the lofi state of the request. | 
 |   if (response->head.is_using_lofi) | 
 |     common_params_.lofi_state = LOFI_ON; | 
 |   else | 
 |     common_params_.lofi_state = LOFI_OFF; | 
 |  | 
 |   // Select an appropriate renderer to commit the navigation. | 
 |   RenderFrameHostImpl* render_frame_host = | 
 |       frame_tree_node_->render_manager()->GetFrameHostForNavigation(*this); | 
 |   NavigatorImpl::CheckWebUIRendererDoesNotDisplayNormalURL(render_frame_host, | 
 |                                                            common_params_.url); | 
 |  | 
 |   // For renderer-initiated navigations that are set to commit in a different | 
 |   // renderer, allow the embedder to cancel the transfer. | 
 |   if (!browser_initiated_ && | 
 |       render_frame_host != frame_tree_node_->current_frame_host() && | 
 |       !frame_tree_node_->navigator() | 
 |            ->GetDelegate() | 
 |            ->ShouldTransferNavigation()) { | 
 |     frame_tree_node_->ResetNavigationRequest(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (navigation_data) | 
 |     navigation_handle_->set_navigation_data(std::move(navigation_data)); | 
 |  | 
 |   // Store the response and the StreamHandle until checks have been processed. | 
 |   response_ = response; | 
 |   body_ = std::move(body); | 
 |  | 
 |   // Check if the navigation should be allowed to proceed. | 
 |   navigation_handle_->WillProcessResponse( | 
 |       render_frame_host, response->head.headers.get(), ssl_status, | 
 |       base::Bind(&NavigationRequest::OnWillProcessResponseChecksComplete, | 
 |                  base::Unretained(this))); | 
 | } | 
 |  | 
 | void NavigationRequest::OnRequestFailed(bool has_stale_copy_in_cache, | 
 |                                         int net_error) { | 
 |   DCHECK(state_ == STARTED); | 
 |   state_ = FAILED; | 
 |   navigation_handle_->set_net_error_code(static_cast<net::Error>(net_error)); | 
 |   frame_tree_node_->navigator()->FailedNavigation( | 
 |       frame_tree_node_, has_stale_copy_in_cache, net_error); | 
 | } | 
 |  | 
 | void NavigationRequest::OnRequestStarted(base::TimeTicks timestamp) { | 
 |   if (frame_tree_node_->IsMainFrame()) { | 
 |     TRACE_EVENT_ASYNC_END_WITH_TIMESTAMP0( | 
 |         "navigation", "Navigation timeToNetworkStack", navigation_handle_.get(), | 
 |         timestamp.ToInternalValue()); | 
 |   } | 
 |  | 
 |   frame_tree_node_->navigator()->LogResourceRequestTime(timestamp, | 
 |                                                         common_params_.url); | 
 | } | 
 |  | 
 | void NavigationRequest::OnServiceWorkerEncountered() { | 
 |   request_params_.should_create_service_worker = true; | 
 |  | 
 |   // TODO(clamy): the navigation should be sent to a RenderFrameHost to be | 
 |   // picked up by the ServiceWorker. | 
 |   NOTIMPLEMENTED(); | 
 | } | 
 |  | 
 | void NavigationRequest::OnStartChecksComplete( | 
 |     NavigationThrottle::ThrottleCheckResult result) { | 
 |   CHECK(result != NavigationThrottle::DEFER); | 
 |  | 
 |   // Abort the request if needed. This will destroy the NavigationRequest. | 
 |   if (result == NavigationThrottle::CANCEL_AND_IGNORE || | 
 |       result == NavigationThrottle::CANCEL) { | 
 |     // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE. | 
 |     frame_tree_node_->ResetNavigationRequest(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Use the SiteInstance of the navigating RenderFrameHost to get access to | 
 |   // the StoragePartition. Using the url of the navigation will result in a | 
 |   // wrong StoragePartition being picked when a WebView is navigating. | 
 |   DCHECK_NE(AssociatedSiteInstanceType::NONE, associated_site_instance_type_); | 
 |   RenderFrameHostImpl* navigating_frame_host = | 
 |       associated_site_instance_type_ == AssociatedSiteInstanceType::SPECULATIVE | 
 |           ? frame_tree_node_->render_manager()->speculative_frame_host() | 
 |           : frame_tree_node_->current_frame_host(); | 
 |   DCHECK(navigating_frame_host); | 
 |  | 
 |   BrowserContext* browser_context = | 
 |       frame_tree_node_->navigator()->GetController()->GetBrowserContext(); | 
 |   StoragePartition* partition = BrowserContext::GetStoragePartition( | 
 |       browser_context, navigating_frame_host->GetSiteInstance()); | 
 |   DCHECK(partition); | 
 |  | 
 |   ServiceWorkerContextWrapper* service_worker_context = | 
 |       static_cast<ServiceWorkerContextWrapper*>( | 
 |           partition->GetServiceWorkerContext()); | 
 |  | 
 |   // Mark the fetch_start (Navigation Timing API). | 
 |   request_params_.navigation_timing.fetch_start = base::TimeTicks::Now(); | 
 |  | 
 |   loader_ = NavigationURLLoader::Create( | 
 |       frame_tree_node_->navigator()->GetController()->GetBrowserContext(), | 
 |       std::move(info_), service_worker_context, this); | 
 | } | 
 |  | 
 | void NavigationRequest::OnRedirectChecksComplete( | 
 |     NavigationThrottle::ThrottleCheckResult result) { | 
 |   CHECK(result != NavigationThrottle::DEFER); | 
 |  | 
 |   // Abort the request if needed. This will destroy the NavigationRequest. | 
 |   if (result == NavigationThrottle::CANCEL_AND_IGNORE || | 
 |       result == NavigationThrottle::CANCEL) { | 
 |     // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE. | 
 |     frame_tree_node_->ResetNavigationRequest(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   loader_->FollowRedirect(); | 
 | } | 
 |  | 
 | void NavigationRequest::OnWillProcessResponseChecksComplete( | 
 |     NavigationThrottle::ThrottleCheckResult result) { | 
 |   CHECK(result != NavigationThrottle::DEFER); | 
 |  | 
 |   // Abort the request if needed. This will destroy the NavigationRequest. | 
 |   if (result == NavigationThrottle::CANCEL_AND_IGNORE || | 
 |       result == NavigationThrottle::CANCEL) { | 
 |     // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE. | 
 |     frame_tree_node_->ResetNavigationRequest(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Have the processing of the response resume in the network stack. | 
 |   loader_->ProceedWithResponse(); | 
 |  | 
 |   CommitNavigation(); | 
 |  | 
 |   // DO NOT ADD CODE after this. The previous call to CommitNavigation caused | 
 |   // the destruction of the NavigationRequest. | 
 | } | 
 |  | 
 | void NavigationRequest::CommitNavigation() { | 
 |   DCHECK(response_ || !ShouldMakeNetworkRequestForURL(common_params_.url)); | 
 |   DCHECK(!common_params_.url.SchemeIs(url::kJavaScriptScheme)); | 
 |  | 
 |   // Retrieve the RenderFrameHost that needs to commit the navigation. | 
 |   RenderFrameHostImpl* render_frame_host = | 
 |       navigation_handle_->GetRenderFrameHost(); | 
 |   DCHECK(render_frame_host == | 
 |              frame_tree_node_->render_manager()->current_frame_host() || | 
 |          render_frame_host == | 
 |              frame_tree_node_->render_manager()->speculative_frame_host()); | 
 |  | 
 |   TransferNavigationHandleOwnership(render_frame_host); | 
 |   render_frame_host->CommitNavigation(response_.get(), std::move(body_), | 
 |                                       common_params_, request_params_, | 
 |                                       is_view_source_); | 
 |  | 
 |   frame_tree_node_->ResetNavigationRequest(true); | 
 | } | 
 |  | 
 | }  // namespace content |