OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 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/websockets/websocket_job.h" |
| 6 |
| 7 #include "googleurl/src/gurl.h" |
| 8 #include "net/base/net_errors.h" |
| 9 #include "net/base/cookie_policy.h" |
| 10 #include "net/base/cookie_store.h" |
| 11 #include "net/http/http_util.h" |
| 12 #include "net/url_request/url_request_context.h" |
| 13 |
| 14 namespace net { |
| 15 |
| 16 // lower-case header names. |
| 17 static const char* const kCookieHeaders[] = { |
| 18 "cookie", "cookie2" |
| 19 }; |
| 20 static const char* const kSetCookieHeaders[] = { |
| 21 "set-cookie", "set-cookie2" |
| 22 }; |
| 23 |
| 24 static SocketStreamJob* WebSocketJobFactory( |
| 25 const GURL& url, SocketStream::Delegate* delegate) { |
| 26 WebSocketJob* job = new WebSocketJob(delegate); |
| 27 job->InitSocketStream(new SocketStream(url, job)); |
| 28 return job; |
| 29 } |
| 30 |
| 31 class WebSocketJobInitSingleton { |
| 32 private: |
| 33 friend struct DefaultSingletonTraits<WebSocketJobInitSingleton>; |
| 34 WebSocketJobInitSingleton() { |
| 35 SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory); |
| 36 SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory); |
| 37 } |
| 38 }; |
| 39 |
| 40 static void ParseHandshakeMessage( |
| 41 const char* handshake_message, int len, |
| 42 std::string* status_line, |
| 43 std::string* header) { |
| 44 size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n"); |
| 45 if (i == base::StringPiece::npos) { |
| 46 *status_line = std::string(handshake_message, len); |
| 47 *header = ""; |
| 48 return; |
| 49 } |
| 50 *status_line = std::string(handshake_message, i + 2); |
| 51 *header = std::string(handshake_message + i + 2, len - i - 2); |
| 52 } |
| 53 |
| 54 static void FetchResponseCookies( |
| 55 const char* handshake_message, int len, |
| 56 std::vector<std::string>* response_cookies) { |
| 57 std::string handshake_response(handshake_message, len); |
| 58 HttpUtil::HeadersIterator iter(handshake_response.begin(), |
| 59 handshake_response.end(), "\r\n"); |
| 60 while (iter.GetNext()) { |
| 61 for (size_t i = 0; i < arraysize(kSetCookieHeaders); i++) { |
| 62 if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), |
| 63 kSetCookieHeaders[i])) { |
| 64 response_cookies->push_back(iter.values()); |
| 65 } |
| 66 } |
| 67 } |
| 68 } |
| 69 |
| 70 // static |
| 71 void WebSocketJob::EnsureInit() { |
| 72 Singleton<WebSocketJobInitSingleton>::get(); |
| 73 } |
| 74 |
| 75 WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate) |
| 76 : delegate_(delegate), |
| 77 state_(INITIALIZED), |
| 78 handshake_request_sent_(0), |
| 79 handshake_response_header_length_(0), |
| 80 response_cookies_save_index_(0), |
| 81 ALLOW_THIS_IN_INITIALIZER_LIST(can_get_cookies_callback_( |
| 82 this, &WebSocketJob::OnCanGetCookiesCompleted)), |
| 83 ALLOW_THIS_IN_INITIALIZER_LIST(can_set_cookie_callback_( |
| 84 this, &WebSocketJob::OnCanSetCookieCompleted)) { |
| 85 } |
| 86 |
| 87 WebSocketJob::~WebSocketJob() { |
| 88 DCHECK_EQ(CLOSED, state_); |
| 89 DCHECK(!delegate_); |
| 90 DCHECK(!socket_.get()); |
| 91 } |
| 92 |
| 93 void WebSocketJob::Connect() { |
| 94 DCHECK(socket_.get()); |
| 95 DCHECK_EQ(state_, INITIALIZED); |
| 96 state_ = CONNECTING; |
| 97 socket_->Connect(); |
| 98 } |
| 99 |
| 100 bool WebSocketJob::SendData(const char* data, int len) { |
| 101 switch (state_) { |
| 102 case INITIALIZED: |
| 103 return false; |
| 104 |
| 105 case CONNECTING: |
| 106 return SendHandshakeRequest(data, len); |
| 107 |
| 108 case OPEN: |
| 109 return socket_->SendData(data, len); |
| 110 |
| 111 case CLOSED: |
| 112 return false; |
| 113 } |
| 114 return false; |
| 115 } |
| 116 |
| 117 void WebSocketJob::Close() { |
| 118 state_ = CLOSED; |
| 119 socket_->Close(); |
| 120 } |
| 121 |
| 122 void WebSocketJob::RestartWithAuth( |
| 123 const std::wstring& username, |
| 124 const std::wstring& password) { |
| 125 state_ = CONNECTING; |
| 126 socket_->RestartWithAuth(username, password); |
| 127 } |
| 128 |
| 129 void WebSocketJob::DetachDelegate() { |
| 130 state_ = CLOSED; |
| 131 delegate_ = NULL; |
| 132 socket_->DetachDelegate(); |
| 133 socket_ = NULL; |
| 134 } |
| 135 |
| 136 void WebSocketJob::OnConnected( |
| 137 SocketStream* socket, int max_pending_send_allowed) { |
| 138 if (delegate_) |
| 139 delegate_->OnConnected(socket, max_pending_send_allowed); |
| 140 } |
| 141 |
| 142 void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) { |
| 143 if (state_ == CONNECTING) { |
| 144 OnSentHandshakeRequest(socket, amount_sent); |
| 145 return; |
| 146 } |
| 147 if (delegate_) |
| 148 delegate_->OnSentData(socket, amount_sent); |
| 149 } |
| 150 |
| 151 void WebSocketJob::OnReceivedData( |
| 152 SocketStream* socket, const char* data, int len) { |
| 153 if (state_ == CONNECTING) { |
| 154 OnReceivedHandshakeResponse(socket, data, len); |
| 155 return; |
| 156 } |
| 157 if (delegate_) |
| 158 delegate_->OnReceivedData(socket, data, len); |
| 159 } |
| 160 |
| 161 void WebSocketJob::OnClose(SocketStream* socket) { |
| 162 state_ = CLOSED; |
| 163 SocketStream::Delegate* delegate = delegate_; |
| 164 delegate_ = NULL; |
| 165 socket_ = NULL; |
| 166 if (delegate) |
| 167 delegate->OnClose(socket); |
| 168 } |
| 169 |
| 170 void WebSocketJob::OnAuthRequired( |
| 171 SocketStream* socket, AuthChallengeInfo* auth_info) { |
| 172 if (delegate_) |
| 173 delegate_->OnAuthRequired(socket, auth_info); |
| 174 } |
| 175 |
| 176 void WebSocketJob::OnError(const SocketStream* socket, int error) { |
| 177 if (delegate_) |
| 178 delegate_->OnError(socket, error); |
| 179 } |
| 180 |
| 181 bool WebSocketJob::SendHandshakeRequest(const char* data, int len) { |
| 182 DCHECK_EQ(state_, CONNECTING); |
| 183 if (!handshake_request_.empty()) { |
| 184 // if we're already sending handshake message, don't send any more data |
| 185 // until handshake is completed. |
| 186 return false; |
| 187 } |
| 188 original_handshake_request_.append(data, len); |
| 189 original_handshake_request_header_length_ = |
| 190 HttpUtil::LocateEndOfHeaders(original_handshake_request_.data(), |
| 191 original_handshake_request_.size(), 0); |
| 192 if (original_handshake_request_header_length_ > 0) { |
| 193 // handshake message is completed. |
| 194 AddCookieHeaderAndSend(); |
| 195 } |
| 196 // Just buffered in original_handshake_request_. |
| 197 return true; |
| 198 } |
| 199 |
| 200 void WebSocketJob::AddCookieHeaderAndSend() { |
| 201 AddRef(); // Balanced in OnCanGetCookiesCompleted |
| 202 |
| 203 int policy = OK; |
| 204 if (socket_->context()->cookie_policy()) { |
| 205 GURL url_for_cookies = GetURLForCookies(); |
| 206 policy = socket_->context()->cookie_policy()->CanGetCookies( |
| 207 url_for_cookies, |
| 208 url_for_cookies, |
| 209 &can_get_cookies_callback_); |
| 210 if (policy == ERR_IO_PENDING) |
| 211 return; // Wait for completion callback |
| 212 } |
| 213 OnCanGetCookiesCompleted(policy); |
| 214 } |
| 215 |
| 216 void WebSocketJob::OnCanGetCookiesCompleted(int policy) { |
| 217 if (socket_ && delegate_ && state_ == CONNECTING) { |
| 218 std::string handshake_request_status_line; |
| 219 std::string handshake_request_header; |
| 220 ParseHandshakeMessage(original_handshake_request_.data(), |
| 221 original_handshake_request_header_length_, |
| 222 &handshake_request_status_line, |
| 223 &handshake_request_header); |
| 224 |
| 225 // Remove cookie headers. |
| 226 handshake_request_header = HttpUtil::StripHeaders( |
| 227 handshake_request_header, |
| 228 kCookieHeaders, arraysize(kCookieHeaders)); |
| 229 |
| 230 if (policy == OK) { |
| 231 // Add cookies, including HttpOnly cookies. |
| 232 if (socket_->context()->cookie_store()) { |
| 233 CookieOptions cookie_options; |
| 234 cookie_options.set_include_httponly(); |
| 235 std::string cookie = |
| 236 socket_->context()->cookie_store()->GetCookiesWithOptions( |
| 237 GetURLForCookies(), cookie_options); |
| 238 if (!cookie.empty()) { |
| 239 HttpUtil::AppendHeaderIfMissing("Cookie", cookie, |
| 240 &handshake_request_header); |
| 241 } |
| 242 } |
| 243 } |
| 244 |
| 245 // Simply ignore rest data in original request header after |
| 246 // original_handshake_request_header_length_, because websocket protocol |
| 247 // doesn't allow sending message before handshake is completed. |
| 248 // TODO(ukai): report as error? |
| 249 handshake_request_ = |
| 250 handshake_request_status_line + handshake_request_header + "\r\n"; |
| 251 |
| 252 handshake_request_sent_ = 0; |
| 253 socket_->SendData(handshake_request_.data(), |
| 254 handshake_request_.size()); |
| 255 } |
| 256 Release(); // Balance AddRef taken in AddCookieHeaderAndSend |
| 257 } |
| 258 |
| 259 void WebSocketJob::OnSentHandshakeRequest( |
| 260 SocketStream* socket, int amount_sent) { |
| 261 DCHECK_EQ(state_, CONNECTING); |
| 262 handshake_request_sent_ += amount_sent; |
| 263 if (handshake_request_sent_ >= handshake_request_.size()) { |
| 264 // handshake request has been sent. |
| 265 // notify original size of handshake request to delegate. |
| 266 if (delegate_) |
| 267 delegate_->OnSentData(socket, original_handshake_request_.size()); |
| 268 } |
| 269 } |
| 270 |
| 271 void WebSocketJob::OnReceivedHandshakeResponse( |
| 272 SocketStream* socket, const char* data, int len) { |
| 273 DCHECK_EQ(state_, CONNECTING); |
| 274 handshake_response_.append(data, len); |
| 275 handshake_response_header_length_ = HttpUtil::LocateEndOfHeaders( |
| 276 handshake_response_.data(), |
| 277 handshake_response_.size(), 0); |
| 278 if (handshake_response_header_length_ > 0) { |
| 279 // handshake message is completed. |
| 280 SaveCookiesAndNotifyHeaderComplete(); |
| 281 } |
| 282 } |
| 283 |
| 284 void WebSocketJob::SaveCookiesAndNotifyHeaderComplete() { |
| 285 // handshake message is completed. |
| 286 DCHECK(handshake_response_.data()); |
| 287 DCHECK_GT(handshake_response_header_length_, 0); |
| 288 |
| 289 response_cookies_.clear(); |
| 290 response_cookies_save_index_ = 0; |
| 291 |
| 292 FetchResponseCookies(handshake_response_.data(), |
| 293 handshake_response_header_length_, |
| 294 &response_cookies_); |
| 295 |
| 296 // Now, loop over the response cookies, and attempt to persist each. |
| 297 SaveNextCookie(); |
| 298 } |
| 299 |
| 300 void WebSocketJob::SaveNextCookie() { |
| 301 if (response_cookies_save_index_ == response_cookies_.size()) { |
| 302 response_cookies_.clear(); |
| 303 response_cookies_save_index_ = 0; |
| 304 |
| 305 std::string handshake_response_status_line; |
| 306 std::string handshake_response_header; |
| 307 ParseHandshakeMessage(handshake_response_.data(), |
| 308 handshake_response_header_length_, |
| 309 &handshake_response_status_line, |
| 310 &handshake_response_header); |
| 311 // Remove cookie headers. |
| 312 std::string filtered_handshake_response_header = |
| 313 HttpUtil::StripHeaders( |
| 314 handshake_response_header, |
| 315 kSetCookieHeaders, arraysize(kSetCookieHeaders)); |
| 316 std::string remaining_data = |
| 317 std::string(handshake_response_.data() + |
| 318 handshake_response_header_length_, |
| 319 handshake_response_.size() - |
| 320 handshake_response_header_length_); |
| 321 std::string received_data = |
| 322 handshake_response_status_line + |
| 323 filtered_handshake_response_header + |
| 324 "\r\n" + |
| 325 remaining_data; |
| 326 state_ = OPEN; |
| 327 if (delegate_) |
| 328 delegate_->OnReceivedData(socket_, |
| 329 received_data.data(), received_data.size()); |
| 330 return; |
| 331 } |
| 332 |
| 333 AddRef(); // Balanced in OnCanSetCookieCompleted |
| 334 |
| 335 int policy = OK; |
| 336 if (socket_->context()->cookie_policy()) { |
| 337 GURL url_for_cookies = GetURLForCookies(); |
| 338 policy = socket_->context()->cookie_policy()->CanSetCookie( |
| 339 url_for_cookies, |
| 340 url_for_cookies, |
| 341 response_cookies_[response_cookies_save_index_], |
| 342 &can_set_cookie_callback_); |
| 343 if (policy == ERR_IO_PENDING) |
| 344 return; // Wait for completion callback |
| 345 } |
| 346 |
| 347 OnCanSetCookieCompleted(policy); |
| 348 } |
| 349 |
| 350 void WebSocketJob::OnCanSetCookieCompleted(int policy) { |
| 351 if (socket_ && delegate_ && state_ == CONNECTING) { |
| 352 if ((policy == OK || policy == OK_FOR_SESSION_ONLY) && |
| 353 socket_->context()->cookie_store()) { |
| 354 CookieOptions options; |
| 355 options.set_include_httponly(); |
| 356 if (policy == OK_FOR_SESSION_ONLY) |
| 357 options.set_force_session(); |
| 358 GURL url_for_cookies = GetURLForCookies(); |
| 359 socket_->context()->cookie_store()->SetCookieWithOptions( |
| 360 url_for_cookies, response_cookies_[response_cookies_save_index_], |
| 361 options); |
| 362 } |
| 363 response_cookies_save_index_++; |
| 364 SaveNextCookie(); |
| 365 } |
| 366 Release(); // Balance AddRef taken in SaveNextCookie |
| 367 } |
| 368 |
| 369 GURL WebSocketJob::GetURLForCookies() const { |
| 370 GURL url = socket_->url(); |
| 371 std::string scheme = socket_->is_secure() ? "https" : "http"; |
| 372 url_canon::Replacements<char> replacements; |
| 373 replacements.SetScheme(scheme.c_str(), |
| 374 url_parse::Component(0, scheme.length())); |
| 375 return url.ReplaceComponents(replacements); |
| 376 } |
| 377 |
| 378 } // namespace net |
OLD | NEW |