| OLD | NEW |
| (Empty) |
| 1 // Copyright 2007-2010 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 // ======================================================================== | |
| 15 | |
| 16 // The implementation does not allow concurrent calls on the same object but | |
| 17 // it allows calling SimpleRequest::Cancel in order to stop an ongoing request. | |
| 18 // The transient state of the request is maintained by request_state_. | |
| 19 // The state is created by SimpleRequest::Send and it is destroyed by | |
| 20 // SimpleRequest::Close. The only concurrent access to the object state can | |
| 21 // happen during calling SimpleRequest::Cancel(), Pause() and Resume(). Cancel | |
| 22 // closes the connection and the request handles. This makes any of the WinHttp | |
| 23 // calls on these handles fail and SimpleRequest::Send return to the caller. | |
| 24 // Pause() also closes the handles but SimpleRequest automatically reopens | |
| 25 // them when Resume() is called. During resume stage, SimpleRequest sends a | |
| 26 // range request to continue download. During these actions, the caller is still | |
| 27 // blocked. | |
| 28 | |
| 29 #include "omaha/net/simple_request.h" | |
| 30 #include <atlconv.h> | |
| 31 #include <climits> | |
| 32 #include <memory> | |
| 33 #include <vector> | |
| 34 #include "omaha/base/const_addresses.h" | |
| 35 #include "omaha/base/constants.h" | |
| 36 #include "omaha/base/debug.h" | |
| 37 #include "omaha/base/error.h" | |
| 38 #include "omaha/base/logging.h" | |
| 39 #include "omaha/base/scoped_any.h" | |
| 40 #include "omaha/base/scope_guard.h" | |
| 41 #include "omaha/base/string.h" | |
| 42 #include "omaha/net/network_config.h" | |
| 43 #include "omaha/net/network_request.h" | |
| 44 #include "omaha/net/proxy_auth.h" | |
| 45 #include "omaha/net/winhttp_adapter.h" | |
| 46 | |
| 47 namespace omaha { | |
| 48 | |
| 49 SimpleRequest::SimpleRequest() | |
| 50 : request_buffer_(NULL), | |
| 51 request_buffer_length_(0), | |
| 52 proxy_auth_config_(NULL, CString()), | |
| 53 is_canceled_(false), | |
| 54 is_closed_(false), | |
| 55 session_handle_(NULL), | |
| 56 low_priority_(false), | |
| 57 callback_(NULL), | |
| 58 download_completed_(false), | |
| 59 pause_happened_(false) { | |
| 60 user_agent_.Format(_T("%s;winhttp"), NetworkConfig::GetUserAgent()); | |
| 61 | |
| 62 // Create a manual reset event to wait on during network transfer. | |
| 63 // The event is signaled by default meaning the network transferring is | |
| 64 // enabled. Pause() resets this event to stop network activity. Resume() | |
| 65 // does the opposite as Pause(). Close() and Cancel() also set the event. | |
| 66 // This unlocks the thread if it is in paused state and returns control | |
| 67 // to the caller. | |
| 68 reset(event_resume_, ::CreateEvent(NULL, true, true, NULL)); | |
| 69 ASSERT1(valid(event_resume_)); | |
| 70 } | |
| 71 | |
| 72 // TODO(omaha): we should attempt to cleanup the file only if we | |
| 73 // created it in the first place. | |
| 74 SimpleRequest::~SimpleRequest() { | |
| 75 Close(); | |
| 76 callback_ = NULL; | |
| 77 | |
| 78 // If download failed, try to clean up the target file. | |
| 79 if (!download_completed_ && !filename_.IsEmpty()) { | |
| 80 if (!::DeleteFile(filename_) && ::GetLastError() != ERROR_FILE_NOT_FOUND) { | |
| 81 NET_LOG(LW, (_T("[SimpleRequest][Failed to delete file: %s][0x%08x]."), | |
| 82 filename_.GetString(), HRESULTFromLastError())); | |
| 83 } | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 void SimpleRequest::set_url(const CString& url) { | |
| 88 __mutexScope(lock_); | |
| 89 if (url_ != url) { | |
| 90 url_ = url; | |
| 91 CloseHandles(); | |
| 92 request_state_.reset(); | |
| 93 } | |
| 94 } | |
| 95 | |
| 96 void SimpleRequest::set_filename(const CString& filename) { | |
| 97 __mutexScope(lock_); | |
| 98 if (filename_ != filename) { | |
| 99 filename_ = filename; | |
| 100 CloseHandles(); | |
| 101 request_state_.reset(); | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 HRESULT SimpleRequest::Close() { | |
| 106 NET_LOG(L3, (_T("[SimpleRequest::Close]"))); | |
| 107 | |
| 108 __mutexScope(lock_); | |
| 109 is_closed_ = true; | |
| 110 CloseHandles(); | |
| 111 request_state_.reset(); | |
| 112 winhttp_adapter_.reset(); | |
| 113 | |
| 114 // Resume the downloading thread if it is blocked. It is still fine if the | |
| 115 // event is set since the operation is like no-op in that case. | |
| 116 return Resume(); | |
| 117 } | |
| 118 | |
| 119 HRESULT SimpleRequest::Cancel() { | |
| 120 NET_LOG(L3, (_T("[SimpleRequest::Cancel]"))); | |
| 121 | |
| 122 __mutexScope(lock_); | |
| 123 is_canceled_ = true; | |
| 124 CloseHandles(); | |
| 125 | |
| 126 // Resume the downloading thread if it is blocked. It is still fine if the | |
| 127 // event is set since the operation is like no-op in that case. | |
| 128 return Resume(); | |
| 129 } | |
| 130 | |
| 131 void SimpleRequest::CloseHandles() { | |
| 132 if (winhttp_adapter_.get()) { | |
| 133 winhttp_adapter_->CloseHandles(); | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 bool SimpleRequest::IsResumeNeeded() const { | |
| 138 __mutexScope(lock_); | |
| 139 if (!IsPauseSupported() || is_canceled_ || is_closed_) { | |
| 140 return false; | |
| 141 } | |
| 142 | |
| 143 return pause_happened_; | |
| 144 } | |
| 145 | |
| 146 bool SimpleRequest::IsPauseSupported() const { | |
| 147 __mutexScope(lock_); | |
| 148 return valid(event_resume_) && !IsPostRequest() && !filename_.IsEmpty(); | |
| 149 } | |
| 150 | |
| 151 HRESULT SimpleRequest::Pause() { | |
| 152 NET_LOG(L3, (_T("[SimpleRequest::Pause]"))); | |
| 153 | |
| 154 if (!IsPauseSupported()) { | |
| 155 return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); | |
| 156 } | |
| 157 | |
| 158 __mutexScope(ready_to_pause_lock_); | |
| 159 __mutexScope(lock_); | |
| 160 | |
| 161 pause_happened_ = true; | |
| 162 CloseHandles(); | |
| 163 return ::ResetEvent(get(event_resume_)) ? S_OK : HRESULTFromLastError(); | |
| 164 } | |
| 165 | |
| 166 HRESULT SimpleRequest::Resume() { | |
| 167 NET_LOG(L3, (_T("[SimpleRequest::Resume]"))); | |
| 168 | |
| 169 __mutexScope(lock_); | |
| 170 HRESULT hr = S_OK; | |
| 171 if (IsPauseSupported()) { | |
| 172 hr = ::SetEvent(get(event_resume_)) ? S_OK : HRESULTFromLastError(); | |
| 173 } | |
| 174 return hr; | |
| 175 } | |
| 176 | |
| 177 void SimpleRequest::WaitForResumeEvent() { | |
| 178 if (!event_resume_) { | |
| 179 return; | |
| 180 } | |
| 181 | |
| 182 // Reset pause_happened_ state to indicate that pause has not yet happened | |
| 183 // during new resume stage. | |
| 184 __mutexBlock(lock_) { | |
| 185 pause_happened_ = false; | |
| 186 } | |
| 187 | |
| 188 VERIFY1(::WaitForSingleObject(get(event_resume_), INFINITE) != WAIT_FAILED); | |
| 189 } | |
| 190 | |
| 191 HRESULT SimpleRequest::Send() { | |
| 192 NET_LOG(L3, (_T("[SimpleRequest::Send][%s]"), url_)); | |
| 193 | |
| 194 ASSERT1(!url_.IsEmpty()); | |
| 195 if (!session_handle_) { | |
| 196 NET_LOG(LE, (_T("[SimpleRequest: session_handle_ is NULL]"))); | |
| 197 return OMAHA_NET_E_WINHTTP_NOT_AVAILABLE; | |
| 198 } | |
| 199 | |
| 200 HRESULT hr = S_OK; | |
| 201 | |
| 202 __mutexBlock(ready_to_pause_lock_) { | |
| 203 __mutexBlock(lock_) { | |
| 204 winhttp_adapter_.reset(new WinHttpAdapter()); | |
| 205 hr = winhttp_adapter_->Initialize(); | |
| 206 if (FAILED(hr)) { | |
| 207 return hr; | |
| 208 } | |
| 209 | |
| 210 if (!IsPauseSupported() || request_state_ == NULL) { | |
| 211 request_state_.reset(new TransientRequestState); | |
| 212 } else { | |
| 213 // Discard all previous download states except content_length and | |
| 214 // current_bytes for resume purpose. These two states will be validated | |
| 215 // against the previously (partially) downloaded file when reopens the | |
| 216 // target file. | |
| 217 scoped_ptr<TransientRequestState> request_state( | |
| 218 new TransientRequestState); | |
| 219 request_state->content_length = request_state_->content_length; | |
| 220 request_state->current_bytes = request_state_->current_bytes; | |
| 221 | |
| 222 request_state_.swap(request_state); | |
| 223 } | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 for (bool first_time = true, cancelled = false; | |
| 228 !cancelled; | |
| 229 first_time = false) { | |
| 230 scoped_hfile file_handle; | |
| 231 | |
| 232 __mutexBlock(ready_to_pause_lock_) { | |
| 233 if (!first_time) { | |
| 234 if (IsResumeNeeded()) { | |
| 235 NET_LOG(L3, (_T("[SimpleRequest::Send paused.]"))); | |
| 236 WaitForResumeEvent(); | |
| 237 NET_LOG(L3, (_T("[SimpleRequest::Send resumed.]"))); | |
| 238 } else { | |
| 239 if (is_canceled_) { | |
| 240 hr = GOOPDATE_E_CANCELLED; | |
| 241 | |
| 242 // Once cancelled, we should set downloaded bytes to 0 | |
| 243 request_state_->current_bytes = 0; | |
| 244 } | |
| 245 | |
| 246 // Macro __mutexBlock has a hidden loop built-in and thus one break | |
| 247 // is not enough to exit the loop. Uses a flag instead. | |
| 248 cancelled = true; | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 if (!cancelled) { | |
| 253 hr = PrepareRequest(address(file_handle)); | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 if (SUCCEEDED(hr) && !cancelled) { | |
| 258 ASSERT1(request_state_->current_bytes <= request_state_->content_length); | |
| 259 | |
| 260 // Only requests data if there is more data to download. | |
| 261 if (request_state_->content_length == 0 || | |
| 262 request_state_->current_bytes < request_state_->content_length) { | |
| 263 hr = RequestData(get(file_handle)); | |
| 264 } | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 NET_LOG(L3, | |
| 269 (_T("[SimpleRequest::Send][0x%08x][%d]"), hr, GetHttpStatusCode())); | |
| 270 return hr; | |
| 271 } | |
| 272 | |
| 273 HRESULT SimpleRequest::Connect() { | |
| 274 HRESULT hr = winhttp_adapter_->CrackUrl(url_, | |
| 275 ICU_DECODE, | |
| 276 &request_state_->scheme, | |
| 277 &request_state_->server, | |
| 278 &request_state_->port, | |
| 279 &request_state_->url_path, | |
| 280 NULL); | |
| 281 if (FAILED(hr)) { | |
| 282 return hr; | |
| 283 } | |
| 284 ASSERT1(!request_state_->scheme.CompareNoCase(kHttpProtoScheme) || | |
| 285 !request_state_->scheme.CompareNoCase(kHttpsProtoScheme)); | |
| 286 | |
| 287 hr = winhttp_adapter_->Connect(session_handle_, | |
| 288 request_state_->server, | |
| 289 request_state_->port); | |
| 290 if (FAILED(hr)) { | |
| 291 return hr; | |
| 292 } | |
| 293 | |
| 294 // TODO(omaha): figure out the accept types. | |
| 295 // figure out more flags. | |
| 296 request_state_->is_https = false; | |
| 297 DWORD flags = WINHTTP_FLAG_REFRESH; | |
| 298 if (request_state_->scheme == kHttpsProtoScheme) { | |
| 299 request_state_->is_https = true; | |
| 300 flags |= WINHTTP_FLAG_SECURE; | |
| 301 } | |
| 302 const TCHAR* verb = IsPostRequest() ? _T("POST") : _T("GET"); | |
| 303 hr = winhttp_adapter_->OpenRequest(verb, request_state_->url_path, | |
| 304 NULL, WINHTTP_NO_REFERER, | |
| 305 WINHTTP_DEFAULT_ACCEPT_TYPES, flags); | |
| 306 if (FAILED(hr)) { | |
| 307 return hr; | |
| 308 } | |
| 309 | |
| 310 // Disable redirects for POST requests. | |
| 311 if (IsPostRequest()) { | |
| 312 VERIFY1(SUCCEEDED( | |
| 313 winhttp_adapter_->SetRequestOptionInt(WINHTTP_OPTION_DISABLE_FEATURE, | |
| 314 WINHTTP_DISABLE_REDIRECTS))); | |
| 315 } | |
| 316 | |
| 317 CString additional_headers = additional_headers_; | |
| 318 | |
| 319 // If the target has been partially downloaded, send a range request to resume | |
| 320 // download, instead of starting from scratch again. | |
| 321 if (request_state_->current_bytes != 0 && | |
| 322 request_state_->current_bytes != request_state_->content_length) { | |
| 323 ASSERT1(request_state_->current_bytes < request_state_->content_length); | |
| 324 additional_headers.AppendFormat(_T("Range: bytes=%d-\r\n"), | |
| 325 request_state_->current_bytes); | |
| 326 } | |
| 327 if (!additional_headers.IsEmpty()) { | |
| 328 uint32 header_flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE; | |
| 329 hr = winhttp_adapter_->AddRequestHeaders(additional_headers, | |
| 330 -1, | |
| 331 header_flags); | |
| 332 if (FAILED(hr)) { | |
| 333 return hr; | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 // If the WPAD detection fails, allow the request to go direct connection. | |
| 338 SetProxyInformation(); | |
| 339 | |
| 340 return S_OK; | |
| 341 } | |
| 342 | |
| 343 HRESULT SimpleRequest::OpenDestinationFile(HANDLE* file_handle) { | |
| 344 ASSERT1(!filename_.IsEmpty()); | |
| 345 ASSERT1(file_handle); | |
| 346 | |
| 347 DWORD create_disposition = request_state_->content_length == 0 ? | |
| 348 CREATE_ALWAYS : OPEN_ALWAYS; | |
| 349 | |
| 350 scoped_hfile file(::CreateFile(filename_, GENERIC_WRITE, 0, NULL, | |
| 351 create_disposition, FILE_ATTRIBUTE_NORMAL, | |
| 352 NULL)); | |
| 353 | |
| 354 if (!file) { | |
| 355 return HRESULTFromLastError(); | |
| 356 } | |
| 357 | |
| 358 if (request_state_->content_length != 0) { | |
| 359 DWORD raw_file_size = ::GetFileSize(get(file), NULL); | |
| 360 if (INVALID_FILE_SIZE == raw_file_size || raw_file_size > INT_MAX) { | |
| 361 return E_FAIL; | |
| 362 } | |
| 363 int file_size = static_cast<int>(raw_file_size); | |
| 364 | |
| 365 // Local file size should not be greater than remote file size and file | |
| 366 // size must match the number of bytes we previously downloaded. If not, | |
| 367 // reset the local file. | |
| 368 bool need_reset_file = file_size > request_state_->content_length; | |
| 369 need_reset_file |= file_size != request_state_->current_bytes; | |
| 370 | |
| 371 if (need_reset_file) { | |
| 372 // Need to download from byte 0. Reopen the file with truncation. | |
| 373 request_state_->current_bytes = 0; | |
| 374 reset(file, ::CreateFile(filename_, GENERIC_WRITE, 0, NULL, | |
| 375 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); | |
| 376 | |
| 377 if (!file) { | |
| 378 return HRESULTFromLastError(); | |
| 379 } | |
| 380 } else { | |
| 381 LARGE_INTEGER start_pos; | |
| 382 start_pos.LowPart = static_cast<DWORD>(request_state_->current_bytes); | |
| 383 start_pos.HighPart = 0; | |
| 384 if (!::SetFilePointerEx(get(file), start_pos, NULL, FILE_BEGIN)) { | |
| 385 return HRESULTFromLastError(); | |
| 386 } | |
| 387 } | |
| 388 } else { | |
| 389 // Always start from byte 0 if we don't know remote file size. | |
| 390 request_state_->current_bytes = 0; | |
| 391 } | |
| 392 | |
| 393 *file_handle = release(file); | |
| 394 return S_OK; | |
| 395 } | |
| 396 | |
| 397 HRESULT SimpleRequest::SendRequest() { | |
| 398 int proxy_retry_count = 0; | |
| 399 int max_proxy_retries = 1; | |
| 400 CString username; | |
| 401 CString password; | |
| 402 HRESULT hr = S_OK; | |
| 403 | |
| 404 bool done = false; | |
| 405 while (!done) { | |
| 406 uint32& request_scheme = request_state_->proxy_authentication_scheme; | |
| 407 if (request_scheme) { | |
| 408 NET_LOG(L3, (_T("[SimpleRequest::SendRequest][auth_scheme][%d]"), | |
| 409 request_scheme)); | |
| 410 winhttp_adapter_->SetCredentials(WINHTTP_AUTH_TARGET_PROXY, | |
| 411 request_scheme, | |
| 412 username, password); | |
| 413 | |
| 414 CString headers; | |
| 415 headers.Format(_T("%s: %d\r\n"), | |
| 416 kHeaderXProxyRetryCount, proxy_retry_count); | |
| 417 if (!username.IsEmpty()) { | |
| 418 headers.AppendFormat(_T("%s: 1\r\n"), kHeaderXProxyManualAuth); | |
| 419 } | |
| 420 uint32 flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE; | |
| 421 VERIFY1(SUCCEEDED(winhttp_adapter_->AddRequestHeaders(headers, | |
| 422 -1, | |
| 423 flags))); | |
| 424 } | |
| 425 | |
| 426 size_t bytes_to_send = request_buffer_length_; | |
| 427 hr = winhttp_adapter_->SendRequest(NULL, | |
| 428 0, | |
| 429 request_buffer_, | |
| 430 bytes_to_send, | |
| 431 bytes_to_send); | |
| 432 if (FAILED(hr)) { | |
| 433 return hr; | |
| 434 } | |
| 435 NET_LOG(L3, | |
| 436 (_T("[SimpleRequest::SendRequest][request sent][server: %s][IP: %s]"), | |
| 437 winhttp_adapter_->server_name(), | |
| 438 winhttp_adapter_->server_ip())); | |
| 439 | |
| 440 hr = winhttp_adapter_->ReceiveResponse(); | |
| 441 #if DEBUG | |
| 442 LogResponseHeaders(); | |
| 443 #endif | |
| 444 if (hr == ERROR_WINHTTP_RESEND_REQUEST) { | |
| 445 // Resend the request if needed, likely because the authentication | |
| 446 // scheme requires many transactions on the same handle. | |
| 447 continue; | |
| 448 } else if (FAILED(hr)) { | |
| 449 return hr; | |
| 450 } | |
| 451 | |
| 452 hr = winhttp_adapter_->QueryRequestHeadersInt( | |
| 453 WINHTTP_QUERY_STATUS_CODE, | |
| 454 NULL, | |
| 455 &request_state_->http_status_code, | |
| 456 NULL); | |
| 457 if (FAILED(hr)) { | |
| 458 return hr; | |
| 459 } | |
| 460 if (request_state_->http_status_code < HTTP_STATUS_FIRST || | |
| 461 request_state_->http_status_code > HTTP_STATUS_LAST) { | |
| 462 return E_FAIL; | |
| 463 } | |
| 464 | |
| 465 NetworkConfigManager& network_manager = NetworkConfigManager::Instance(); | |
| 466 switch (request_state_->http_status_code) { | |
| 467 case HTTP_STATUS_DENIED: | |
| 468 // 401 responses are not supported. Omaha does not have to authenticate | |
| 469 // to our backend. | |
| 470 done = true; | |
| 471 break; | |
| 472 | |
| 473 case HTTP_STATUS_PROXY_AUTH_REQ: { | |
| 474 NET_LOG(L2, (_T("[http proxy requires authentication]"))); | |
| 475 ++proxy_retry_count; | |
| 476 if (proxy_retry_count > max_proxy_retries) { | |
| 477 // If we get multiple 407s in a row then we are done. It does not make | |
| 478 // sense to retry further. | |
| 479 done = true; | |
| 480 break; | |
| 481 } | |
| 482 if (!request_scheme) { | |
| 483 uint32 supported_schemes(0), first_scheme(0), auth_target(0); | |
| 484 hr = winhttp_adapter_->QueryAuthSchemes(&supported_schemes, | |
| 485 &first_scheme, | |
| 486 &auth_target); | |
| 487 if (FAILED(hr)) { | |
| 488 return hr; | |
| 489 } | |
| 490 ASSERT1(auth_target == WINHTTP_AUTH_TARGET_PROXY); | |
| 491 request_scheme = ChooseProxyAuthScheme(supported_schemes); | |
| 492 ASSERT1(request_scheme); | |
| 493 NET_LOG(L3, (_T("[SimpleRequest::SendRequest][Auth scheme][%d]"), | |
| 494 request_scheme)); | |
| 495 if (request_scheme == WINHTTP_AUTH_SCHEME_NEGOTIATE || | |
| 496 request_scheme == WINHTTP_AUTH_SCHEME_NTLM) { | |
| 497 // Increases the retry count. Tries to do an autologon at first, and | |
| 498 // if that fails, will call GetProxyCredentials below. | |
| 499 ++max_proxy_retries; | |
| 500 break; | |
| 501 } | |
| 502 } | |
| 503 | |
| 504 uint32 auth_scheme = UNKNOWN_AUTH_SCHEME; | |
| 505 // May prompt the user for credentials, or get cached credentials. | |
| 506 NetworkConfig* network_config = NULL; | |
| 507 hr = network_manager.GetUserNetworkConfig(&network_config); | |
| 508 if (FAILED(hr)) { | |
| 509 return hr; | |
| 510 } | |
| 511 if (!network_config->GetProxyCredentials(true, | |
| 512 false, | |
| 513 request_state_->proxy, | |
| 514 proxy_auth_config_, | |
| 515 request_state_->is_https, | |
| 516 &username, | |
| 517 &password, | |
| 518 &auth_scheme)) { | |
| 519 NET_LOG(LE, | |
| 520 (_T("[SimpleRequest::SendRequest][GetProxyCreds failed]"))); | |
| 521 done = true; | |
| 522 break; | |
| 523 } | |
| 524 if (auth_scheme != UNKNOWN_AUTH_SCHEME) { | |
| 525 // Uses the known scheme that was successful previously. | |
| 526 request_scheme = auth_scheme; | |
| 527 } | |
| 528 break; | |
| 529 } | |
| 530 | |
| 531 default: | |
| 532 // We got some kind of response. If we have a valid username, we | |
| 533 // record the auth scheme with the NetworkConfig, so it can be cached | |
| 534 // for future use within this process for current user.. | |
| 535 if (!username.IsEmpty()) { | |
| 536 NetworkConfig* network_config = NULL; | |
| 537 hr = network_manager.GetUserNetworkConfig(&network_config); | |
| 538 if (SUCCEEDED(hr)) { | |
| 539 VERIFY1(SUCCEEDED(network_config->SetProxyAuthScheme( | |
| 540 request_state_->proxy, request_state_->is_https, | |
| 541 request_scheme))); | |
| 542 } | |
| 543 } | |
| 544 done = true; | |
| 545 break; | |
| 546 } | |
| 547 } | |
| 548 | |
| 549 return hr; | |
| 550 } | |
| 551 | |
| 552 HRESULT SimpleRequest::ReceiveData(HANDLE file_handle) { | |
| 553 ASSERT1(file_handle != INVALID_HANDLE_VALUE || filename_.IsEmpty()); | |
| 554 | |
| 555 HRESULT hr = S_OK; | |
| 556 | |
| 557 // In the case of a "204 No Content" response, WinHttp blocks when | |
| 558 // querying or reading the available data. According to the RFC, | |
| 559 // the 204 response must not include a message-body, and thus is always | |
| 560 // terminated by the first empty line after the header fields. | |
| 561 // It appears WinHttp does not internally handles the 204 response. If this, | |
| 562 // condition is not handled here explicitly, WinHttp will timeout when | |
| 563 // waiting for the data instead of returning right away. | |
| 564 if (request_state_->http_status_code == HTTP_STATUS_NO_CONTENT) { | |
| 565 return S_OK; | |
| 566 } | |
| 567 | |
| 568 int content_length = 0; | |
| 569 winhttp_adapter_->QueryRequestHeadersInt(WINHTTP_QUERY_CONTENT_LENGTH, | |
| 570 WINHTTP_HEADER_NAME_BY_INDEX, | |
| 571 &content_length, | |
| 572 WINHTTP_NO_HEADER_INDEX); | |
| 573 if (request_state_->content_length == 0) { | |
| 574 request_state_->content_length = content_length; | |
| 575 request_state_->current_bytes = 0; | |
| 576 } | |
| 577 | |
| 578 const bool is_http_success = | |
| 579 request_state_->http_status_code == HTTP_STATUS_OK || | |
| 580 request_state_->http_status_code == HTTP_STATUS_PARTIAL_CONTENT; | |
| 581 | |
| 582 std::vector<uint8> buffer; | |
| 583 do { | |
| 584 DWORD bytes_available(0); | |
| 585 winhttp_adapter_->QueryDataAvailable(&bytes_available); | |
| 586 buffer.resize(1 + bytes_available); | |
| 587 hr = winhttp_adapter_->ReadData(&buffer.front(), | |
| 588 buffer.size(), | |
| 589 &bytes_available); | |
| 590 if (FAILED(hr)) { | |
| 591 return hr; | |
| 592 } | |
| 593 | |
| 594 buffer.resize(bytes_available); | |
| 595 if (!buffer.empty()) { | |
| 596 if (!filename_.IsEmpty()) { | |
| 597 DWORD num_bytes(0); | |
| 598 if (!::WriteFile(file_handle, | |
| 599 reinterpret_cast<const char*>(&buffer.front()), | |
| 600 buffer.size(), &num_bytes, NULL)) { | |
| 601 return HRESULTFromLastError(); | |
| 602 } | |
| 603 ASSERT1(num_bytes == buffer.size()); | |
| 604 } else { | |
| 605 request_state_->response.insert(request_state_->response.end(), | |
| 606 buffer.begin(), | |
| 607 buffer.end()); | |
| 608 } | |
| 609 } | |
| 610 | |
| 611 // Update current_bytes after those bytes are serialized in case we | |
| 612 // pause before current_bytes is updated, we can throw away the last | |
| 613 // batch of bytes received and resume. | |
| 614 request_state_->current_bytes += bytes_available; | |
| 615 if (request_state_->content_length) { | |
| 616 ASSERT1(request_state_->current_bytes <= request_state_->content_length); | |
| 617 } | |
| 618 | |
| 619 // The callback is called only for 200 or 206 http codes. | |
| 620 if (callback_ && request_state_->content_length && is_http_success) { | |
| 621 callback_->OnProgress(request_state_->current_bytes, | |
| 622 request_state_->content_length, | |
| 623 WINHTTP_CALLBACK_STATUS_READ_COMPLETE, | |
| 624 NULL); | |
| 625 } | |
| 626 } while (!buffer.empty()); | |
| 627 | |
| 628 NET_LOG(L3, (_T("[bytes downloaded %d]"), request_state_->current_bytes)); | |
| 629 if (file_handle != INVALID_HANDLE_VALUE) { | |
| 630 // All bytes must be written to the file in the file download case. | |
| 631 ASSERT1(::SetFilePointer(file_handle, 0, NULL, FILE_CURRENT) == | |
| 632 static_cast<DWORD>(request_state_->current_bytes)); | |
| 633 } | |
| 634 | |
| 635 download_completed_ = true; | |
| 636 return hr; | |
| 637 } | |
| 638 | |
| 639 HRESULT SimpleRequest::PrepareRequest(HANDLE* file_handle) { | |
| 640 // Read the remaining bytes of the body. If we have a file to save the | |
| 641 // response into, create the file. | |
| 642 HRESULT hr = S_OK; | |
| 643 if (!filename_.IsEmpty()) { | |
| 644 hr = OpenDestinationFile(file_handle); | |
| 645 if (FAILED(hr)) { | |
| 646 return hr; | |
| 647 } | |
| 648 } else { | |
| 649 // Always restarts if downloading to memory. | |
| 650 request_state_->current_bytes = 0; | |
| 651 request_state_->response.clear(); | |
| 652 } | |
| 653 | |
| 654 return Connect(); | |
| 655 } | |
| 656 | |
| 657 HRESULT SimpleRequest::RequestData(HANDLE file_handle) { | |
| 658 HRESULT hr = SendRequest(); | |
| 659 if (FAILED(hr)) { | |
| 660 return hr; | |
| 661 } | |
| 662 | |
| 663 return ReceiveData(file_handle); | |
| 664 } | |
| 665 | |
| 666 std::vector<uint8> SimpleRequest::GetResponse() const { | |
| 667 return request_state_.get() ? request_state_->response : | |
| 668 std::vector<uint8>(); | |
| 669 } | |
| 670 | |
| 671 HRESULT SimpleRequest::QueryHeadersString(uint32 info_level, | |
| 672 const TCHAR* name, | |
| 673 CString* value) const { | |
| 674 // Name can be null when the info_level specifies the header to query. | |
| 675 if (winhttp_adapter_.get()) { | |
| 676 return winhttp_adapter_->QueryRequestHeadersString(info_level, | |
| 677 name, | |
| 678 value, | |
| 679 WINHTTP_NO_HEADER_INDEX); | |
| 680 } else { | |
| 681 return E_UNEXPECTED; | |
| 682 } | |
| 683 } | |
| 684 | |
| 685 CString SimpleRequest::GetResponseHeaders() const { | |
| 686 CString response_headers; | |
| 687 if (winhttp_adapter_.get()) { | |
| 688 CString response_headers; | |
| 689 if (SUCCEEDED(winhttp_adapter_->QueryRequestHeadersString( | |
| 690 WINHTTP_QUERY_RAW_HEADERS_CRLF, | |
| 691 WINHTTP_HEADER_NAME_BY_INDEX, | |
| 692 &response_headers, | |
| 693 WINHTTP_NO_HEADER_INDEX))) { | |
| 694 return response_headers; | |
| 695 } | |
| 696 } | |
| 697 return CString(); | |
| 698 } | |
| 699 | |
| 700 uint32 SimpleRequest::ChooseProxyAuthScheme(uint32 supported_schemes) { | |
| 701 // It is the server's responsibility only to accept | |
| 702 // authentication schemes that provide a sufficient level | |
| 703 // of security to protect the server's resources. | |
| 704 // | |
| 705 // The client is also obligated only to use an authentication | |
| 706 // scheme that adequately protects its username and password. | |
| 707 // | |
| 708 // TODO(omaha): remove Basic authentication because Basic authentication | |
| 709 // exposes the client's username and password to anyone monitoring | |
| 710 // the connection. This option is here for Fiddler testing purposes. | |
| 711 | |
| 712 uint32 auth_schemes[] = { | |
| 713 WINHTTP_AUTH_SCHEME_NEGOTIATE, | |
| 714 WINHTTP_AUTH_SCHEME_NTLM, | |
| 715 WINHTTP_AUTH_SCHEME_DIGEST, | |
| 716 WINHTTP_AUTH_SCHEME_BASIC, | |
| 717 }; | |
| 718 for (int i = 0; i < arraysize(auth_schemes); ++i) { | |
| 719 if (supported_schemes & auth_schemes[i]) { | |
| 720 return auth_schemes[i]; | |
| 721 } | |
| 722 } | |
| 723 | |
| 724 return 0; | |
| 725 } | |
| 726 | |
| 727 void SimpleRequest::SetProxyInformation() { | |
| 728 bool uses_proxy = false; | |
| 729 CString proxy, proxy_bypass; | |
| 730 int access_type = NetworkConfig::GetAccessType(proxy_config_); | |
| 731 if (access_type == WINHTTP_ACCESS_TYPE_AUTO_DETECT) { | |
| 732 HttpClient::ProxyInfo proxy_info = {0}; | |
| 733 NetworkConfig* network_config = NULL; | |
| 734 NetworkConfigManager& network_manager = NetworkConfigManager::Instance(); | |
| 735 HRESULT hr = network_manager.GetUserNetworkConfig(&network_config); | |
| 736 if (SUCCEEDED(hr)) { | |
| 737 hr = network_config->GetProxyForUrl( | |
| 738 url_, | |
| 739 proxy_config_.auto_config_url, | |
| 740 &proxy_info); | |
| 741 if (SUCCEEDED(hr)) { | |
| 742 // The result of proxy auto-detection could be that either a proxy is | |
| 743 // found, or direct connection is allowed for the specified url. | |
| 744 ASSERT(proxy_info.access_type == WINHTTP_ACCESS_TYPE_NAMED_PROXY || | |
| 745 proxy_info.access_type == WINHTTP_ACCESS_TYPE_NO_PROXY, | |
| 746 (_T("[Unexpected access_type][%d]"), proxy_info.access_type)); | |
| 747 | |
| 748 uses_proxy = proxy_info.access_type == WINHTTP_ACCESS_TYPE_NAMED_PROXY; | |
| 749 | |
| 750 proxy = proxy_info.proxy; | |
| 751 proxy_bypass = proxy_info.proxy_bypass; | |
| 752 | |
| 753 ::GlobalFree(const_cast<wchar_t*>(proxy_info.proxy)); | |
| 754 ::GlobalFree(const_cast<wchar_t*>(proxy_info.proxy_bypass)); | |
| 755 } else { | |
| 756 ASSERT1(!uses_proxy); | |
| 757 NET_LOG(LW, (_T("[GetProxyForUrl failed][0x%08x]"), hr)); | |
| 758 } | |
| 759 } else { | |
| 760 NET_LOG(LW, (_T("[GetUserNetworkConfig failed][0x%08x]"), hr)); | |
| 761 } | |
| 762 } else if (access_type == WINHTTP_ACCESS_TYPE_NAMED_PROXY) { | |
| 763 uses_proxy = true; | |
| 764 proxy = proxy_config_.proxy; | |
| 765 proxy_bypass = proxy_config_.proxy_bypass; | |
| 766 } | |
| 767 | |
| 768 // If a proxy is going to be used, modify the state of the object and | |
| 769 // set the proxy information on the request handle. | |
| 770 if (uses_proxy) { | |
| 771 ASSERT1(!proxy.IsEmpty()); | |
| 772 | |
| 773 request_state_->proxy = proxy; | |
| 774 request_state_->proxy_bypass = proxy_bypass; | |
| 775 | |
| 776 HttpClient::ProxyInfo proxy_info = {0}; | |
| 777 proxy_info.access_type = WINHTTP_ACCESS_TYPE_NAMED_PROXY; | |
| 778 proxy_info.proxy = request_state_->proxy; | |
| 779 proxy_info.proxy_bypass = request_state_->proxy_bypass; | |
| 780 | |
| 781 NET_LOG(L3, (_T("[using proxy %s]"), proxy_info.proxy)); | |
| 782 VERIFY1(SUCCEEDED(winhttp_adapter_->SetRequestOption(WINHTTP_OPTION_PROXY, | |
| 783 &proxy_info, | |
| 784 sizeof(proxy_info)))); | |
| 785 } | |
| 786 } | |
| 787 | |
| 788 void SimpleRequest::LogResponseHeaders() { | |
| 789 CString response_headers; | |
| 790 winhttp_adapter_->QueryRequestHeadersString(WINHTTP_QUERY_RAW_HEADERS_CRLF, | |
| 791 WINHTTP_HEADER_NAME_BY_INDEX, | |
| 792 &response_headers, | |
| 793 WINHTTP_NO_HEADER_INDEX); | |
| 794 NET_LOG(L3, (_T("[response headers...]\r\n%s"), response_headers)); | |
| 795 } | |
| 796 | |
| 797 } // namespace omaha | |
| 798 | |
| OLD | NEW |