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 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 42 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session, 43 bool for_websockets) 44 : session_(session), 45 for_websockets_(for_websockets) {} 46 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 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 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 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 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 175 const HostMappingRules* HttpStreamFactoryImpl::GetHostMappingRules() const { 176 return session_->params().host_mapping_rules; 177 } 178 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 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 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 302 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) { 303 orphaned_job_set_.erase(job); 304 delete job; 305 } 306 307 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) { 308 preconnect_job_set_.erase(job); 309 delete job; 310 OnPreconnectsCompleteInternal(); 311 } 312 313 } // namespace net 314