Home | History | Annotate | Download | only in http
      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