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.
5 #include "net/spdy/spdy_proxy_client_socket.h"
7 #include <algorithm> // min
10 #include "base/bind_helpers.h"
11 #include "base/logging.h"
12 #include "base/string_util.h"
13 #include "googleurl/src/gurl.h"
14 #include "net/base/auth.h"
15 #include "net/base/io_buffer.h"
16 #include "net/base/net_util.h"
17 #include "net/http/http_auth_cache.h"
18 #include "net/http/http_auth_handler_factory.h"
19 #include "net/http/http_response_headers.h"
20 #include "net/spdy/spdy_http_utils.h"
24 SpdyProxyClientSocket::SpdyProxyClientSocket(
25 SpdyStream
* spdy_stream
,
26 const std::string
& user_agent
,
27 const HostPortPair
& endpoint
,
29 const HostPortPair
& proxy_server
,
30 HttpAuthCache
* auth_cache
,
31 HttpAuthHandlerFactory
* auth_handler_factory
)
32 : next_state_(STATE_DISCONNECTED
),
33 spdy_stream_(spdy_stream
),
36 new HttpAuthController(HttpAuth::AUTH_PROXY
,
37 GURL("https://" + proxy_server
.ToString()),
39 auth_handler_factory
)),
42 write_bytes_outstanding_(0),
43 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
44 net_log_(spdy_stream
->net_log()) {
45 request_
.method
= "CONNECT";
47 if (!user_agent
.empty())
48 request_
.extra_headers
.SetHeader(HttpRequestHeaders::kUserAgent
,
50 spdy_stream_
->SetDelegate(this);
51 was_ever_used_
= spdy_stream_
->WasEverUsed();
54 SpdyProxyClientSocket::~SpdyProxyClientSocket() {
58 const HttpResponseInfo
* SpdyProxyClientSocket::GetConnectResponseInfo() const {
59 return response_
.headers
? &response_
: NULL
;
62 const scoped_refptr
<HttpAuthController
>&
63 SpdyProxyClientSocket::GetAuthController() const {
67 int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback
& callback
) {
68 // A SPDY Stream can only handle a single request, so the underlying
69 // stream may not be reused and a new SpdyProxyClientSocket must be
70 // created (possibly on top of the same SPDY Session).
71 next_state_
= STATE_DISCONNECTED
;
75 bool SpdyProxyClientSocket::IsUsingSpdy() const {
79 NextProto
SpdyProxyClientSocket::GetProtocolNegotiated() const {
80 // Save the negotiated protocol
82 bool was_npn_negotiated
;
83 NextProto protocol_negotiated
;
84 spdy_stream_
->GetSSLInfo(&ssl_info
, &was_npn_negotiated
,
85 &protocol_negotiated
);
86 return protocol_negotiated
;
89 HttpStream
* SpdyProxyClientSocket::CreateConnectResponseStream() {
90 DCHECK(response_stream_
.get());
91 return response_stream_
.release();
94 // Sends a SYN_STREAM frame to the proxy with a CONNECT request
95 // for the specified endpoint. Waits for the server to send back
96 // a SYN_REPLY frame. OK will be returned if the status is 200.
97 // ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status.
98 // In any of these cases, Read() may be called to retrieve the HTTP
99 // response body. Any other return values should be considered fatal.
100 // TODO(rch): handle 407 proxy auth requested correctly, perhaps
101 // by creating a new stream for the subsequent request.
102 // TODO(rch): create a more appropriate error code to disambiguate
103 // the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure.
104 int SpdyProxyClientSocket::Connect(const CompletionCallback
& callback
) {
105 DCHECK(read_callback_
.is_null());
106 if (next_state_
== STATE_OPEN
)
109 DCHECK_EQ(STATE_DISCONNECTED
, next_state_
);
110 next_state_
= STATE_GENERATE_AUTH_TOKEN
;
113 if (rv
== ERR_IO_PENDING
)
114 read_callback_
= callback
;
118 void SpdyProxyClientSocket::Disconnect() {
119 read_buffer_
.clear();
121 read_callback_
.Reset();
123 write_buffer_len_
= 0;
124 write_bytes_outstanding_
= 0;
125 write_callback_
.Reset();
127 next_state_
= STATE_DISCONNECTED
;
130 // This will cause OnClose to be invoked, which takes care of
131 // cleaning up all the internal state.
132 spdy_stream_
->Cancel();
135 bool SpdyProxyClientSocket::IsConnected() const {
136 return next_state_
== STATE_OPEN
;
139 bool SpdyProxyClientSocket::IsConnectedAndIdle() const {
140 return IsConnected() && read_buffer_
.empty() && spdy_stream_
->is_idle();
143 const BoundNetLog
& SpdyProxyClientSocket::NetLog() const {
147 void SpdyProxyClientSocket::SetSubresourceSpeculation() {
148 // TODO(rch): what should this implementation be?
151 void SpdyProxyClientSocket::SetOmniboxSpeculation() {
152 // TODO(rch): what should this implementation be?
155 bool SpdyProxyClientSocket::WasEverUsed() const {
156 return was_ever_used_
|| (spdy_stream_
&& spdy_stream_
->WasEverUsed());
159 bool SpdyProxyClientSocket::UsingTCPFastOpen() const {
163 int64
SpdyProxyClientSocket::NumBytesRead() const {
167 base::TimeDelta
SpdyProxyClientSocket::GetConnectTimeMicros() const {
168 return base::TimeDelta::FromMicroseconds(-1);
171 bool SpdyProxyClientSocket::WasNpnNegotiated() const {
175 NextProto
SpdyProxyClientSocket::GetNegotiatedProtocol() const {
176 return kProtoUnknown
;
179 bool SpdyProxyClientSocket::GetSSLInfo(SSLInfo
* ssl_info
) {
180 bool was_npn_negotiated
;
181 NextProto protocol_negotiated
;
182 return spdy_stream_
->GetSSLInfo(ssl_info
, &was_npn_negotiated
,
183 &protocol_negotiated
);
186 int SpdyProxyClientSocket::Read(IOBuffer
* buf
, int buf_len
,
187 const CompletionCallback
& callback
) {
188 DCHECK(read_callback_
.is_null());
189 DCHECK(!user_buffer_
);
191 if (next_state_
== STATE_DISCONNECTED
)
192 return ERR_SOCKET_NOT_CONNECTED
;
194 if (next_state_
== STATE_CLOSED
&& read_buffer_
.empty()) {
198 DCHECK(next_state_
== STATE_OPEN
|| next_state_
== STATE_CLOSED
);
200 user_buffer_
= new DrainableIOBuffer(buf
, buf_len
);
201 int result
= PopulateUserReadBuffer();
203 DCHECK(!callback
.is_null());
204 read_callback_
= callback
;
205 return ERR_IO_PENDING
;
211 int SpdyProxyClientSocket::PopulateUserReadBuffer() {
213 return ERR_IO_PENDING
;
216 while (!read_buffer_
.empty() && user_buffer_
->BytesRemaining() > 0) {
217 scoped_refptr
<DrainableIOBuffer
> data
= read_buffer_
.front();
218 const int bytes_to_copy
= std::min(user_buffer_
->BytesRemaining(),
219 data
->BytesRemaining());
220 memcpy(user_buffer_
->data(), data
->data(), bytes_to_copy
);
221 user_buffer_
->DidConsume(bytes_to_copy
);
222 bytes_read
+= bytes_to_copy
;
223 if (data
->BytesRemaining() == bytes_to_copy
) {
224 // Consumed all data from this buffer
225 read_buffer_
.pop_front();
227 data
->DidConsume(bytes_to_copy
);
231 if (bytes_read
> 0 && spdy_stream_
)
232 spdy_stream_
->IncreaseRecvWindowSize(bytes_read
);
234 return user_buffer_
->BytesConsumed();
237 int SpdyProxyClientSocket::Write(IOBuffer
* buf
, int buf_len
,
238 const CompletionCallback
& callback
) {
239 DCHECK(write_callback_
.is_null());
240 if (next_state_
!= STATE_OPEN
)
241 return ERR_SOCKET_NOT_CONNECTED
;
243 DCHECK(spdy_stream_
);
244 write_bytes_outstanding_
= buf_len
;
245 if (buf_len
<= kMaxSpdyFrameChunkSize
) {
246 int rv
= spdy_stream_
->WriteStreamData(buf
, buf_len
, DATA_FLAG_NONE
);
247 if (rv
== ERR_IO_PENDING
) {
248 write_callback_
= callback
;
249 write_buffer_len_
= buf_len
;
254 // Since a SPDY Data frame can only include kMaxSpdyFrameChunkSize bytes
255 // we need to send multiple data frames
256 for (int i
= 0; i
< buf_len
; i
+= kMaxSpdyFrameChunkSize
) {
257 int len
= std::min(kMaxSpdyFrameChunkSize
, buf_len
- i
);
258 scoped_refptr
<DrainableIOBuffer
> iobuf(new DrainableIOBuffer(buf
, i
+ len
));
260 int rv
= spdy_stream_
->WriteStreamData(iobuf
, len
, DATA_FLAG_NONE
);
262 write_bytes_outstanding_
-= rv
;
263 } else if (rv
!= ERR_IO_PENDING
) {
267 if (write_bytes_outstanding_
> 0) {
268 write_callback_
= callback
;
269 write_buffer_len_
= buf_len
;
270 return ERR_IO_PENDING
;
276 bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size
) {
277 // Since this StreamSocket sits on top of a shared SpdySession, it
278 // is not safe for callers to set change this underlying socket.
282 bool SpdyProxyClientSocket::SetSendBufferSize(int32 size
) {
283 // Since this StreamSocket sits on top of a shared SpdySession, it
284 // is not safe for callers to set change this underlying socket.
288 int SpdyProxyClientSocket::GetPeerAddress(IPEndPoint
* address
) const {
290 return ERR_SOCKET_NOT_CONNECTED
;
291 return spdy_stream_
->GetPeerAddress(address
);
294 int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint
* address
) const {
296 return ERR_SOCKET_NOT_CONNECTED
;
297 return spdy_stream_
->GetLocalAddress(address
);
300 void SpdyProxyClientSocket::LogBlockedTunnelResponse() const {
301 ProxyClientSocket::LogBlockedTunnelResponse(
302 response_
.headers
->response_code(),
304 /* is_https_proxy = */ true);
307 void SpdyProxyClientSocket::OnIOComplete(int result
) {
308 DCHECK_NE(STATE_DISCONNECTED
, next_state_
);
309 int rv
= DoLoop(result
);
310 if (rv
!= ERR_IO_PENDING
) {
311 CompletionCallback c
= read_callback_
;
312 read_callback_
.Reset();
317 int SpdyProxyClientSocket::DoLoop(int last_io_result
) {
318 DCHECK_NE(next_state_
, STATE_DISCONNECTED
);
319 int rv
= last_io_result
;
321 State state
= next_state_
;
322 next_state_
= STATE_DISCONNECTED
;
324 case STATE_GENERATE_AUTH_TOKEN
:
326 rv
= DoGenerateAuthToken();
328 case STATE_GENERATE_AUTH_TOKEN_COMPLETE
:
329 rv
= DoGenerateAuthTokenComplete(rv
);
331 case STATE_SEND_REQUEST
:
334 NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
);
335 rv
= DoSendRequest();
337 case STATE_SEND_REQUEST_COMPLETE
:
338 net_log_
.EndEventWithNetErrorCode(
339 NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
, rv
);
340 rv
= DoSendRequestComplete(rv
);
342 case STATE_READ_REPLY_COMPLETE
:
343 rv
= DoReadReplyComplete(rv
);
344 net_log_
.EndEventWithNetErrorCode(
345 NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS
, rv
);
348 NOTREACHED() << "bad state";
352 } while (rv
!= ERR_IO_PENDING
&& next_state_
!= STATE_DISCONNECTED
&&
353 next_state_
!= STATE_OPEN
);
357 int SpdyProxyClientSocket::DoGenerateAuthToken() {
358 next_state_
= STATE_GENERATE_AUTH_TOKEN_COMPLETE
;
359 return auth_
->MaybeGenerateAuthToken(
361 base::Bind(&SpdyProxyClientSocket::OnIOComplete
, base::Unretained(this)),
365 int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result
) {
366 DCHECK_NE(ERR_IO_PENDING
, result
);
368 next_state_
= STATE_SEND_REQUEST
;
372 int SpdyProxyClientSocket::DoSendRequest() {
373 next_state_
= STATE_SEND_REQUEST_COMPLETE
;
375 // Add Proxy-Authentication header if necessary.
376 HttpRequestHeaders authorization_headers
;
377 if (auth_
->HaveAuth()) {
378 auth_
->AddAuthorizationHeader(&authorization_headers
);
381 std::string request_line
;
382 HttpRequestHeaders request_headers
;
383 BuildTunnelRequest(request_
, authorization_headers
, endpoint_
, &request_line
,
387 NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS
,
388 base::Bind(&HttpRequestHeaders::NetLogCallback
,
389 base::Unretained(&request_headers
),
392 request_
.extra_headers
.MergeFrom(request_headers
);
393 scoped_ptr
<SpdyHeaderBlock
> headers(new SpdyHeaderBlock());
394 CreateSpdyHeadersFromHttpRequest(request_
, request_headers
, headers
.get(),
395 spdy_stream_
->GetProtocolVersion(), true);
396 // Reset the URL to be the endpoint of the connection
397 if (spdy_stream_
->GetProtocolVersion() > 2) {
398 (*headers
)[":path"] = endpoint_
.ToString();
399 headers
->erase(":scheme");
401 (*headers
)["url"] = endpoint_
.ToString();
402 headers
->erase("scheme");
404 spdy_stream_
->set_spdy_headers(headers
.Pass());
406 return spdy_stream_
->SendRequest(true);
409 int SpdyProxyClientSocket::DoSendRequestComplete(int result
) {
413 // Wait for SYN_REPLY frame from the server
414 next_state_
= STATE_READ_REPLY_COMPLETE
;
415 return ERR_IO_PENDING
;
418 int SpdyProxyClientSocket::DoReadReplyComplete(int result
) {
419 // We enter this method directly from DoSendRequestComplete, since
420 // we are notified by a callback when the SYN_REPLY frame arrives
425 // Require the "HTTP/1.x" status line for SSL CONNECT.
426 if (response_
.headers
->GetParsedHttpVersion() < HttpVersion(1, 0))
427 return ERR_TUNNEL_CONNECTION_FAILED
;
430 NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS
,
431 base::Bind(&HttpResponseHeaders::NetLogCallback
, response_
.headers
));
433 switch (response_
.headers
->response_code()) {
435 next_state_
= STATE_OPEN
;
438 case 302: // Found / Moved Temporarily
439 // Try to return a sanitized response so we can follow auth redirects.
440 // If we can't, fail the tunnel connection.
441 if (SanitizeProxyRedirect(&response_
, request_
.url
)) {
442 // Immediately hand off our SpdyStream to a newly created
443 // SpdyHttpStream so that any subsequent SpdyFrames are processed in
444 // the context of the HttpStream, not the socket.
445 DCHECK(spdy_stream_
);
446 SpdyStream
* stream
= spdy_stream_
;
448 response_stream_
.reset(new SpdyHttpStream(NULL
, false));
449 response_stream_
->InitializeWithExistingStream(stream
);
450 next_state_
= STATE_DISCONNECTED
;
451 return ERR_HTTPS_PROXY_TUNNEL_RESPONSE
;
453 LogBlockedTunnelResponse();
454 return ERR_TUNNEL_CONNECTION_FAILED
;
457 case 407: // Proxy Authentication Required
458 next_state_
= STATE_OPEN
;
459 return HandleProxyAuthChallenge(auth_
, &response_
, net_log_
);
462 // Ignore response to avoid letting the proxy impersonate the target
463 // server. (See http://crbug.com/137891.)
464 LogBlockedTunnelResponse();
465 return ERR_TUNNEL_CONNECTION_FAILED
;
469 // SpdyStream::Delegate methods:
470 // Called when SYN frame has been sent.
471 // Returns true if no more data to be sent after SYN frame.
472 bool SpdyProxyClientSocket::OnSendHeadersComplete(int status
) {
473 DCHECK_EQ(next_state_
, STATE_SEND_REQUEST_COMPLETE
);
475 OnIOComplete(status
);
477 // We return true here so that we send |spdy_stream_| into
478 // STATE_OPEN (ala WebSockets).
482 int SpdyProxyClientSocket::OnSendBody() {
483 // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
484 // OnSendBody() should never be called.
486 return ERR_UNEXPECTED
;
489 int SpdyProxyClientSocket::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
490 // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
491 // OnSendBodyComplete() should never be called.
493 return ERR_UNEXPECTED
;
496 int SpdyProxyClientSocket::OnResponseReceived(
497 const SpdyHeaderBlock
& response
,
498 base::Time response_time
,
500 // If we've already received the reply, existing headers are too late.
501 // TODO(mbelshe): figure out a way to make HEADERS frames useful after the
503 if (next_state_
!= STATE_READ_REPLY_COMPLETE
)
507 if (!SpdyHeadersToHttpResponse(
508 response
, spdy_stream_
->GetProtocolVersion(), &response_
))
509 return ERR_INCOMPLETE_SPDY_HEADERS
;
511 OnIOComplete(status
);
515 void SpdyProxyClientSocket::OnHeadersSent() {
516 // Proxy client sockets don't send any HEADERS frame.
520 // Called when data is received.
521 int SpdyProxyClientSocket::OnDataReceived(const char* data
, int length
) {
523 // Save the received data.
524 scoped_refptr
<IOBuffer
> io_buffer(new IOBuffer(length
));
525 memcpy(io_buffer
->data(), data
, length
);
526 read_buffer_
.push_back(
527 make_scoped_refptr(new DrainableIOBuffer(io_buffer
, length
)));
530 if (!read_callback_
.is_null()) {
531 int rv
= PopulateUserReadBuffer();
532 CompletionCallback c
= read_callback_
;
533 read_callback_
.Reset();
540 void SpdyProxyClientSocket::OnDataSent(int length
) {
541 DCHECK(!write_callback_
.is_null());
543 write_bytes_outstanding_
-= length
;
545 DCHECK_GE(write_bytes_outstanding_
, 0);
547 if (write_bytes_outstanding_
== 0) {
548 int rv
= write_buffer_len_
;
549 write_buffer_len_
= 0;
550 write_bytes_outstanding_
= 0;
551 CompletionCallback c
= write_callback_
;
552 write_callback_
.Reset();
557 void SpdyProxyClientSocket::OnClose(int status
) {
558 DCHECK(spdy_stream_
);
559 was_ever_used_
= spdy_stream_
->WasEverUsed();
562 bool connecting
= next_state_
!= STATE_DISCONNECTED
&&
563 next_state_
< STATE_OPEN
;
564 if (next_state_
== STATE_OPEN
)
565 next_state_
= STATE_CLOSED
;
567 next_state_
= STATE_DISCONNECTED
;
569 base::WeakPtr
<SpdyProxyClientSocket
> weak_ptr
= weak_factory_
.GetWeakPtr();
570 CompletionCallback write_callback
= write_callback_
;
571 write_callback_
.Reset();
572 write_buffer_len_
= 0;
573 write_bytes_outstanding_
= 0;
575 // If we're in the middle of connecting, we need to make sure
576 // we invoke the connect callback.
578 DCHECK(!read_callback_
.is_null());
579 CompletionCallback read_callback
= read_callback_
;
580 read_callback_
.Reset();
581 read_callback
.Run(status
);
582 } else if (!read_callback_
.is_null()) {
583 // If we have a read_callback_, the we need to make sure we call it back.
584 OnDataReceived(NULL
, 0);
586 // This may have been deleted by read_callback_, so check first.
587 if (weak_ptr
&& !write_callback
.is_null())
588 write_callback
.Run(ERR_CONNECTION_CLOSED
);