| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "chrome_frame/urlmon_url_request.h" | |
| 6 | |
| 7 #include <urlmon.h> | |
| 8 #include <wininet.h> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/bind_helpers.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/memory/scoped_ptr.h" | |
| 14 #include "base/message_loop/message_loop.h" | |
| 15 #include "base/strings/string_number_conversions.h" | |
| 16 #include "base/strings/stringprintf.h" | |
| 17 #include "base/strings/utf_string_conversions.h" | |
| 18 #include "base/threading/platform_thread.h" | |
| 19 #include "base/threading/thread.h" | |
| 20 #include "chrome/common/automation_messages.h" | |
| 21 #include "chrome_frame/bind_context_info.h" | |
| 22 #include "chrome_frame/chrome_frame_activex_base.h" | |
| 23 #include "chrome_frame/extra_system_apis.h" | |
| 24 #include "chrome_frame/html_utils.h" | |
| 25 #include "chrome_frame/urlmon_upload_data_stream.h" | |
| 26 #include "chrome_frame/urlmon_url_request_private.h" | |
| 27 #include "chrome_frame/utils.h" | |
| 28 #include "net/base/load_flags.h" | |
| 29 #include "net/http/http_response_headers.h" | |
| 30 #include "net/http/http_util.h" | |
| 31 | |
| 32 #define IS_HTTP_SUCCESS_CODE(code) (code >= 200 && code <= 299) | |
| 33 | |
| 34 UrlmonUrlRequest::UrlmonUrlRequest() | |
| 35 : pending_read_size_(0), | |
| 36 headers_received_(false), | |
| 37 calling_delegate_(0), | |
| 38 thread_(NULL), | |
| 39 parent_window_(NULL), | |
| 40 privileged_mode_(false), | |
| 41 pending_(false), | |
| 42 is_expecting_download_(true), | |
| 43 cleanup_transaction_(false) { | |
| 44 DVLOG(1) << __FUNCTION__ << me(); | |
| 45 } | |
| 46 | |
| 47 UrlmonUrlRequest::~UrlmonUrlRequest() { | |
| 48 DVLOG(1) << __FUNCTION__ << me(); | |
| 49 } | |
| 50 | |
| 51 std::string UrlmonUrlRequest::me() const { | |
| 52 return base::StringPrintf(" id: %i Obj: %X ", id(), this); | |
| 53 } | |
| 54 | |
| 55 bool UrlmonUrlRequest::Start() { | |
| 56 DVLOG(1) << __FUNCTION__ << me() << url(); | |
| 57 DCHECK(thread_ == 0 || thread_ == base::PlatformThread::CurrentId()); | |
| 58 thread_ = base::PlatformThread::CurrentId(); | |
| 59 status_.Start(); | |
| 60 // Initialize the net::HostPortPair structure from the url initially. We may | |
| 61 // not receive the ip address of the host if the request is satisfied from | |
| 62 // the cache. | |
| 63 socket_address_ = net::HostPortPair::FromURL(GURL(url())); | |
| 64 // The UrlmonUrlRequest instance can get destroyed in the context of | |
| 65 // StartAsyncDownload if BindToStorage finishes synchronously with an error. | |
| 66 // Grab a reference to protect against this. | |
| 67 scoped_refptr<UrlmonUrlRequest> ref(this); | |
| 68 HRESULT hr = StartAsyncDownload(); | |
| 69 if (FAILED(hr) && status_.get_state() != UrlmonUrlRequest::Status::DONE) { | |
| 70 status_.Done(); | |
| 71 status_.set_result(net::URLRequestStatus::FAILED, HresultToNetError(hr)); | |
| 72 NotifyDelegateAndDie(); | |
| 73 } | |
| 74 return true; | |
| 75 } | |
| 76 | |
| 77 void UrlmonUrlRequest::Stop() { | |
| 78 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 79 DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL)); | |
| 80 Status::State state = status_.get_state(); | |
| 81 delegate_ = NULL; | |
| 82 | |
| 83 // If DownloadInHost is already requested, we will quit soon anyway. | |
| 84 if (terminate_requested()) | |
| 85 return; | |
| 86 | |
| 87 switch (state) { | |
| 88 case Status::WORKING: | |
| 89 status_.Cancel(); | |
| 90 if (binding_) | |
| 91 binding_->Abort(); | |
| 92 break; | |
| 93 | |
| 94 case Status::ABORTING: | |
| 95 status_.Cancel(); | |
| 96 break; | |
| 97 | |
| 98 case Status::DONE: | |
| 99 status_.Cancel(); | |
| 100 NotifyDelegateAndDie(); | |
| 101 break; | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 bool UrlmonUrlRequest::Read(int bytes_to_read) { | |
| 106 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 107 DCHECK_GE(bytes_to_read, 0); | |
| 108 DCHECK_EQ(0, calling_delegate_); | |
| 109 DVLOG(1) << __FUNCTION__ << me(); | |
| 110 | |
| 111 is_expecting_download_ = false; | |
| 112 | |
| 113 // Re-entrancy check. Thou shall not call Read() while process OnReadComplete! | |
| 114 DCHECK_EQ(0u, pending_read_size_); | |
| 115 if (pending_read_size_ != 0) | |
| 116 return false; | |
| 117 | |
| 118 DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL)); | |
| 119 if (status_.get_state() == Status::ABORTING) | |
| 120 return true; | |
| 121 | |
| 122 // Send data if available. | |
| 123 size_t bytes_copied = 0; | |
| 124 if ((bytes_copied = SendDataToDelegate(bytes_to_read))) { | |
| 125 DVLOG(1) << __FUNCTION__ << me() << " bytes read: " << bytes_copied; | |
| 126 return true; | |
| 127 } | |
| 128 | |
| 129 if (status_.get_state() == Status::WORKING) { | |
| 130 DVLOG(1) << __FUNCTION__ << me() << " pending: " << bytes_to_read; | |
| 131 pending_read_size_ = bytes_to_read; | |
| 132 } else { | |
| 133 DVLOG(1) << __FUNCTION__ << me() << " Response finished."; | |
| 134 NotifyDelegateAndDie(); | |
| 135 } | |
| 136 | |
| 137 return true; | |
| 138 } | |
| 139 | |
| 140 HRESULT UrlmonUrlRequest::InitPending(const GURL& url, IMoniker* moniker, | |
| 141 IBindCtx* bind_context, | |
| 142 bool enable_frame_busting, | |
| 143 bool privileged_mode, | |
| 144 HWND notification_window, | |
| 145 IStream* cache) { | |
| 146 DVLOG(1) << __FUNCTION__ << me() << url.spec(); | |
| 147 DCHECK(bind_context_ == NULL); | |
| 148 DCHECK(moniker_ == NULL); | |
| 149 DCHECK(cache_ == NULL); | |
| 150 DCHECK(thread_ == 0 || thread_ == base::PlatformThread::CurrentId()); | |
| 151 thread_ = base::PlatformThread::CurrentId(); | |
| 152 bind_context_ = bind_context; | |
| 153 moniker_ = moniker; | |
| 154 enable_frame_busting_ = enable_frame_busting; | |
| 155 privileged_mode_ = privileged_mode; | |
| 156 parent_window_ = notification_window; | |
| 157 cache_ = cache; | |
| 158 set_url(url.spec()); | |
| 159 set_pending(true); | |
| 160 | |
| 161 // Request has already started and data is fetched. We will get the | |
| 162 // GetBindInfo call as per contract but the return values are | |
| 163 // ignored. So just set "get" as a method to make our GetBindInfo | |
| 164 // implementation happy. | |
| 165 method_ = "get"; | |
| 166 return S_OK; | |
| 167 } | |
| 168 | |
| 169 void UrlmonUrlRequest::TerminateBind(const TerminateBindCallback& callback) { | |
| 170 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 171 DVLOG(1) << __FUNCTION__ << me(); | |
| 172 cleanup_transaction_ = false; | |
| 173 if (status_.get_state() == Status::DONE) { | |
| 174 // Binding is stopped. Note result could be an error. | |
| 175 callback.Run(moniker_, bind_context_, upload_data_, | |
| 176 request_headers_.c_str()); | |
| 177 } else { | |
| 178 // WORKING (ABORTING?). Save the callback. | |
| 179 // Now we will return INET_TERMINATE_BIND from ::OnDataAvailable() and in | |
| 180 // ::OnStopBinding will invoke the callback passing our moniker and | |
| 181 // bind context. | |
| 182 terminate_bind_callback_ = callback; | |
| 183 if (pending_data_) { | |
| 184 // For downloads to work correctly, we must induce a call to | |
| 185 // OnDataAvailable so that we can download INET_E_TERMINATED_BIND and | |
| 186 // get IE into the correct state. | |
| 187 // To accomplish this we read everything that's readily available in | |
| 188 // the current stream. Once we've reached the end of the stream we | |
| 189 // should get E_PENDING back and then later we'll get that call | |
| 190 // to OnDataAvailable. | |
| 191 std::string data; | |
| 192 base::win::ScopedComPtr<IStream> read_stream(pending_data_); | |
| 193 HRESULT hr; | |
| 194 while ((hr = ReadStream(read_stream, 0xffff, &data)) == S_OK) { | |
| 195 // Just drop the data. | |
| 196 } | |
| 197 DLOG_IF(WARNING, hr != E_PENDING) << __FUNCTION__ << | |
| 198 base::StringPrintf(" expected E_PENDING but got 0x%08X", hr); | |
| 199 } | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 size_t UrlmonUrlRequest::SendDataToDelegate(size_t bytes_to_read) { | |
| 204 return 0; | |
| 205 } | |
| 206 | |
| 207 STDMETHODIMP UrlmonUrlRequest::OnStartBinding(DWORD reserved, | |
| 208 IBinding* binding) { | |
| 209 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 210 binding_ = binding; | |
| 211 if (pending_) { | |
| 212 response_headers_ = GetHttpHeadersFromBinding(binding_); | |
| 213 DCHECK(!response_headers_.empty()); | |
| 214 } | |
| 215 return S_OK; | |
| 216 } | |
| 217 | |
| 218 STDMETHODIMP UrlmonUrlRequest::GetPriority(LONG *priority) { | |
| 219 if (!priority) | |
| 220 return E_POINTER; | |
| 221 *priority = THREAD_PRIORITY_NORMAL; | |
| 222 return S_OK; | |
| 223 } | |
| 224 | |
| 225 STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) { | |
| 226 return S_OK; | |
| 227 } | |
| 228 | |
| 229 STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress, | |
| 230 ULONG status_code, LPCWSTR status_text) { | |
| 231 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 232 | |
| 233 if (status_.get_state() != Status::WORKING) | |
| 234 return S_OK; | |
| 235 | |
| 236 // Ignore any notifications received while we are in the pending state | |
| 237 // waiting for the request to be initiated by Chrome. | |
| 238 if (pending_ && status_code != BINDSTATUS_REDIRECTING) | |
| 239 return S_OK; | |
| 240 | |
| 241 if (!delegate_) { | |
| 242 DVLOG(1) << "Invalid delegate"; | |
| 243 return S_OK; | |
| 244 } | |
| 245 | |
| 246 switch (status_code) { | |
| 247 case BINDSTATUS_CONNECTING: { | |
| 248 if (status_text) { | |
| 249 socket_address_.set_host(base::WideToUTF8(status_text)); | |
| 250 } | |
| 251 break; | |
| 252 } | |
| 253 | |
| 254 case BINDSTATUS_REDIRECTING: { | |
| 255 // If we receive a redirect for the initial pending request initiated | |
| 256 // when our document loads we should stash it away and inform Chrome | |
| 257 // accordingly when it requests data for the original URL. | |
| 258 base::win::ScopedComPtr<BindContextInfo> info; | |
| 259 BindContextInfo::FromBindContext(bind_context_, info.Receive()); | |
| 260 DCHECK(info); | |
| 261 GURL previously_redirected(info ? info->GetUrl() : std::wstring()); | |
| 262 if (GURL(status_text) != previously_redirected) { | |
| 263 DVLOG(1) << __FUNCTION__ << me() << "redirect from " << url() | |
| 264 << " to " << status_text; | |
| 265 // Fetch the redirect status as they aren't all equal (307 in particular | |
| 266 // retains the HTTP request verb). | |
| 267 int http_code = GetHttpResponseStatusFromBinding(binding_); | |
| 268 status_.SetRedirected(http_code, base::WideToUTF8(status_text)); | |
| 269 // Abort. We will inform Chrome in OnStopBinding callback. | |
| 270 binding_->Abort(); | |
| 271 return E_ABORT; | |
| 272 } | |
| 273 break; | |
| 274 } | |
| 275 | |
| 276 case BINDSTATUS_COOKIE_SENT: | |
| 277 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_READ); | |
| 278 break; | |
| 279 | |
| 280 case BINDSTATUS_COOKIE_SUPPRESSED: | |
| 281 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_SUPPRESS); | |
| 282 break; | |
| 283 | |
| 284 case BINDSTATUS_COOKIE_STATE_ACCEPT: | |
| 285 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_ACCEPT); | |
| 286 break; | |
| 287 | |
| 288 case BINDSTATUS_COOKIE_STATE_REJECT: | |
| 289 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_REJECT); | |
| 290 break; | |
| 291 | |
| 292 case BINDSTATUS_COOKIE_STATE_LEASH: | |
| 293 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_LEASH); | |
| 294 break; | |
| 295 | |
| 296 case BINDSTATUS_COOKIE_STATE_DOWNGRADE: | |
| 297 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_DOWNGRADE); | |
| 298 break; | |
| 299 | |
| 300 case BINDSTATUS_COOKIE_STATE_UNKNOWN: | |
| 301 NOTREACHED() << L"Unknown cookie state received"; | |
| 302 break; | |
| 303 | |
| 304 default: | |
| 305 DVLOG(1) << __FUNCTION__ << me() | |
| 306 << base::StringPrintf(L"code: %i status: %ls", status_code, | |
| 307 status_text); | |
| 308 break; | |
| 309 } | |
| 310 | |
| 311 return S_OK; | |
| 312 } | |
| 313 | |
| 314 STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) { | |
| 315 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 316 DVLOG(1) << __FUNCTION__ << me() | |
| 317 << "- Request stopped, Result: " << std::hex << result; | |
| 318 DCHECK(status_.get_state() == Status::WORKING || | |
| 319 status_.get_state() == Status::ABORTING); | |
| 320 | |
| 321 Status::State state = status_.get_state(); | |
| 322 | |
| 323 // Mark we a are done. | |
| 324 status_.Done(); | |
| 325 | |
| 326 if (result == INET_E_TERMINATED_BIND) { | |
| 327 if (terminate_requested()) { | |
| 328 terminate_bind_callback_.Run(moniker_, bind_context_, upload_data_, | |
| 329 request_headers_.c_str()); | |
| 330 } else { | |
| 331 cleanup_transaction_ = true; | |
| 332 } | |
| 333 // We may have returned INET_E_TERMINATED_BIND from OnDataAvailable. | |
| 334 result = S_OK; | |
| 335 } | |
| 336 | |
| 337 if (state == Status::WORKING) { | |
| 338 status_.set_result(result); | |
| 339 | |
| 340 if (FAILED(result)) { | |
| 341 int http_code = GetHttpResponseStatusFromBinding(binding_); | |
| 342 // For certain requests like empty POST requests the server can return | |
| 343 // back a HTTP success code in the range 200 to 299. We need to flag | |
| 344 // these requests as succeeded. | |
| 345 if (IS_HTTP_SUCCESS_CODE(http_code)) { | |
| 346 // If this DCHECK fires it means that the server returned a HTTP | |
| 347 // success code outside the standard range 200-206. We need to confirm | |
| 348 // if the following code path is correct. | |
| 349 DCHECK_LE(http_code, 206); | |
| 350 status_.set_result(S_OK); | |
| 351 std::string headers = GetHttpHeadersFromBinding(binding_); | |
| 352 OnResponse(0, base::UTF8ToWide(headers).c_str(), NULL, NULL); | |
| 353 } else if (net::HttpResponseHeaders::IsRedirectResponseCode(http_code) && | |
| 354 result == E_ACCESSDENIED) { | |
| 355 // Special case. If the last request was a redirect and the current OS | |
| 356 // error value is E_ACCESSDENIED, that means an unsafe redirect was | |
| 357 // attempted. In that case, correct the OS error value to be the more | |
| 358 // specific ERR_UNSAFE_REDIRECT error value. | |
| 359 status_.set_result(net::URLRequestStatus::FAILED, | |
| 360 net::ERR_UNSAFE_REDIRECT); | |
| 361 } | |
| 362 } | |
| 363 | |
| 364 // The code below seems easy but it is not. :) | |
| 365 // The network policy in Chrome network is that error code/end_of_stream | |
| 366 // should be returned only as a result of read (or start) request. | |
| 367 // Here are the possible cases: | |
| 368 // pending_data_|pending_read | |
| 369 // FALSE |FALSE => EndRequest if no headers, otherwise wait for Read. | |
| 370 // FALSE |TRUE => EndRequest. | |
| 371 // TRUE |FALSE => Wait for Read. | |
| 372 // TRUE |TRUE => Something went wrong!! | |
| 373 | |
| 374 if (pending_data_) { | |
| 375 DCHECK_EQ(pending_read_size_, 0UL); | |
| 376 ReleaseBindings(); | |
| 377 return S_OK; | |
| 378 } | |
| 379 | |
| 380 if (headers_received_ && pending_read_size_ == 0) { | |
| 381 ReleaseBindings(); | |
| 382 return S_OK; | |
| 383 } | |
| 384 | |
| 385 // No headers or there is a pending read from Chrome. | |
| 386 NotifyDelegateAndDie(); | |
| 387 return S_OK; | |
| 388 } | |
| 389 | |
| 390 // Status::ABORTING | |
| 391 if (status_.was_redirected()) { | |
| 392 // Just release bindings here. Chrome will issue EndRequest(request_id) | |
| 393 // after processing headers we had provided. | |
| 394 if (!pending_) { | |
| 395 std::string headers = GetHttpHeadersFromBinding(binding_); | |
| 396 OnResponse(0, base::UTF8ToWide(headers).c_str(), NULL, NULL); | |
| 397 } | |
| 398 ReleaseBindings(); | |
| 399 return S_OK; | |
| 400 } | |
| 401 | |
| 402 // Stop invoked. | |
| 403 NotifyDelegateAndDie(); | |
| 404 return S_OK; | |
| 405 } | |
| 406 | |
| 407 STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, | |
| 408 BINDINFO* bind_info) { | |
| 409 if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL)) | |
| 410 return E_INVALIDARG; | |
| 411 | |
| 412 *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA; | |
| 413 | |
| 414 bind_info->dwOptionsFlags = INTERNET_FLAG_NO_AUTO_REDIRECT; | |
| 415 bind_info->dwOptions = BINDINFO_OPTIONS_WININETFLAG; | |
| 416 | |
| 417 // TODO(ananta) | |
| 418 // Look into whether the other load flags need to be supported in chrome | |
| 419 // frame. | |
| 420 if (load_flags_ & net::LOAD_VALIDATE_CACHE) | |
| 421 *bind_flags |= BINDF_RESYNCHRONIZE; | |
| 422 | |
| 423 if (load_flags_ & net::LOAD_BYPASS_CACHE) | |
| 424 *bind_flags |= BINDF_GETNEWESTVERSION; | |
| 425 | |
| 426 if (LowerCaseEqualsASCII(method(), "get")) { | |
| 427 bind_info->dwBindVerb = BINDVERB_GET; | |
| 428 } else if (LowerCaseEqualsASCII(method(), "post")) { | |
| 429 bind_info->dwBindVerb = BINDVERB_POST; | |
| 430 } else if (LowerCaseEqualsASCII(method(), "put")) { | |
| 431 bind_info->dwBindVerb = BINDVERB_PUT; | |
| 432 } else { | |
| 433 std::wstring verb(base::ASCIIToWide(StringToUpperASCII(method()))); | |
| 434 bind_info->dwBindVerb = BINDVERB_CUSTOM; | |
| 435 bind_info->szCustomVerb = reinterpret_cast<wchar_t*>( | |
| 436 ::CoTaskMemAlloc((verb.length() + 1) * sizeof(wchar_t))); | |
| 437 lstrcpyW(bind_info->szCustomVerb, verb.c_str()); | |
| 438 } | |
| 439 | |
| 440 if (bind_info->dwBindVerb == BINDVERB_POST || | |
| 441 bind_info->dwBindVerb == BINDVERB_PUT || | |
| 442 post_data_len() > 0) { | |
| 443 // Bypass caching proxies on upload requests and avoid writing responses to | |
| 444 // the browser's cache. | |
| 445 *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_PRAGMA_NO_CACHE; | |
| 446 | |
| 447 // Attempt to avoid storing the response for upload requests. | |
| 448 // See http://crbug.com/55918 | |
| 449 if (resource_type_ != ResourceType::MAIN_FRAME) | |
| 450 *bind_flags |= BINDF_NOWRITECACHE; | |
| 451 | |
| 452 // Initialize the STGMEDIUM. | |
| 453 memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM)); | |
| 454 bind_info->grfBindInfoF = 0; | |
| 455 | |
| 456 if (bind_info->dwBindVerb != BINDVERB_CUSTOM) | |
| 457 bind_info->szCustomVerb = NULL; | |
| 458 | |
| 459 if ((post_data_len() || is_chunked_upload()) && | |
| 460 get_upload_data(&bind_info->stgmedData.pstm) == S_OK) { | |
| 461 bind_info->stgmedData.tymed = TYMED_ISTREAM; | |
| 462 if (!is_chunked_upload()) { | |
| 463 bind_info->cbstgmedData = static_cast<DWORD>(post_data_len()); | |
| 464 } | |
| 465 DVLOG(1) << __FUNCTION__ << me() << method() | |
| 466 << " request with " << base::Int64ToString(post_data_len()) | |
| 467 << " bytes. url=" << url(); | |
| 468 } else { | |
| 469 DVLOG(1) << __FUNCTION__ << me() << "POST request with no data!"; | |
| 470 } | |
| 471 } | |
| 472 return S_OK; | |
| 473 } | |
| 474 | |
| 475 STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size, | |
| 476 FORMATETC* formatetc, | |
| 477 STGMEDIUM* storage) { | |
| 478 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 479 DVLOG(1) << __FUNCTION__ << me() << "bytes available: " << size; | |
| 480 | |
| 481 if (terminate_requested()) { | |
| 482 DVLOG(1) << " Download requested. INET_E_TERMINATED_BIND returned"; | |
| 483 return INET_E_TERMINATED_BIND; | |
| 484 } | |
| 485 | |
| 486 if (!storage || (storage->tymed != TYMED_ISTREAM)) { | |
| 487 NOTREACHED(); | |
| 488 return E_INVALIDARG; | |
| 489 } | |
| 490 | |
| 491 IStream* read_stream = storage->pstm; | |
| 492 if (!read_stream) { | |
| 493 NOTREACHED(); | |
| 494 return E_UNEXPECTED; | |
| 495 } | |
| 496 | |
| 497 // Some requests such as HEAD have zero data. | |
| 498 if (size > 0) | |
| 499 pending_data_ = read_stream; | |
| 500 | |
| 501 if (pending_read_size_) { | |
| 502 size_t bytes_copied = SendDataToDelegate(pending_read_size_); | |
| 503 DVLOG(1) << __FUNCTION__ << me() << "size read: " << bytes_copied; | |
| 504 } else { | |
| 505 DVLOG(1) << __FUNCTION__ << me() << "- waiting for remote read"; | |
| 506 } | |
| 507 | |
| 508 if (BSCF_LASTDATANOTIFICATION & flags) { | |
| 509 if (!is_expecting_download_ || pending()) { | |
| 510 DVLOG(1) << __FUNCTION__ << me() << "EOF"; | |
| 511 return S_OK; | |
| 512 } | |
| 513 // Always return INET_E_TERMINATED_BIND to allow bind context reuse | |
| 514 // if DownloadToHost is suddenly requested. | |
| 515 DVLOG(1) << __FUNCTION__ << " EOF: INET_E_TERMINATED_BIND returned"; | |
| 516 return INET_E_TERMINATED_BIND; | |
| 517 } | |
| 518 return S_OK; | |
| 519 } | |
| 520 | |
| 521 STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown* object) { | |
| 522 // We are calling BindToStorage on the moniker we should always get called | |
| 523 // back on OnDataAvailable and should never get OnObjectAvailable | |
| 524 NOTREACHED(); | |
| 525 return E_NOTIMPL; | |
| 526 } | |
| 527 | |
| 528 STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, | |
| 529 const wchar_t* current_headers, DWORD reserved, | |
| 530 wchar_t** additional_headers) { | |
| 531 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 532 if (!additional_headers) { | |
| 533 NOTREACHED(); | |
| 534 return E_POINTER; | |
| 535 } | |
| 536 | |
| 537 DVLOG(1) << __FUNCTION__ << me() << "headers: \n" << current_headers; | |
| 538 | |
| 539 if (status_.get_state() == Status::ABORTING) { | |
| 540 // At times the BINDSTATUS_REDIRECTING notification which is sent to the | |
| 541 // IBindStatusCallback interface does not have an accompanying HTTP | |
| 542 // redirect status code, i.e. the attempt to query the HTTP status code | |
| 543 // from the binding returns 0, 200, etc which are invalid redirect codes. | |
| 544 // We don't want urlmon to follow redirects. We return E_ABORT in our | |
| 545 // IBindStatusCallback::OnProgress function and also abort the binding. | |
| 546 // However urlmon still tries to establish a transaction with the | |
| 547 // redirected URL which confuses the web server. | |
| 548 // Fix is to abort the attempted transaction. | |
| 549 DLOG(WARNING) << __FUNCTION__ << me() | |
| 550 << ": Aborting connection to URL:" | |
| 551 << url | |
| 552 << " as the binding has been aborted"; | |
| 553 return E_ABORT; | |
| 554 } | |
| 555 | |
| 556 HRESULT hr = S_OK; | |
| 557 | |
| 558 std::string new_headers; | |
| 559 if (is_chunked_upload()) { | |
| 560 new_headers = base::StringPrintf("Transfer-Encoding: chunked\r\n"); | |
| 561 } | |
| 562 | |
| 563 if (!extra_headers().empty()) { | |
| 564 // TODO(robertshield): We may need to sanitize headers on POST here. | |
| 565 new_headers += extra_headers(); | |
| 566 } | |
| 567 | |
| 568 if (!referrer().empty()) { | |
| 569 // Referrer is famously misspelled in HTTP: | |
| 570 new_headers += base::StringPrintf("Referer: %s\r\n", referrer().c_str()); | |
| 571 } | |
| 572 | |
| 573 // In the rare case if "User-Agent" string is already in |current_headers|. | |
| 574 // We send Chrome's user agent in requests initiated within ChromeFrame to | |
| 575 // enable third party content in pages rendered in ChromeFrame to correctly | |
| 576 // send content for Chrome as the third party content may not be equipped to | |
| 577 // identify chromeframe as the user agent. This also ensures that the user | |
| 578 // agent reported in scripts in chrome frame is consistent with that sent | |
| 579 // in outgoing requests. | |
| 580 std::string user_agent = http_utils::AddChromeFrameToUserAgentValue( | |
| 581 http_utils::GetChromeUserAgent()); | |
| 582 new_headers += ReplaceOrAddUserAgent(current_headers, user_agent); | |
| 583 | |
| 584 if (!new_headers.empty()) { | |
| 585 *additional_headers = reinterpret_cast<wchar_t*>( | |
| 586 CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t))); | |
| 587 | |
| 588 if (*additional_headers == NULL) { | |
| 589 NOTREACHED(); | |
| 590 hr = E_OUTOFMEMORY; | |
| 591 } else { | |
| 592 lstrcpynW(*additional_headers, base::ASCIIToWide(new_headers).c_str(), | |
| 593 new_headers.size()); | |
| 594 } | |
| 595 } | |
| 596 request_headers_ = new_headers; | |
| 597 return hr; | |
| 598 } | |
| 599 | |
| 600 STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, | |
| 601 const wchar_t* response_headers, const wchar_t* request_headers, | |
| 602 wchar_t** additional_headers) { | |
| 603 return S_OK; | |
| 604 } | |
| 605 | |
| 606 STDMETHODIMP UrlmonUrlRequest::GetWindow(const GUID& guid_reason, | |
| 607 HWND* parent_window) { | |
| 608 if (!parent_window) | |
| 609 return E_INVALIDARG; | |
| 610 | |
| 611 #ifndef NDEBUG | |
| 612 wchar_t guid[40] = {0}; | |
| 613 ::StringFromGUID2(guid_reason, guid, arraysize(guid)); | |
| 614 const wchar_t* str = guid; | |
| 615 if (guid_reason == IID_IAuthenticate) | |
| 616 str = L"IAuthenticate"; | |
| 617 else if (guid_reason == IID_IHttpSecurity) | |
| 618 str = L"IHttpSecurity"; | |
| 619 else if (guid_reason == IID_IWindowForBindingUI) | |
| 620 str = L"IWindowForBindingUI"; | |
| 621 DVLOG(1) << __FUNCTION__ << me() << "GetWindow: " << str; | |
| 622 #endif | |
| 623 // We should return a non-NULL HWND as parent. Otherwise no dialog is shown. | |
| 624 // TODO(iyengar): This hits when running the URL request tests. | |
| 625 DLOG_IF(WARNING, !::IsWindow(parent_window_)) | |
| 626 << "UrlmonUrlRequest::GetWindow - no window!"; | |
| 627 *parent_window = parent_window_; | |
| 628 return S_OK; | |
| 629 } | |
| 630 | |
| 631 STDMETHODIMP UrlmonUrlRequest::Authenticate(HWND* parent_window, | |
| 632 LPWSTR* user_name, | |
| 633 LPWSTR* password) { | |
| 634 if (!parent_window) | |
| 635 return E_INVALIDARG; | |
| 636 | |
| 637 if (privileged_mode_) | |
| 638 return E_ACCESSDENIED; | |
| 639 | |
| 640 DCHECK(::IsWindow(parent_window_)); | |
| 641 *parent_window = parent_window_; | |
| 642 return S_OK; | |
| 643 } | |
| 644 | |
| 645 STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) { | |
| 646 // Urlmon notifies the client of authentication problems, certificate | |
| 647 // errors, etc by querying the object implementing the IBindStatusCallback | |
| 648 // interface for the IHttpSecurity interface. If this interface is not | |
| 649 // implemented then Urlmon checks for the problem codes defined below | |
| 650 // and performs actions as defined below:- | |
| 651 // It invokes the ReportProgress method of the protocol sink with | |
| 652 // these problem codes and eventually invokes the ReportResult method | |
| 653 // on the protocol sink which ends up in a call to the OnStopBinding | |
| 654 // method of the IBindStatusCallBack interface. | |
| 655 | |
| 656 // MSHTML's implementation of the IBindStatusCallback interface does not | |
| 657 // implement the IHttpSecurity interface. However it handles the | |
| 658 // OnStopBinding call with a HRESULT of 0x800c0019 and navigates to | |
| 659 // an interstitial page which presents the user with a choice of whether | |
| 660 // to abort the navigation. | |
| 661 | |
| 662 // In our OnStopBinding implementation we stop the navigation and inform | |
| 663 // Chrome about the result. Ideally Chrome should behave in a manner similar | |
| 664 // to IE, i.e. display the SSL error interstitial page and if the user | |
| 665 // decides to proceed anyway we would turn off SSL warnings for that | |
| 666 // particular navigation and allow IE to download the content. | |
| 667 // We would need to return the certificate information to Chrome for display | |
| 668 // purposes. Currently we only return a dummy certificate to Chrome. | |
| 669 // At this point we decided that it is a lot of work at this point and | |
| 670 // decided to go with the easier option of implementing the IHttpSecurity | |
| 671 // interface and replicating the checks performed by Urlmon. This | |
| 672 // causes Urlmon to display a dialog box on the same lines as IE6. | |
| 673 DVLOG(1) << __FUNCTION__ << me() << "Security problem : " << problem; | |
| 674 | |
| 675 // On IE6 the default IBindStatusCallback interface does not implement the | |
| 676 // IHttpSecurity interface and thus causes IE to put up a certificate error | |
| 677 // dialog box. We need to emulate this behavior for sites with mismatched | |
| 678 // certificates to work. | |
| 679 if (GetIEVersion() == IE_6) | |
| 680 return S_FALSE; | |
| 681 | |
| 682 HRESULT hr = E_ABORT; | |
| 683 | |
| 684 switch (problem) { | |
| 685 case ERROR_INTERNET_SEC_CERT_REV_FAILED: { | |
| 686 hr = RPC_E_RETRY; | |
| 687 break; | |
| 688 } | |
| 689 | |
| 690 case ERROR_INTERNET_SEC_CERT_DATE_INVALID: | |
| 691 case ERROR_INTERNET_SEC_CERT_CN_INVALID: | |
| 692 case ERROR_INTERNET_INVALID_CA: { | |
| 693 hr = S_FALSE; | |
| 694 break; | |
| 695 } | |
| 696 | |
| 697 default: { | |
| 698 NOTREACHED() << "Unhandled security problem : " << problem; | |
| 699 break; | |
| 700 } | |
| 701 } | |
| 702 return hr; | |
| 703 } | |
| 704 | |
| 705 HRESULT UrlmonUrlRequest::StartAsyncDownload() { | |
| 706 DVLOG(1) << __FUNCTION__ << me() << url(); | |
| 707 HRESULT hr = E_FAIL; | |
| 708 DCHECK((moniker_ && bind_context_) || (!moniker_ && !bind_context_)); | |
| 709 | |
| 710 if (!moniker_.get()) { | |
| 711 std::wstring wide_url = base::UTF8ToWide(url()); | |
| 712 hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(), | |
| 713 URL_MK_UNIFORM); | |
| 714 if (FAILED(hr)) { | |
| 715 NOTREACHED() << "CreateURLMonikerEx failed. Error: " << hr; | |
| 716 return hr; | |
| 717 } | |
| 718 } | |
| 719 | |
| 720 if (bind_context_.get() == NULL) { | |
| 721 hr = ::CreateAsyncBindCtxEx(NULL, 0, this, NULL, | |
| 722 bind_context_.Receive(), 0); | |
| 723 DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtxEx failed. Error: " << hr; | |
| 724 } else { | |
| 725 // Use existing bind context. | |
| 726 hr = ::RegisterBindStatusCallback(bind_context_, this, NULL, 0); | |
| 727 DCHECK(SUCCEEDED(hr)) << "RegisterBindStatusCallback failed. Error: " << hr; | |
| 728 } | |
| 729 | |
| 730 if (SUCCEEDED(hr)) { | |
| 731 base::win::ScopedComPtr<IStream> stream; | |
| 732 | |
| 733 // BindToStorage may complete synchronously. | |
| 734 // We still get all the callbacks - OnStart/StopBinding, this may result | |
| 735 // in destruction of our object. It's fine but we access some members | |
| 736 // below for debug info. :) | |
| 737 base::win::ScopedComPtr<IHttpSecurity> self(this); | |
| 738 | |
| 739 // Inform our moniker patch this binding should not be tortured. | |
| 740 base::win::ScopedComPtr<BindContextInfo> info; | |
| 741 BindContextInfo::FromBindContext(bind_context_, info.Receive()); | |
| 742 DCHECK(info); | |
| 743 if (info) | |
| 744 info->set_chrome_request(true); | |
| 745 | |
| 746 hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream), | |
| 747 reinterpret_cast<void**>(stream.Receive())); | |
| 748 if (hr == S_OK) | |
| 749 DCHECK(binding_ != NULL || status_.get_state() == Status::DONE); | |
| 750 | |
| 751 if (FAILED(hr)) { | |
| 752 // TODO(joshia): Look into. This currently fails for: | |
| 753 // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged | |
| 754 // when running the UrlRequest unit tests. | |
| 755 DLOG(ERROR) << __FUNCTION__ << me() << | |
| 756 base::StringPrintf("IUrlMoniker::BindToStorage failed 0x%08X.", hr); | |
| 757 // In most cases we'll get a MK_E_SYNTAX error here but if we abort | |
| 758 // the navigation ourselves such as in the case of seeing something | |
| 759 // else than ALLOWALL in X-Frame-Options. | |
| 760 } | |
| 761 } | |
| 762 | |
| 763 DLOG_IF(ERROR, FAILED(hr)) << me() << | |
| 764 base::StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr); | |
| 765 | |
| 766 return hr; | |
| 767 } | |
| 768 | |
| 769 void UrlmonUrlRequest::NotifyDelegateAndDie() { | |
| 770 } | |
| 771 | |
| 772 void UrlmonUrlRequest::TerminateTransaction() { | |
| 773 if (cleanup_transaction_ && bind_context_ && moniker_) { | |
| 774 // We return INET_E_TERMINATED_BIND from our OnDataAvailable implementation | |
| 775 // to ensure that the transaction stays around if Chrome decides to issue | |
| 776 // a download request when it finishes inspecting the headers received in | |
| 777 // OnResponse. However this causes the urlmon transaction object to leak. | |
| 778 // To workaround this we save away the IInternetProtocol interface which is | |
| 779 // implemented by the urlmon CTransaction object in our BindContextInfo | |
| 780 // instance which is maintained per bind context. Invoking Terminate | |
| 781 // on this with the special flags 0x2000000 cleanly releases the | |
| 782 // transaction. | |
| 783 static const int kUrlmonTerminateTransactionFlags = 0x2000000; | |
| 784 base::win::ScopedComPtr<BindContextInfo> info; | |
| 785 BindContextInfo::FromBindContext(bind_context_, info.Receive()); | |
| 786 DCHECK(info); | |
| 787 if (info && info->protocol()) { | |
| 788 info->protocol()->Terminate(kUrlmonTerminateTransactionFlags); | |
| 789 } | |
| 790 } | |
| 791 bind_context_.Release(); | |
| 792 } | |
| 793 | |
| 794 void UrlmonUrlRequest::ReleaseBindings() { | |
| 795 binding_.Release(); | |
| 796 // Do not release bind_context here! | |
| 797 // We may get DownloadToHost request and therefore we want the bind_context | |
| 798 // to be available. | |
| 799 if (bind_context_) | |
| 800 ::RevokeBindStatusCallback(bind_context_, this); | |
| 801 } | |
| 802 | |
| 803 net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) { | |
| 804 const int kInvalidHostName = 0x8007007b; | |
| 805 // Useful reference: | |
| 806 // http://msdn.microsoft.com/en-us/library/ms775145(VS.85).aspx | |
| 807 | |
| 808 net::Error ret = net::ERR_UNEXPECTED; | |
| 809 | |
| 810 switch (hr) { | |
| 811 case S_OK: | |
| 812 ret = net::OK; | |
| 813 break; | |
| 814 | |
| 815 case MK_E_SYNTAX: | |
| 816 ret = net::ERR_INVALID_URL; | |
| 817 break; | |
| 818 | |
| 819 case INET_E_CANNOT_CONNECT: | |
| 820 ret = net::ERR_CONNECTION_FAILED; | |
| 821 break; | |
| 822 | |
| 823 case INET_E_DOWNLOAD_FAILURE: | |
| 824 case INET_E_CONNECTION_TIMEOUT: | |
| 825 case E_ABORT: | |
| 826 ret = net::ERR_CONNECTION_ABORTED; | |
| 827 break; | |
| 828 | |
| 829 case INET_E_DATA_NOT_AVAILABLE: | |
| 830 ret = net::ERR_EMPTY_RESPONSE; | |
| 831 break; | |
| 832 | |
| 833 case INET_E_RESOURCE_NOT_FOUND: | |
| 834 // To behave more closely to the chrome network stack, we translate this | |
| 835 // error value as tunnel connection failed. This error value is tested | |
| 836 // in the ProxyTunnelRedirectTest and UnexpectedServerAuthTest tests. | |
| 837 ret = net::ERR_TUNNEL_CONNECTION_FAILED; | |
| 838 break; | |
| 839 | |
| 840 // The following error codes can be returned while processing an invalid | |
| 841 // url. http://msdn.microsoft.com/en-us/library/bb250493(v=vs.85).aspx | |
| 842 case INET_E_INVALID_URL: | |
| 843 case INET_E_UNKNOWN_PROTOCOL: | |
| 844 case INET_E_REDIRECT_FAILED: | |
| 845 case INET_E_SECURITY_PROBLEM: | |
| 846 case kInvalidHostName: | |
| 847 case E_INVALIDARG: | |
| 848 case E_OUTOFMEMORY: | |
| 849 ret = net::ERR_INVALID_URL; | |
| 850 break; | |
| 851 | |
| 852 case INET_E_INVALID_CERTIFICATE: | |
| 853 ret = net::ERR_CERT_INVALID; | |
| 854 break; | |
| 855 | |
| 856 case E_ACCESSDENIED: | |
| 857 ret = net::ERR_ACCESS_DENIED; | |
| 858 break; | |
| 859 | |
| 860 default: | |
| 861 DLOG(WARNING) | |
| 862 << base::StringPrintf("TODO: translate HRESULT 0x%08X to net::Error", | |
| 863 hr); | |
| 864 break; | |
| 865 } | |
| 866 return ret; | |
| 867 } | |
| 868 | |
| 869 | |
| 870 PluginUrlRequestManager::ThreadSafeFlags | |
| 871 UrlmonUrlRequestManager::GetThreadSafeFlags() { | |
| 872 return PluginUrlRequestManager::NOT_THREADSAFE; | |
| 873 } | |
| 874 | |
| 875 void UrlmonUrlRequestManager::SetInfoForUrl(const std::wstring& url, | |
| 876 IMoniker* moniker, LPBC bind_ctx) { | |
| 877 CComObject<UrlmonUrlRequest>* new_request = NULL; | |
| 878 CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); | |
| 879 if (new_request) { | |
| 880 GURL start_url(url); | |
| 881 DCHECK(start_url.is_valid()); | |
| 882 DCHECK(pending_request_ == NULL); | |
| 883 | |
| 884 base::win::ScopedComPtr<BindContextInfo> info; | |
| 885 BindContextInfo::FromBindContext(bind_ctx, info.Receive()); | |
| 886 DCHECK(info); | |
| 887 IStream* cache = info ? info->cache() : NULL; | |
| 888 pending_request_ = new_request; | |
| 889 pending_request_->InitPending(start_url, moniker, bind_ctx, | |
| 890 enable_frame_busting_, privileged_mode_, | |
| 891 notification_window_, cache); | |
| 892 // Start the request | |
| 893 bool is_started = pending_request_->Start(); | |
| 894 DCHECK(is_started); | |
| 895 } | |
| 896 } | |
| 897 | |
| 898 void UrlmonUrlRequestManager::BindTerminated(IMoniker* moniker, | |
| 899 IBindCtx* bind_ctx, | |
| 900 IStream* post_data, | |
| 901 const char* request_headers) { | |
| 902 } | |
| 903 | |
| 904 scoped_refptr<UrlmonUrlRequest> UrlmonUrlRequestManager::LookupRequest( | |
| 905 int request_id, RequestMap* request_map) { | |
| 906 RequestMap::iterator it = request_map->find(request_id); | |
| 907 if (request_map->end() != it) | |
| 908 return it->second; | |
| 909 return NULL; | |
| 910 } | |
| 911 | |
| 912 UrlmonUrlRequestManager::UrlmonUrlRequestManager() | |
| 913 : stopping_(false), notification_window_(NULL), | |
| 914 privileged_mode_(false), | |
| 915 container_(NULL), | |
| 916 background_worker_thread_enabled_(true) { | |
| 917 background_thread_.reset(new base::Thread("cf_iexplore_background_thread")); | |
| 918 background_thread_->init_com_with_mta(false); | |
| 919 background_worker_thread_enabled_ = | |
| 920 GetConfigBool(true, kUseBackgroundThreadForSubResources); | |
| 921 if (background_worker_thread_enabled_) { | |
| 922 base::Thread::Options options; | |
| 923 options.message_loop_type = base::MessageLoop::TYPE_UI; | |
| 924 background_thread_->StartWithOptions(options); | |
| 925 } | |
| 926 } | |
| 927 | |
| 928 UrlmonUrlRequestManager::~UrlmonUrlRequestManager() { | |
| 929 } | |
| 930 | |
| 931 void UrlmonUrlRequestManager::AddPrivacyDataForUrl( | |
| 932 const std::string& url, const std::string& policy_ref, | |
| 933 int32 flags) { | |
| 934 DCHECK(!url.empty()); | |
| 935 | |
| 936 bool fire_privacy_event = false; | |
| 937 | |
| 938 if (privacy_info_.privacy_records.empty()) | |
| 939 flags |= PRIVACY_URLISTOPLEVEL; | |
| 940 | |
| 941 if (!privacy_info_.privacy_impacted) { | |
| 942 if (flags & (COOKIEACTION_ACCEPT | COOKIEACTION_REJECT | | |
| 943 COOKIEACTION_DOWNGRADE)) { | |
| 944 privacy_info_.privacy_impacted = true; | |
| 945 fire_privacy_event = true; | |
| 946 } | |
| 947 } | |
| 948 | |
| 949 PrivacyInfo::PrivacyEntry& privacy_entry = | |
| 950 privacy_info_.privacy_records[base::UTF8ToWide(url)]; | |
| 951 | |
| 952 privacy_entry.flags |= flags; | |
| 953 privacy_entry.policy_ref = base::UTF8ToWide(policy_ref); | |
| 954 | |
| 955 if (fire_privacy_event && IsWindow(notification_window_)) { | |
| 956 PostMessage(notification_window_, WM_FIRE_PRIVACY_CHANGE_NOTIFICATION, 1, | |
| 957 0); | |
| 958 } | |
| 959 } | |
| OLD | NEW |