• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "net/http/http_stream_factory_impl.h"
6 
7 #include <string>
8 
9 #include "base/logging.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "net/base/net_log.h"
13 #include "net/base/net_util.h"
14 #include "net/http/http_network_session.h"
15 #include "net/http/http_server_properties.h"
16 #include "net/http/http_stream_factory_impl_job.h"
17 #include "net/http/http_stream_factory_impl_request.h"
18 #include "net/spdy/spdy_http_stream.h"
19 #include "url/gurl.h"
20 
21 namespace net {
22 
23 namespace {
24 
25 const PortAlternateProtocolPair kNoAlternateProtocol = {
26   0,  UNINITIALIZED_ALTERNATE_PROTOCOL
27 };
28 
UpgradeUrlToHttps(const GURL & original_url,int port)29 GURL UpgradeUrlToHttps(const GURL& original_url, int port) {
30   GURL::Replacements replacements;
31   // new_sheme and new_port need to be in scope here because GURL::Replacements
32   // references the memory contained by them directly.
33   const std::string new_scheme = "https";
34   const std::string new_port = base::IntToString(port);
35   replacements.SetSchemeStr(new_scheme);
36   replacements.SetPortStr(new_port);
37   return original_url.ReplaceComponents(replacements);
38 }
39 
40 }  // namespace
41 
HttpStreamFactoryImpl(HttpNetworkSession * session,bool for_websockets)42 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session,
43                                              bool for_websockets)
44     : session_(session),
45       for_websockets_(for_websockets) {}
46 
~HttpStreamFactoryImpl()47 HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
48   DCHECK(request_map_.empty());
49   DCHECK(spdy_session_request_map_.empty());
50 
51   std::set<const Job*> tmp_job_set;
52   tmp_job_set.swap(orphaned_job_set_);
53   STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
54   DCHECK(orphaned_job_set_.empty());
55 
56   tmp_job_set.clear();
57   tmp_job_set.swap(preconnect_job_set_);
58   STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
59   DCHECK(preconnect_job_set_.empty());
60 }
61 
RequestStream(const HttpRequestInfo & request_info,RequestPriority priority,const SSLConfig & server_ssl_config,const SSLConfig & proxy_ssl_config,HttpStreamRequest::Delegate * delegate,const BoundNetLog & net_log)62 HttpStreamRequest* HttpStreamFactoryImpl::RequestStream(
63     const HttpRequestInfo& request_info,
64     RequestPriority priority,
65     const SSLConfig& server_ssl_config,
66     const SSLConfig& proxy_ssl_config,
67     HttpStreamRequest::Delegate* delegate,
68     const BoundNetLog& net_log) {
69   DCHECK(!for_websockets_);
70   return RequestStreamInternal(request_info,
71                                priority,
72                                server_ssl_config,
73                                proxy_ssl_config,
74                                delegate,
75                                NULL,
76                                net_log);
77 }
78 
RequestWebSocketHandshakeStream(const HttpRequestInfo & request_info,RequestPriority priority,const SSLConfig & server_ssl_config,const SSLConfig & proxy_ssl_config,HttpStreamRequest::Delegate * delegate,WebSocketHandshakeStreamBase::CreateHelper * create_helper,const BoundNetLog & net_log)79 HttpStreamRequest* HttpStreamFactoryImpl::RequestWebSocketHandshakeStream(
80     const HttpRequestInfo& request_info,
81     RequestPriority priority,
82     const SSLConfig& server_ssl_config,
83     const SSLConfig& proxy_ssl_config,
84     HttpStreamRequest::Delegate* delegate,
85     WebSocketHandshakeStreamBase::CreateHelper* create_helper,
86     const BoundNetLog& net_log) {
87   DCHECK(for_websockets_);
88   DCHECK(create_helper);
89   return RequestStreamInternal(request_info,
90                                priority,
91                                server_ssl_config,
92                                proxy_ssl_config,
93                                delegate,
94                                create_helper,
95                                net_log);
96 }
97 
RequestStreamInternal(const HttpRequestInfo & request_info,RequestPriority priority,const SSLConfig & server_ssl_config,const SSLConfig & proxy_ssl_config,HttpStreamRequest::Delegate * delegate,WebSocketHandshakeStreamBase::CreateHelper * websocket_handshake_stream_create_helper,const BoundNetLog & net_log)98 HttpStreamRequest* HttpStreamFactoryImpl::RequestStreamInternal(
99     const HttpRequestInfo& request_info,
100     RequestPriority priority,
101     const SSLConfig& server_ssl_config,
102     const SSLConfig& proxy_ssl_config,
103     HttpStreamRequest::Delegate* delegate,
104     WebSocketHandshakeStreamBase::CreateHelper*
105         websocket_handshake_stream_create_helper,
106     const BoundNetLog& net_log) {
107   Request* request = new Request(request_info.url,
108                                  this,
109                                  delegate,
110                                  websocket_handshake_stream_create_helper,
111                                  net_log);
112 
113   GURL alternate_url;
114   PortAlternateProtocolPair alternate =
115       GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
116   Job* alternate_job = NULL;
117   if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
118     // Never share connection with other jobs for FTP requests.
119     DCHECK(!request_info.url.SchemeIs("ftp"));
120 
121     HttpRequestInfo alternate_request_info = request_info;
122     alternate_request_info.url = alternate_url;
123     alternate_job =
124         new Job(this, session_, alternate_request_info, priority,
125                 server_ssl_config, proxy_ssl_config, net_log.net_log());
126     request->AttachJob(alternate_job);
127     alternate_job->MarkAsAlternate(request_info.url, alternate);
128   }
129 
130   Job* job = new Job(this, session_, request_info, priority,
131                      server_ssl_config, proxy_ssl_config, net_log.net_log());
132   request->AttachJob(job);
133   if (alternate_job) {
134     // Never share connection with other jobs for FTP requests.
135     DCHECK(!request_info.url.SchemeIs("ftp"));
136 
137     job->WaitFor(alternate_job);
138     // Make sure to wait until we call WaitFor(), before starting
139     // |alternate_job|, otherwise |alternate_job| will not notify |job|
140     // appropriately.
141     alternate_job->Start(request);
142   }
143   // Even if |alternate_job| has already finished, it won't have notified the
144   // request yet, since we defer that to the next iteration of the MessageLoop,
145   // so starting |job| is always safe.
146   job->Start(request);
147   return request;
148 }
149 
PreconnectStreams(int num_streams,const HttpRequestInfo & request_info,RequestPriority priority,const SSLConfig & server_ssl_config,const SSLConfig & proxy_ssl_config)150 void HttpStreamFactoryImpl::PreconnectStreams(
151     int num_streams,
152     const HttpRequestInfo& request_info,
153     RequestPriority priority,
154     const SSLConfig& server_ssl_config,
155     const SSLConfig& proxy_ssl_config) {
156   DCHECK(!for_websockets_);
157   GURL alternate_url;
158   PortAlternateProtocolPair alternate =
159       GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
160   Job* job = NULL;
161   if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
162     HttpRequestInfo alternate_request_info = request_info;
163     alternate_request_info.url = alternate_url;
164     job = new Job(this, session_, alternate_request_info, priority,
165                   server_ssl_config, proxy_ssl_config, session_->net_log());
166     job->MarkAsAlternate(request_info.url, alternate);
167   } else {
168     job = new Job(this, session_, request_info, priority,
169                   server_ssl_config, proxy_ssl_config, session_->net_log());
170   }
171   preconnect_job_set_.insert(job);
172   job->Preconnect(num_streams);
173 }
174 
GetHostMappingRules() const175 const HostMappingRules* HttpStreamFactoryImpl::GetHostMappingRules() const {
176   return session_->params().host_mapping_rules;
177 }
178 
GetAlternateProtocolRequestFor(const GURL & original_url,GURL * alternate_url)179 PortAlternateProtocolPair HttpStreamFactoryImpl::GetAlternateProtocolRequestFor(
180     const GURL& original_url,
181     GURL* alternate_url) {
182   if (!session_->params().use_alternate_protocols)
183     return kNoAlternateProtocol;
184 
185   if (original_url.SchemeIs("ftp"))
186     return kNoAlternateProtocol;
187 
188   HostPortPair origin = HostPortPair(original_url.HostNoBrackets(),
189                                      original_url.EffectiveIntPort());
190 
191   HttpServerProperties& http_server_properties =
192       *session_->http_server_properties();
193   if (!http_server_properties.HasAlternateProtocol(origin))
194     return kNoAlternateProtocol;
195 
196   PortAlternateProtocolPair alternate =
197       http_server_properties.GetAlternateProtocol(origin);
198   if (alternate.protocol == ALTERNATE_PROTOCOL_BROKEN) {
199     HistogramAlternateProtocolUsage(
200         ALTERNATE_PROTOCOL_USAGE_BROKEN,
201         http_server_properties.GetAlternateProtocolExperiment());
202     return kNoAlternateProtocol;
203   }
204 
205   if (!IsAlternateProtocolValid(alternate.protocol)) {
206     NOTREACHED();
207     return kNoAlternateProtocol;
208   }
209 
210   // Some shared unix systems may have user home directories (like
211   // http://foo.com/~mike) which allow users to emit headers.  This is a bad
212   // idea already, but with Alternate-Protocol, it provides the ability for a
213   // single user on a multi-user system to hijack the alternate protocol.
214   // These systems also enforce ports <1024 as restricted ports.  So don't
215   // allow protocol upgrades to user-controllable ports.
216   const int kUnrestrictedPort = 1024;
217   if (!session_->params().enable_user_alternate_protocol_ports &&
218       (alternate.port >= kUnrestrictedPort &&
219        origin.port() < kUnrestrictedPort))
220     return kNoAlternateProtocol;
221 
222   origin.set_port(alternate.port);
223   if (alternate.protocol >= NPN_SPDY_MINIMUM_VERSION &&
224       alternate.protocol <= NPN_SPDY_MAXIMUM_VERSION) {
225     if (!HttpStreamFactory::spdy_enabled())
226       return kNoAlternateProtocol;
227 
228     if (session_->HasSpdyExclusion(origin))
229       return kNoAlternateProtocol;
230 
231     *alternate_url = UpgradeUrlToHttps(original_url, alternate.port);
232   } else {
233     DCHECK_EQ(QUIC, alternate.protocol);
234     if (!session_->params().enable_quic ||
235         !(original_url.SchemeIs("http") ||
236           session_->params().enable_quic_https)) {
237         return kNoAlternateProtocol;
238     }
239     // TODO(rch):  Figure out how to make QUIC iteract with PAC
240     // scripts.  By not re-writing the URL, we will query the PAC script
241     // for the proxy to use to reach the original URL via TCP.  But
242     // the alternate request will be going via UDP to a different port.
243     *alternate_url = original_url;
244   }
245   return alternate;
246 }
247 
OrphanJob(Job * job,const Request * request)248 void HttpStreamFactoryImpl::OrphanJob(Job* job, const Request* request) {
249   DCHECK(ContainsKey(request_map_, job));
250   DCHECK_EQ(request_map_[job], request);
251   DCHECK(!ContainsKey(orphaned_job_set_, job));
252 
253   request_map_.erase(job);
254 
255   orphaned_job_set_.insert(job);
256   job->Orphan(request);
257 }
258 
OnNewSpdySessionReady(const base::WeakPtr<SpdySession> & spdy_session,bool direct,const SSLConfig & used_ssl_config,const ProxyInfo & used_proxy_info,bool was_npn_negotiated,NextProto protocol_negotiated,bool using_spdy,const BoundNetLog & net_log)259 void HttpStreamFactoryImpl::OnNewSpdySessionReady(
260     const base::WeakPtr<SpdySession>& spdy_session,
261     bool direct,
262     const SSLConfig& used_ssl_config,
263     const ProxyInfo& used_proxy_info,
264     bool was_npn_negotiated,
265     NextProto protocol_negotiated,
266     bool using_spdy,
267     const BoundNetLog& net_log) {
268   while (true) {
269     if (!spdy_session)
270       break;
271     const SpdySessionKey& spdy_session_key = spdy_session->spdy_session_key();
272     // Each iteration may empty out the RequestSet for |spdy_session_key| in
273     // |spdy_session_request_map_|. So each time, check for RequestSet and use
274     // the first one.
275     //
276     // TODO(willchan): If it's important, switch RequestSet out for a FIFO
277     // queue (Order by priority first, then FIFO within same priority). Unclear
278     // that it matters here.
279     if (!ContainsKey(spdy_session_request_map_, spdy_session_key))
280       break;
281     Request* request = *spdy_session_request_map_[spdy_session_key].begin();
282     request->Complete(was_npn_negotiated,
283                       protocol_negotiated,
284                       using_spdy,
285                       net_log);
286     if (for_websockets_) {
287       // TODO(ricea): Restore this code path when WebSocket over SPDY
288       // implementation is ready.
289       NOTREACHED();
290     } else {
291       bool use_relative_url = direct || request->url().SchemeIs("https");
292       request->OnStreamReady(
293           NULL,
294           used_ssl_config,
295           used_proxy_info,
296           new SpdyHttpStream(spdy_session, use_relative_url));
297     }
298   }
299   // TODO(mbelshe): Alert other valid requests.
300 }
301 
OnOrphanedJobComplete(const Job * job)302 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) {
303   orphaned_job_set_.erase(job);
304   delete job;
305 }
306 
OnPreconnectsComplete(const Job * job)307 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) {
308   preconnect_job_set_.erase(job);
309   delete job;
310   OnPreconnectsCompleteInternal();
311 }
312 
313 }  // namespace net
314