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