Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / net / spdy / spdy_session.cc
index 7561145..d5f04ad 100644 (file)
 #include "net/base/net_log.h"
 #include "net/base/net_util.h"
 #include "net/cert/asn1_util.h"
+#include "net/cert/cert_verify_result.h"
 #include "net/http/http_log_util.h"
 #include "net/http/http_network_session.h"
 #include "net/http/http_server_properties.h"
 #include "net/http/http_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/ssl_client_socket.h"
 #include "net/spdy/spdy_buffer_producer.h"
 #include "net/spdy/spdy_frame_builder.h"
 #include "net/spdy/spdy_http_utils.h"
 #include "net/spdy/spdy_protocol.h"
 #include "net/spdy/spdy_session_pool.h"
 #include "net/spdy/spdy_stream.h"
-#include "net/ssl/server_bound_cert_service.h"
+#include "net/ssl/channel_id_service.h"
 #include "net/ssl/ssl_cipher_suite_names.h"
 #include "net/ssl/ssl_connection_status_flags.h"
 
@@ -132,6 +135,18 @@ base::Value* NetLogSpdySessionCallback(const HostPortProxyPair* host_pair,
   return dict;
 }
 
+base::Value* NetLogSpdyInitializedCallback(NetLog::Source source,
+                                           const NextProto protocol_version,
+                                           NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  if (source.IsValid()) {
+    source.AddToEventParameters(dict);
+  }
+  dict->SetString("protocol",
+                  SSLClientSocket::NextProtoToString(protocol_version));
+  return dict;
+}
+
 base::Value* NetLogSpdySettingsCallback(const HostPortPair& host_port_pair,
                                         bool clear_persisted,
                                         NetLog::LogLevel /* log_level */) {
@@ -142,18 +157,22 @@ base::Value* NetLogSpdySettingsCallback(const HostPortPair& host_port_pair,
 }
 
 base::Value* NetLogSpdySettingCallback(SpdySettingsIds id,
+                                       const SpdyMajorVersion protocol_version,
                                        SpdySettingsFlags flags,
                                        uint32 value,
                                        NetLog::LogLevel /* log_level */) {
   base::DictionaryValue* dict = new base::DictionaryValue();
-  dict->SetInteger("id", id);
+  dict->SetInteger("id",
+                   SpdyConstants::SerializeSettingId(protocol_version, id));
   dict->SetInteger("flags", flags);
   dict->SetInteger("value", value);
   return dict;
 }
 
-base::Value* NetLogSpdySendSettingsCallback(const SettingsMap* settings,
-                                            NetLog::LogLevel /* log_level */) {
+base::Value* NetLogSpdySendSettingsCallback(
+    const SettingsMap* settings,
+    const SpdyMajorVersion protocol_version,
+    NetLog::LogLevel /* log_level */) {
   base::DictionaryValue* dict = new base::DictionaryValue();
   base::ListValue* settings_list = new base::ListValue();
   for (SettingsMap::const_iterator it = settings->begin();
@@ -161,8 +180,11 @@ base::Value* NetLogSpdySendSettingsCallback(const SettingsMap* settings,
     const SpdySettingsIds id = it->first;
     const SpdySettingsFlags flags = it->second.first;
     const uint32 value = it->second.second;
-    settings_list->Append(new base::StringValue(
-        base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value)));
+    settings_list->Append(new base::StringValue(base::StringPrintf(
+        "[id:%u flags:%u value:%u]",
+        SpdyConstants::SerializeSettingId(protocol_version, id),
+        flags,
+        value)));
   }
   dict->Set("settings", settings_list);
   return dict;
@@ -529,9 +551,46 @@ SpdySession::PushedStreamInfo::PushedStreamInfo(
 
 SpdySession::PushedStreamInfo::~PushedStreamInfo() {}
 
+// static
+bool SpdySession::CanPool(TransportSecurityState* transport_security_state,
+                          const SSLInfo& ssl_info,
+                          const std::string& old_hostname,
+                          const std::string& new_hostname) {
+  // Pooling is prohibited if the server cert is not valid for the new domain,
+  // and for connections on which client certs were sent. It is also prohibited
+  // when channel ID was sent if the hosts are from different eTLDs+1.
+  if (IsCertStatusError(ssl_info.cert_status))
+    return false;
+
+  if (ssl_info.client_cert_sent)
+    return false;
+
+  if (ssl_info.channel_id_sent &&
+      ChannelIDService::GetDomainForHost(new_hostname) !=
+      ChannelIDService::GetDomainForHost(old_hostname)) {
+    return false;
+  }
+
+  bool unused = false;
+  if (!ssl_info.cert->VerifyNameMatch(new_hostname, &unused))
+    return false;
+
+  std::string pinning_failure_log;
+  if (!transport_security_state->CheckPublicKeyPins(
+          new_hostname,
+          ssl_info.is_issued_by_known_root,
+          ssl_info.public_key_hashes,
+          &pinning_failure_log)) {
+    return false;
+  }
+
+  return true;
+}
+
 SpdySession::SpdySession(
     const SpdySessionKey& spdy_session_key,
     const base::WeakPtr<HttpServerProperties>& http_server_properties,
+    TransportSecurityState* transport_security_state,
     bool verify_domain_authentication,
     bool enable_sending_initial_data,
     bool enable_compression,
@@ -547,8 +606,12 @@ SpdySession::SpdySession(
       spdy_session_key_(spdy_session_key),
       pool_(NULL),
       http_server_properties_(http_server_properties),
+      transport_security_state_(transport_security_state),
       read_buffer_(new IOBuffer(kReadBufferSize)),
       stream_hi_water_mark_(kFirstStreamId),
+      last_accepted_push_stream_id_(0),
+      num_pushed_streams_(0u),
+      num_active_pushed_streams_(0u),
       in_flight_write_frame_type_(DATA),
       in_flight_write_frame_size_(0),
       is_secure_(false),
@@ -563,6 +626,7 @@ SpdySession::SpdySession(
       max_concurrent_streams_limit_(max_concurrent_streams_limit == 0
                                         ? kMaxConcurrentStreamLimit
                                         : max_concurrent_streams_limit),
+      max_concurrent_pushed_streams_(kMaxConcurrentPushedStreams),
       streams_initiated_count_(0),
       streams_pushed_count_(0),
       streams_pushed_and_claimed_count_(0),
@@ -674,16 +738,15 @@ void SpdySession::InitializeWithSocket(
                              enable_compression_));
   buffered_spdy_framer_->set_visitor(this);
   buffered_spdy_framer_->set_debug_visitor(this);
-  UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol_, kProtoMaximumVersion);
-#if defined(SPDY_PROXY_AUTH_ORIGIN)
-  UMA_HISTOGRAM_BOOLEAN("Net.SpdySessions_DataReductionProxy",
-                        host_port_pair().Equals(HostPortPair::FromURL(
-                            GURL(SPDY_PROXY_AUTH_ORIGIN))));
-#endif
+  UMA_HISTOGRAM_ENUMERATION(
+      "Net.SpdyVersion2",
+      protocol_ - kProtoSPDYMinimumVersion,
+      kProtoSPDYMaximumVersion - kProtoSPDYMinimumVersion + 1);
 
-  net_log_.AddEvent(
-      NetLog::TYPE_SPDY_SESSION_INITIALIZED,
-      connection_->socket()->NetLog().source().ToEventParametersCallback());
+  net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_INITIALIZED,
+                    base::Bind(&NetLogSpdyInitializedCallback,
+                               connection_->socket()->NetLog().source(),
+                               protocol_));
 
   DCHECK_EQ(availability_state_, STATE_AVAILABLE);
   connection_->AddHigherLayeredPool(this);
@@ -711,13 +774,8 @@ bool SpdySession::VerifyDomainAuthentication(const std::string& domain) {
   if (!GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated))
     return true;   // This is not a secure session, so all domains are okay.
 
-  bool unused = false;
-  return
-      !ssl_info.client_cert_sent &&
-      (!ssl_info.channel_id_sent ||
-       (ServerBoundCertService::GetDomainForHost(domain) ==
-        ServerBoundCertService::GetDomainForHost(host_port_pair().host()))) &&
-      ssl_info.cert->VerifyNameMatch(domain, &unused);
+  return CanPool(transport_security_state_, ssl_info,
+                 host_port_pair().host(), domain);
 }
 
 int SpdySession::GetPushStream(
@@ -777,7 +835,7 @@ int SpdySession::TryCreateStream(
     return err;
 
   if (!max_concurrent_streams_ ||
-      (active_streams_.size() + created_streams_.size() <
+      (active_streams_.size() + created_streams_.size() - num_pushed_streams_ <
        max_concurrent_streams_)) {
     return CreateStream(*request, stream);
   }
@@ -981,7 +1039,7 @@ scoped_ptr<SpdyFrame> SpdySession::CreateSynStream(
     SpdyStreamId stream_id,
     RequestPriority priority,
     SpdyControlFlags flags,
-    const SpdyHeaderBlock& headers) {
+    const SpdyHeaderBlock& block) {
   ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
   CHECK(it != active_streams_.end());
   CHECK_EQ(it->second.stream->stream_id(), stream_id);
@@ -991,22 +1049,38 @@ scoped_ptr<SpdyFrame> SpdySession::CreateSynStream(
   DCHECK(buffered_spdy_framer_.get());
   SpdyPriority spdy_priority =
       ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion());
-  scoped_ptr<SpdyFrame> syn_frame(
-      buffered_spdy_framer_->CreateSynStream(stream_id, 0, spdy_priority, flags,
-                                             &headers));
+
+  scoped_ptr<SpdyFrame> syn_frame;
+  // TODO(hkhalil): Avoid copy of |block|.
+  if (GetProtocolVersion() <= SPDY3) {
+    SpdySynStreamIR syn_stream(stream_id);
+    syn_stream.set_associated_to_stream_id(0);
+    syn_stream.set_priority(spdy_priority);
+    syn_stream.set_fin((flags & CONTROL_FLAG_FIN) != 0);
+    syn_stream.set_unidirectional((flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
+    syn_stream.set_name_value_block(block);
+    syn_frame.reset(buffered_spdy_framer_->SerializeFrame(syn_stream));
+  } else {
+    SpdyHeadersIR headers(stream_id);
+    headers.set_priority(spdy_priority);
+    headers.set_has_priority(true);
+    headers.set_fin((flags & CONTROL_FLAG_FIN) != 0);
+    headers.set_name_value_block(block);
+    syn_frame.reset(buffered_spdy_framer_->SerializeFrame(headers));
+  }
 
   base::StatsCounter spdy_requests("spdy.requests");
   spdy_requests.Increment();
   streams_initiated_count_++;
 
   if (net_log().IsLogging()) {
-    net_log().AddEvent(
-        NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
-        base::Bind(&NetLogSpdySynStreamSentCallback, &headers,
-                   (flags & CONTROL_FLAG_FIN) != 0,
-                   (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0,
-                   spdy_priority,
-                   stream_id));
+    net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+                       base::Bind(&NetLogSpdySynStreamSentCallback,
+                                  &block,
+                                  (flags & CONTROL_FLAG_FIN) != 0,
+                                  (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0,
+                                  spdy_priority,
+                                  stream_id));
   }
 
   return syn_frame.Pass();
@@ -1127,7 +1201,10 @@ scoped_ptr<SpdyBuffer> SpdySession::CreateDataBuffer(SpdyStreamId stream_id,
 
   scoped_ptr<SpdyBuffer> data_buffer(new SpdyBuffer(frame.Pass()));
 
-  if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+  // Send window size is based on payload size, so nothing to do if this is
+  // just a FIN with no payload.
+  if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION &&
+      effective_len != 0) {
     DecreaseSendWindowSize(static_cast<int32>(effective_len));
     data_buffer->AddConsumeCallback(
         base::Bind(&SpdySession::OnWriteBufferConsumed,
@@ -1202,8 +1279,12 @@ void SpdySession::CloseActiveStreamIterator(ActiveStreamMap::iterator it,
   // probably something that we still want to support, although server
   // push is hardly used. Write tests for this and fix this. (See
   // http://crbug.com/261712 .)
-  if (owned_stream->type() == SPDY_PUSH_STREAM)
+  if (owned_stream->type() == SPDY_PUSH_STREAM) {
     unclaimed_pushed_streams_.erase(owned_stream->url());
+    num_pushed_streams_--;
+    if (!owned_stream->IsReservedRemote())
+      num_active_pushed_streams_--;
+  }
 
   DeleteStream(owned_stream.Pass(), status);
   MaybeFinishGoingAway();
@@ -1615,7 +1696,7 @@ void SpdySession::DoDrainSession(Error err, const std::string& description) {
       err != ERR_SOCKET_NOT_CONNECTED &&
       err != ERR_CONNECTION_CLOSED && err != ERR_CONNECTION_RESET) {
     // Enqueue a GOAWAY to inform the peer of why we're closing the connection.
-    SpdyGoAwayIR goaway_ir(0,  // Last accepted stream ID.
+    SpdyGoAwayIR goaway_ir(last_accepted_push_stream_id_,
                            MapNetErrorToGoAwayStatus(err),
                            description);
     EnqueueSessionWrite(HIGHEST,
@@ -2039,10 +2120,13 @@ void SpdySession::OnSetting(SpdySettingsIds id,
   received_settings_ = true;
 
   // Log the setting.
-  net_log_.AddEvent(
-      NetLog::TYPE_SPDY_SESSION_RECV_SETTING,
-      base::Bind(&NetLogSpdySettingCallback,
-                 id, static_cast<SpdySettingsFlags>(flags), value));
+  const SpdyMajorVersion protocol_version = GetProtocolVersion();
+  net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_RECV_SETTING,
+                    base::Bind(&NetLogSpdySettingCallback,
+                               id,
+                               protocol_version,
+                               static_cast<SpdySettingsFlags>(flags),
+                               value));
 }
 
 void SpdySession::OnSendCompressedFrame(
@@ -2050,7 +2134,7 @@ void SpdySession::OnSendCompressedFrame(
     SpdyFrameType type,
     size_t payload_len,
     size_t frame_len) {
-  if (type != SYN_STREAM)
+  if (type != SYN_STREAM && type != HEADERS)
     return;
 
   DCHECK(buffered_spdy_framer_.get());
@@ -2079,6 +2163,23 @@ int SpdySession::OnInitialResponseHeadersReceived(
     SpdyStream* stream) {
   CHECK(in_io_loop_);
   SpdyStreamId stream_id = stream->stream_id();
+
+  if (stream->type() == SPDY_PUSH_STREAM) {
+    DCHECK(stream->IsReservedRemote());
+    if (max_concurrent_pushed_streams_ &&
+        num_active_pushed_streams_ >= max_concurrent_pushed_streams_) {
+      ResetStream(stream_id,
+                  RST_STREAM_REFUSED_STREAM,
+                  "Stream concurrency limit reached.");
+      return STATUS_CODE_REFUSED_STREAM;
+    }
+  }
+
+  if (stream->type() == SPDY_PUSH_STREAM) {
+    // Will be balanced in DeleteStream.
+    num_active_pushed_streams_++;
+  }
+
   // May invalidate |stream|.
   int rv = stream->OnInitialResponseHeadersReceived(
       response_headers, response_time, recv_first_byte_time);
@@ -2086,6 +2187,7 @@ int SpdySession::OnInitialResponseHeadersReceived(
     DCHECK_NE(rv, ERR_IO_PENDING);
     DCHECK(active_streams_.find(stream_id) == active_streams_.end());
   }
+
   return rv;
 }
 
@@ -2275,6 +2377,18 @@ void SpdySession::OnHeaders(SpdyStreamId stream_id,
   }
 }
 
+bool SpdySession::OnUnknownFrame(SpdyStreamId stream_id, int frame_type) {
+  // Validate stream id.
+  // Was the frame sent on a stream id that has not been used in this session?
+  if (stream_id % 2 == 1 && stream_id > stream_hi_water_mark_)
+    return false;
+
+  if (stream_id % 2 == 0 && stream_id > last_accepted_push_stream_id_)
+    return false;
+
+  return true;
+}
+
 void SpdySession::OnRstStream(SpdyStreamId stream_id,
                               SpdyRstStreamStatus status) {
   CHECK(in_io_loop_);
@@ -2432,14 +2546,32 @@ bool SpdySession::TryCreatePushStream(SpdyStreamId stream_id,
   // Server-initiated streams should have even sequence numbers.
   if ((stream_id & 0x1) != 0) {
     LOG(WARNING) << "Received invalid push stream id " << stream_id;
+    if (GetProtocolVersion() > SPDY2)
+      CloseSessionOnError(ERR_SPDY_PROTOCOL_ERROR, "Odd push stream id.");
     return false;
   }
 
+  if (GetProtocolVersion() > SPDY2) {
+    if (stream_id <= last_accepted_push_stream_id_) {
+      LOG(WARNING) << "Received push stream id lesser or equal to the last "
+                   << "accepted before " << stream_id;
+      CloseSessionOnError(
+          ERR_SPDY_PROTOCOL_ERROR,
+          "New push stream id must be greater than the last accepted.");
+      return false;
+    }
+  }
+
   if (IsStreamActive(stream_id)) {
+    // For SPDY3 and higher we should not get here, we'll start going away
+    // earlier on |last_seen_push_stream_id_| check.
+    CHECK_GT(SPDY3, GetProtocolVersion());
     LOG(WARNING) << "Received push for active stream " << stream_id;
     return false;
   }
 
+  last_accepted_push_stream_id_ = stream_id;
+
   RequestPriority request_priority =
       ConvertSpdyPriorityToRequestPriority(priority, GetProtocolVersion());
 
@@ -2568,6 +2700,7 @@ bool SpdySession::TryCreatePushStream(SpdyStreamId stream_id,
 
   active_it->second.stream->OnPushPromiseHeadersReceived(headers);
   DCHECK(active_it->second.stream->IsReservedRemote());
+  num_pushed_streams_++;
   return true;
 }
 
@@ -2645,35 +2778,37 @@ void SpdySession::SendInitialData() {
         kDefaultInitialRecvWindowSize - session_recv_window_size_);
   }
 
-  // Finally, notify the server about the settings they have
-  // previously told us to use when communicating with them (after
-  // applying them).
-  const SettingsMap& server_settings_map =
-      http_server_properties_->GetSpdySettings(host_port_pair());
-  if (server_settings_map.empty())
-    return;
+  if (protocol_ <= kProtoSPDY31) {
+    // Finally, notify the server about the settings they have
+    // previously told us to use when communicating with them (after
+    // applying them).
+    const SettingsMap& server_settings_map =
+        http_server_properties_->GetSpdySettings(host_port_pair());
+    if (server_settings_map.empty())
+      return;
 
-  SettingsMap::const_iterator it =
-      server_settings_map.find(SETTINGS_CURRENT_CWND);
-  uint32 cwnd = (it != server_settings_map.end()) ? it->second.second : 0;
-  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
+    SettingsMap::const_iterator it =
+        server_settings_map.find(SETTINGS_CURRENT_CWND);
+    uint32 cwnd = (it != server_settings_map.end()) ? it->second.second : 0;
+    UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
 
-  for (SettingsMap::const_iterator it = server_settings_map.begin();
-       it != server_settings_map.end(); ++it) {
-    const SpdySettingsIds new_id = it->first;
-    const uint32 new_val = it->second.second;
-    HandleSetting(new_id, new_val);
-  }
+    for (SettingsMap::const_iterator it = server_settings_map.begin();
+         it != server_settings_map.end(); ++it) {
+      const SpdySettingsIds new_id = it->first;
+      const uint32 new_val = it->second.second;
+      HandleSetting(new_id, new_val);
+    }
 
-  SendSettings(server_settings_map);
+    SendSettings(server_settings_map);
+  }
 }
 
 
 void SpdySession::SendSettings(const SettingsMap& settings) {
+  const SpdyMajorVersion protocol_version = GetProtocolVersion();
   net_log_.AddEvent(
       NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS,
-      base::Bind(&NetLogSpdySendSettingsCallback, &settings));
-
+      base::Bind(&NetLogSpdySendSettingsCallback, &settings, protocol_version));
   // Create the SETTINGS frame and send it.
   DCHECK(buffered_spdy_framer_.get());
   scoped_ptr<SpdyFrame> settings_frame(