| Index: net/websockets/websocket_job.cc
|
| diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3ba0c36d6eb8b8fb9caa87f53a20f3f26861ca8a
|
| --- /dev/null
|
| +++ b/net/websockets/websocket_job.cc
|
| @@ -0,0 +1,378 @@
|
| +// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "net/websockets/websocket_job.h"
|
| +
|
| +#include "googleurl/src/gurl.h"
|
| +#include "net/base/net_errors.h"
|
| +#include "net/base/cookie_policy.h"
|
| +#include "net/base/cookie_store.h"
|
| +#include "net/http/http_util.h"
|
| +#include "net/url_request/url_request_context.h"
|
| +
|
| +namespace net {
|
| +
|
| +// lower-case header names.
|
| +static const char* const kCookieHeaders[] = {
|
| + "cookie", "cookie2"
|
| +};
|
| +static const char* const kSetCookieHeaders[] = {
|
| + "set-cookie", "set-cookie2"
|
| +};
|
| +
|
| +static SocketStreamJob* WebSocketJobFactory(
|
| + const GURL& url, SocketStream::Delegate* delegate) {
|
| + WebSocketJob* job = new WebSocketJob(delegate);
|
| + job->InitSocketStream(new SocketStream(url, job));
|
| + return job;
|
| +}
|
| +
|
| +class WebSocketJobInitSingleton {
|
| + private:
|
| + friend struct DefaultSingletonTraits<WebSocketJobInitSingleton>;
|
| + WebSocketJobInitSingleton() {
|
| + SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory);
|
| + SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory);
|
| + }
|
| +};
|
| +
|
| +static void ParseHandshakeMessage(
|
| + const char* handshake_message, int len,
|
| + std::string* status_line,
|
| + std::string* header) {
|
| + size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n");
|
| + if (i == base::StringPiece::npos) {
|
| + *status_line = std::string(handshake_message, len);
|
| + *header = "";
|
| + return;
|
| + }
|
| + *status_line = std::string(handshake_message, i + 2);
|
| + *header = std::string(handshake_message + i + 2, len - i - 2);
|
| +}
|
| +
|
| +static void FetchResponseCookies(
|
| + const char* handshake_message, int len,
|
| + std::vector<std::string>* response_cookies) {
|
| + std::string handshake_response(handshake_message, len);
|
| + HttpUtil::HeadersIterator iter(handshake_response.begin(),
|
| + handshake_response.end(), "\r\n");
|
| + while (iter.GetNext()) {
|
| + for (size_t i = 0; i < arraysize(kSetCookieHeaders); i++) {
|
| + if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
|
| + kSetCookieHeaders[i])) {
|
| + response_cookies->push_back(iter.values());
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +// static
|
| +void WebSocketJob::EnsureInit() {
|
| + Singleton<WebSocketJobInitSingleton>::get();
|
| +}
|
| +
|
| +WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate)
|
| + : delegate_(delegate),
|
| + state_(INITIALIZED),
|
| + handshake_request_sent_(0),
|
| + handshake_response_header_length_(0),
|
| + response_cookies_save_index_(0),
|
| + ALLOW_THIS_IN_INITIALIZER_LIST(can_get_cookies_callback_(
|
| + this, &WebSocketJob::OnCanGetCookiesCompleted)),
|
| + ALLOW_THIS_IN_INITIALIZER_LIST(can_set_cookie_callback_(
|
| + this, &WebSocketJob::OnCanSetCookieCompleted)) {
|
| +}
|
| +
|
| +WebSocketJob::~WebSocketJob() {
|
| + DCHECK_EQ(CLOSED, state_);
|
| + DCHECK(!delegate_);
|
| + DCHECK(!socket_.get());
|
| +}
|
| +
|
| +void WebSocketJob::Connect() {
|
| + DCHECK(socket_.get());
|
| + DCHECK_EQ(state_, INITIALIZED);
|
| + state_ = CONNECTING;
|
| + socket_->Connect();
|
| +}
|
| +
|
| +bool WebSocketJob::SendData(const char* data, int len) {
|
| + switch (state_) {
|
| + case INITIALIZED:
|
| + return false;
|
| +
|
| + case CONNECTING:
|
| + return SendHandshakeRequest(data, len);
|
| +
|
| + case OPEN:
|
| + return socket_->SendData(data, len);
|
| +
|
| + case CLOSED:
|
| + return false;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +void WebSocketJob::Close() {
|
| + state_ = CLOSED;
|
| + socket_->Close();
|
| +}
|
| +
|
| +void WebSocketJob::RestartWithAuth(
|
| + const std::wstring& username,
|
| + const std::wstring& password) {
|
| + state_ = CONNECTING;
|
| + socket_->RestartWithAuth(username, password);
|
| +}
|
| +
|
| +void WebSocketJob::DetachDelegate() {
|
| + state_ = CLOSED;
|
| + delegate_ = NULL;
|
| + socket_->DetachDelegate();
|
| + socket_ = NULL;
|
| +}
|
| +
|
| +void WebSocketJob::OnConnected(
|
| + SocketStream* socket, int max_pending_send_allowed) {
|
| + if (delegate_)
|
| + delegate_->OnConnected(socket, max_pending_send_allowed);
|
| +}
|
| +
|
| +void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) {
|
| + if (state_ == CONNECTING) {
|
| + OnSentHandshakeRequest(socket, amount_sent);
|
| + return;
|
| + }
|
| + if (delegate_)
|
| + delegate_->OnSentData(socket, amount_sent);
|
| +}
|
| +
|
| +void WebSocketJob::OnReceivedData(
|
| + SocketStream* socket, const char* data, int len) {
|
| + if (state_ == CONNECTING) {
|
| + OnReceivedHandshakeResponse(socket, data, len);
|
| + return;
|
| + }
|
| + if (delegate_)
|
| + delegate_->OnReceivedData(socket, data, len);
|
| +}
|
| +
|
| +void WebSocketJob::OnClose(SocketStream* socket) {
|
| + state_ = CLOSED;
|
| + SocketStream::Delegate* delegate = delegate_;
|
| + delegate_ = NULL;
|
| + socket_ = NULL;
|
| + if (delegate)
|
| + delegate->OnClose(socket);
|
| +}
|
| +
|
| +void WebSocketJob::OnAuthRequired(
|
| + SocketStream* socket, AuthChallengeInfo* auth_info) {
|
| + if (delegate_)
|
| + delegate_->OnAuthRequired(socket, auth_info);
|
| +}
|
| +
|
| +void WebSocketJob::OnError(const SocketStream* socket, int error) {
|
| + if (delegate_)
|
| + delegate_->OnError(socket, error);
|
| +}
|
| +
|
| +bool WebSocketJob::SendHandshakeRequest(const char* data, int len) {
|
| + DCHECK_EQ(state_, CONNECTING);
|
| + if (!handshake_request_.empty()) {
|
| + // if we're already sending handshake message, don't send any more data
|
| + // until handshake is completed.
|
| + return false;
|
| + }
|
| + original_handshake_request_.append(data, len);
|
| + original_handshake_request_header_length_ =
|
| + HttpUtil::LocateEndOfHeaders(original_handshake_request_.data(),
|
| + original_handshake_request_.size(), 0);
|
| + if (original_handshake_request_header_length_ > 0) {
|
| + // handshake message is completed.
|
| + AddCookieHeaderAndSend();
|
| + }
|
| + // Just buffered in original_handshake_request_.
|
| + return true;
|
| +}
|
| +
|
| +void WebSocketJob::AddCookieHeaderAndSend() {
|
| + AddRef(); // Balanced in OnCanGetCookiesCompleted
|
| +
|
| + int policy = OK;
|
| + if (socket_->context()->cookie_policy()) {
|
| + GURL url_for_cookies = GetURLForCookies();
|
| + policy = socket_->context()->cookie_policy()->CanGetCookies(
|
| + url_for_cookies,
|
| + url_for_cookies,
|
| + &can_get_cookies_callback_);
|
| + if (policy == ERR_IO_PENDING)
|
| + return; // Wait for completion callback
|
| + }
|
| + OnCanGetCookiesCompleted(policy);
|
| +}
|
| +
|
| +void WebSocketJob::OnCanGetCookiesCompleted(int policy) {
|
| + if (socket_ && delegate_ && state_ == CONNECTING) {
|
| + std::string handshake_request_status_line;
|
| + std::string handshake_request_header;
|
| + ParseHandshakeMessage(original_handshake_request_.data(),
|
| + original_handshake_request_header_length_,
|
| + &handshake_request_status_line,
|
| + &handshake_request_header);
|
| +
|
| + // Remove cookie headers.
|
| + handshake_request_header = HttpUtil::StripHeaders(
|
| + handshake_request_header,
|
| + kCookieHeaders, arraysize(kCookieHeaders));
|
| +
|
| + if (policy == OK) {
|
| + // Add cookies, including HttpOnly cookies.
|
| + if (socket_->context()->cookie_store()) {
|
| + CookieOptions cookie_options;
|
| + cookie_options.set_include_httponly();
|
| + std::string cookie =
|
| + socket_->context()->cookie_store()->GetCookiesWithOptions(
|
| + GetURLForCookies(), cookie_options);
|
| + if (!cookie.empty()) {
|
| + HttpUtil::AppendHeaderIfMissing("Cookie", cookie,
|
| + &handshake_request_header);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Simply ignore rest data in original request header after
|
| + // original_handshake_request_header_length_, because websocket protocol
|
| + // doesn't allow sending message before handshake is completed.
|
| + // TODO(ukai): report as error?
|
| + handshake_request_ =
|
| + handshake_request_status_line + handshake_request_header + "\r\n";
|
| +
|
| + handshake_request_sent_ = 0;
|
| + socket_->SendData(handshake_request_.data(),
|
| + handshake_request_.size());
|
| + }
|
| + Release(); // Balance AddRef taken in AddCookieHeaderAndSend
|
| +}
|
| +
|
| +void WebSocketJob::OnSentHandshakeRequest(
|
| + SocketStream* socket, int amount_sent) {
|
| + DCHECK_EQ(state_, CONNECTING);
|
| + handshake_request_sent_ += amount_sent;
|
| + if (handshake_request_sent_ >= handshake_request_.size()) {
|
| + // handshake request has been sent.
|
| + // notify original size of handshake request to delegate.
|
| + if (delegate_)
|
| + delegate_->OnSentData(socket, original_handshake_request_.size());
|
| + }
|
| +}
|
| +
|
| +void WebSocketJob::OnReceivedHandshakeResponse(
|
| + SocketStream* socket, const char* data, int len) {
|
| + DCHECK_EQ(state_, CONNECTING);
|
| + handshake_response_.append(data, len);
|
| + handshake_response_header_length_ = HttpUtil::LocateEndOfHeaders(
|
| + handshake_response_.data(),
|
| + handshake_response_.size(), 0);
|
| + if (handshake_response_header_length_ > 0) {
|
| + // handshake message is completed.
|
| + SaveCookiesAndNotifyHeaderComplete();
|
| + }
|
| +}
|
| +
|
| +void WebSocketJob::SaveCookiesAndNotifyHeaderComplete() {
|
| + // handshake message is completed.
|
| + DCHECK(handshake_response_.data());
|
| + DCHECK_GT(handshake_response_header_length_, 0);
|
| +
|
| + response_cookies_.clear();
|
| + response_cookies_save_index_ = 0;
|
| +
|
| + FetchResponseCookies(handshake_response_.data(),
|
| + handshake_response_header_length_,
|
| + &response_cookies_);
|
| +
|
| + // Now, loop over the response cookies, and attempt to persist each.
|
| + SaveNextCookie();
|
| +}
|
| +
|
| +void WebSocketJob::SaveNextCookie() {
|
| + if (response_cookies_save_index_ == response_cookies_.size()) {
|
| + response_cookies_.clear();
|
| + response_cookies_save_index_ = 0;
|
| +
|
| + std::string handshake_response_status_line;
|
| + std::string handshake_response_header;
|
| + ParseHandshakeMessage(handshake_response_.data(),
|
| + handshake_response_header_length_,
|
| + &handshake_response_status_line,
|
| + &handshake_response_header);
|
| + // Remove cookie headers.
|
| + std::string filtered_handshake_response_header =
|
| + HttpUtil::StripHeaders(
|
| + handshake_response_header,
|
| + kSetCookieHeaders, arraysize(kSetCookieHeaders));
|
| + std::string remaining_data =
|
| + std::string(handshake_response_.data() +
|
| + handshake_response_header_length_,
|
| + handshake_response_.size() -
|
| + handshake_response_header_length_);
|
| + std::string received_data =
|
| + handshake_response_status_line +
|
| + filtered_handshake_response_header +
|
| + "\r\n" +
|
| + remaining_data;
|
| + state_ = OPEN;
|
| + if (delegate_)
|
| + delegate_->OnReceivedData(socket_,
|
| + received_data.data(), received_data.size());
|
| + return;
|
| + }
|
| +
|
| + AddRef(); // Balanced in OnCanSetCookieCompleted
|
| +
|
| + int policy = OK;
|
| + if (socket_->context()->cookie_policy()) {
|
| + GURL url_for_cookies = GetURLForCookies();
|
| + policy = socket_->context()->cookie_policy()->CanSetCookie(
|
| + url_for_cookies,
|
| + url_for_cookies,
|
| + response_cookies_[response_cookies_save_index_],
|
| + &can_set_cookie_callback_);
|
| + if (policy == ERR_IO_PENDING)
|
| + return; // Wait for completion callback
|
| + }
|
| +
|
| + OnCanSetCookieCompleted(policy);
|
| +}
|
| +
|
| +void WebSocketJob::OnCanSetCookieCompleted(int policy) {
|
| + if (socket_ && delegate_ && state_ == CONNECTING) {
|
| + if ((policy == OK || policy == OK_FOR_SESSION_ONLY) &&
|
| + socket_->context()->cookie_store()) {
|
| + CookieOptions options;
|
| + options.set_include_httponly();
|
| + if (policy == OK_FOR_SESSION_ONLY)
|
| + options.set_force_session();
|
| + GURL url_for_cookies = GetURLForCookies();
|
| + socket_->context()->cookie_store()->SetCookieWithOptions(
|
| + url_for_cookies, response_cookies_[response_cookies_save_index_],
|
| + options);
|
| + }
|
| + response_cookies_save_index_++;
|
| + SaveNextCookie();
|
| + }
|
| + Release(); // Balance AddRef taken in SaveNextCookie
|
| +}
|
| +
|
| +GURL WebSocketJob::GetURLForCookies() const {
|
| + GURL url = socket_->url();
|
| + std::string scheme = socket_->is_secure() ? "https" : "http";
|
| + url_canon::Replacements<char> replacements;
|
| + replacements.SetScheme(scheme.c_str(),
|
| + url_parse::Component(0, scheme.length()));
|
| + return url.ReplaceComponents(replacements);
|
| +}
|
| +
|
| +} // namespace net
|
|
|