blob: 066513fe9946928a91c53cf6efe8af27c8db22db [file] [log] [blame]
// Copyright 2019 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 "chrome/updater/win/net/network_winhttp.h"
#include <limits>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/strings/strcat.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/updater/win/net/net_util.h"
#include "chrome/updater/win/net/network.h"
#include "chrome/updater/win/net/scoped_hinternet.h"
#include "chrome/updater/win/util.h"
#include "url/url_constants.h"
namespace updater {
namespace {
void CrackUrl(const GURL& url,
bool* is_https,
std::string* host,
int* port,
std::string* path_for_request) {
if (is_https)
*is_https = url.SchemeIs(url::kHttpsScheme);
if (host)
*host = url.host();
if (port)
*port = url.EffectiveIntPort();
if (path_for_request)
*path_for_request = url.PathForRequest();
}
} // namespace
NetworkFetcherWinHTTP::NetworkFetcherWinHTTP(const HINTERNET& session_handle)
: main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
session_handle_(session_handle) {}
NetworkFetcherWinHTTP::~NetworkFetcherWinHTTP() {}
void NetworkFetcherWinHTTP::Close() {
request_handle_.reset();
}
std::string NetworkFetcherWinHTTP::GetResponseBody() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return post_response_body_;
}
HRESULT NetworkFetcherWinHTTP::GetNetError() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return net_error_;
}
std::string NetworkFetcherWinHTTP::GetHeaderETag() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return etag_;
}
int64_t NetworkFetcherWinHTTP::GetXHeaderRetryAfterSec() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return xheader_retry_after_sec_;
}
base::FilePath NetworkFetcherWinHTTP::GetFilePath() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return file_path_;
}
int64_t NetworkFetcherWinHTTP::GetContentSize() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return content_size_;
}
void NetworkFetcherWinHTTP::PostRequest(
const GURL& url,
const std::string& post_data,
const base::flat_map<std::string, std::string>& post_additional_headers,
FetchStartedCallback fetch_started_callback,
FetchProgressCallback fetch_progress_callback,
FetchCompleteCallback fetch_complete_callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
url_ = url;
fetch_started_callback_ = std::move(fetch_started_callback);
fetch_progress_callback_ = std::move(fetch_progress_callback);
fetch_complete_callback_ = std::move(fetch_complete_callback);
DCHECK(url.SchemeIsHTTPOrHTTPS());
CrackUrl(url, &is_https_, &host_, &port_, &path_for_request_);
verb_ = L"POST";
content_type_ = L"Content-Type: application/json\r\n";
write_data_callback_ = base::BindRepeating(
&NetworkFetcherWinHTTP::WriteDataToMemory, base::Unretained(this));
net_error_ = BeginFetch(post_data, post_additional_headers);
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
void NetworkFetcherWinHTTP::DownloadToFile(
const GURL& url,
const base::FilePath& file_path,
FetchStartedCallback fetch_started_callback,
FetchProgressCallback fetch_progress_callback,
FetchCompleteCallback fetch_complete_callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
url_ = url;
file_path_ = file_path;
fetch_started_callback_ = std::move(fetch_started_callback);
fetch_progress_callback_ = std::move(fetch_progress_callback);
fetch_complete_callback_ = std::move(fetch_complete_callback);
DCHECK(url.SchemeIsHTTPOrHTTPS());
CrackUrl(url, &is_https_, &host_, &port_, &path_for_request_);
verb_ = L"GET";
write_data_callback_ = base::BindRepeating(
&NetworkFetcherWinHTTP::WriteDataToFile, base::Unretained(this));
net_error_ = BeginFetch({}, {});
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
HRESULT NetworkFetcherWinHTTP::BeginFetch(
const std::string& data,
const base::flat_map<std::string, std::string>& additional_headers) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
connect_handle_ = Connect();
if (!connect_handle_.get())
return HRESULTFromLastError();
request_handle_ = OpenRequest();
if (!request_handle_.get())
return HRESULTFromLastError();
const auto winhttp_callback = ::WinHttpSetStatusCallback(
request_handle_.get(), &NetworkFetcherWinHTTP::WinHttpStatusCallback,
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
if (winhttp_callback == WINHTTP_INVALID_STATUS_CALLBACK)
return HRESULTFromLastError();
auto hr =
SetOption(request_handle_.get(), WINHTTP_OPTION_CONTEXT_VALUE, context());
if (FAILED(hr))
return hr;
self_ = this;
// Disables both saving and sending cookies.
hr = SetOption(request_handle_.get(), WINHTTP_OPTION_DISABLE_FEATURE,
WINHTTP_DISABLE_COOKIES);
if (FAILED(hr))
return hr;
if (!content_type_.empty()) {
::WinHttpAddRequestHeaders(
request_handle_.get(), content_type_.data(), content_type_.size(),
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
}
for (const auto& header : additional_headers) {
const auto raw_header = base::SysUTF8ToWide(
base::StrCat({header.first, ": ", header.second, "\r\n"}));
::WinHttpAddRequestHeaders(
request_handle_.get(), raw_header.c_str(), raw_header.size(),
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
}
hr = SendRequest(data);
if (FAILED(hr))
return hr;
return S_OK;
}
scoped_hinternet NetworkFetcherWinHTTP::Connect() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return scoped_hinternet(::WinHttpConnect(
session_handle_, base::SysUTF8ToWide(host_).c_str(), port_, 0));
}
scoped_hinternet NetworkFetcherWinHTTP::OpenRequest() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
uint32_t flags = WINHTTP_FLAG_REFRESH;
if (is_https_)
flags |= WINHTTP_FLAG_SECURE;
return scoped_hinternet(::WinHttpOpenRequest(
connect_handle_.get(), verb_.data(),
base::SysUTF8ToWide(path_for_request_).c_str(), nullptr,
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags));
}
HRESULT NetworkFetcherWinHTTP::SendRequest(const std::string& data) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
const uint32_t bytes_to_send = base::saturated_cast<uint32_t>(data.size());
void* request_body =
bytes_to_send ? const_cast<char*>(data.c_str()) : WINHTTP_NO_REQUEST_DATA;
if (!::WinHttpSendRequest(request_handle_.get(),
WINHTTP_NO_ADDITIONAL_HEADERS, 0, request_body,
bytes_to_send, bytes_to_send, context())) {
return HRESULTFromLastError();
}
return S_OK;
}
void NetworkFetcherWinHTTP::SendRequestComplete() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::string16 all;
QueryHeadersString(
request_handle_.get(),
WINHTTP_QUERY_RAW_HEADERS_CRLF | WINHTTP_QUERY_FLAG_REQUEST_HEADERS,
WINHTTP_HEADER_NAME_BY_INDEX, &all);
VLOG(2) << "request headers: " << all;
net_error_ = ReceiveResponse();
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
HRESULT NetworkFetcherWinHTTP::ReceiveResponse() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!::WinHttpReceiveResponse(request_handle_.get(), nullptr))
return HRESULTFromLastError();
return S_OK;
}
void NetworkFetcherWinHTTP::ReceiveResponseComplete() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::string16 all;
QueryHeadersString(request_handle_.get(), WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX, &all);
VLOG(2) << "response headers: " << all;
int response_code = 0;
net_error_ = QueryHeadersInt(request_handle_.get(), WINHTTP_QUERY_STATUS_CODE,
WINHTTP_HEADER_NAME_BY_INDEX, &response_code);
if (FAILED(net_error_)) {
std::move(fetch_complete_callback_).Run();
return;
}
int content_length = 0;
net_error_ =
QueryHeadersInt(request_handle_.get(), WINHTTP_QUERY_CONTENT_LENGTH,
WINHTTP_HEADER_NAME_BY_INDEX, &content_length);
if (FAILED(net_error_)) {
std::move(fetch_complete_callback_).Run();
return;
}
base::string16 etag;
if (SUCCEEDED(QueryHeadersString(request_handle_.get(), WINHTTP_QUERY_ETAG,
WINHTTP_HEADER_NAME_BY_INDEX, &etag))) {
etag_ = base::SysWideToUTF8(etag);
}
int xheader_retry_after_sec = 0;
if (SUCCEEDED(QueryHeadersInt(
request_handle_.get(), WINHTTP_QUERY_CUSTOM,
base::SysUTF8ToWide(
update_client::NetworkFetcher::kHeaderXRetryAfter),
&xheader_retry_after_sec))) {
xheader_retry_after_sec_ = xheader_retry_after_sec;
}
std::move(fetch_started_callback_)
.Run(final_url_.is_valid() ? final_url_ : url_, response_code,
content_length);
net_error_ = QueryDataAvailable();
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
HRESULT NetworkFetcherWinHTTP::QueryDataAvailable() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!::WinHttpQueryDataAvailable(request_handle_.get(), nullptr))
return HRESULTFromLastError();
return S_OK;
}
void NetworkFetcherWinHTTP::QueryDataAvailableComplete(
size_t num_bytes_available) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
net_error_ = ReadData(num_bytes_available);
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
HRESULT NetworkFetcherWinHTTP::ReadData(size_t num_bytes_available) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
const int num_bytes_to_read = base::saturated_cast<int>(num_bytes_available);
read_buffer_.resize(num_bytes_to_read);
if (!::WinHttpReadData(request_handle_.get(), &read_buffer_.front(),
read_buffer_.size(), nullptr)) {
return HRESULTFromLastError();
}
return S_OK;
}
void NetworkFetcherWinHTTP::ReadDataComplete(size_t num_bytes_read) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
fetch_progress_callback_.Run(base::saturated_cast<int64_t>(num_bytes_read));
read_buffer_.resize(num_bytes_read);
write_data_callback_.Run();
}
void NetworkFetcherWinHTTP::RequestError(const WINHTTP_ASYNC_RESULT* result) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
net_error_ = HRESULTFromUpdaterError(result->dwError);
std::move(fetch_complete_callback_).Run();
}
void NetworkFetcherWinHTTP::WriteDataToFile() {
constexpr base::TaskTraits kTaskTraits = {
base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(&NetworkFetcherWinHTTP::WriteDataToFileBlocking,
base::Unretained(this)),
base::BindOnce(&NetworkFetcherWinHTTP::WriteDataToFileComplete,
base::Unretained(this)));
}
bool NetworkFetcherWinHTTP::WriteDataToFileBlocking() {
if (read_buffer_.empty()) {
file_.Close();
net_error_ = S_OK;
return true;
}
if (!file_.IsValid()) {
file_.Initialize(file_path_, base::File::Flags::FLAG_CREATE_ALWAYS |
base::File::Flags::FLAG_WRITE |
base::File::Flags::FLAG_SEQUENTIAL_SCAN);
if (!file_.IsValid()) {
net_error_ = HRESULTFromUpdaterError(file_.error_details());
return false;
}
}
DCHECK(file_.IsValid());
if (file_.WriteAtCurrentPos(&read_buffer_.front(), read_buffer_.size()) ==
-1) {
net_error_ = HRESULTFromUpdaterError(base::File::GetLastFileError());
file_.Close();
base::DeleteFile(file_path_, false);
return false;
}
content_size_ += read_buffer_.size();
return false;
}
void NetworkFetcherWinHTTP::WriteDataToFileComplete(bool is_eof) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (is_eof || FAILED(net_error_)) {
std::move(fetch_complete_callback_).Run();
return;
}
net_error_ = QueryDataAvailable();
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
void NetworkFetcherWinHTTP::WriteDataToMemory() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (read_buffer_.empty()) {
VLOG(2) << post_response_body_;
net_error_ = S_OK;
std::move(fetch_complete_callback_).Run();
return;
}
post_response_body_.append(read_buffer_.begin(), read_buffer_.end());
content_size_ += read_buffer_.size();
net_error_ = QueryDataAvailable();
if (FAILED(net_error_))
std::move(fetch_complete_callback_).Run();
}
void __stdcall NetworkFetcherWinHTTP::WinHttpStatusCallback(HINTERNET handle,
DWORD_PTR context,
DWORD status,
void* info,
DWORD info_len) {
DCHECK(handle);
DCHECK(context);
NetworkFetcherWinHTTP* network_fetcher =
reinterpret_cast<NetworkFetcherWinHTTP*>(context);
network_fetcher->main_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&NetworkFetcherWinHTTP::StatusCallback,
base::Unretained(network_fetcher), handle,
status, info, info_len));
}
void NetworkFetcherWinHTTP::StatusCallback(HINTERNET handle,
uint32_t status,
void* info,
uint32_t info_len) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::StringPiece status_string;
base::string16 info_string;
switch (status) {
case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
status_string = "handle created";
break;
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
status_string = "handle closing";
break;
case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
status_string = "resolving";
info_string.assign(static_cast<base::char16*>(info), info_len); // host.
break;
case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
status_string = "resolved";
break;
case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
status_string = "connecting";
info_string.assign(static_cast<base::char16*>(info), info_len); // IP.
break;
case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
status_string = "connected";
break;
case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
status_string = "sending";
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
status_string = "sent";
break;
case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
status_string = "receiving response";
break;
case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
status_string = "response received";
break;
case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
status_string = "connection closing";
break;
case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
status_string = "connection closed";
break;
case WINHTTP_CALLBACK_STATUS_REDIRECT:
status_string = "redirect";
info_string.assign(static_cast<base::char16*>(info), info_len); // URL.
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
status_string = "data available";
DCHECK_EQ(info_len, sizeof(uint32_t));
info_string = base::StringPrintf(L"%lu", *static_cast<uint32_t*>(info));
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
status_string = "headers available";
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
status_string = "read complete";
info_string = base::StringPrintf(L"%lu", info_len);
break;
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
status_string = "send request complete";
break;
case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
status_string = "write complete";
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
status_string = "request error";
break;
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
status_string = "https failure";
DCHECK(info);
DCHECK_EQ(info_len, sizeof(uint32_t));
info_string = base::StringPrintf(L"%#x", *static_cast<uint32_t*>(info));
break;
default:
status_string = "unknown callback";
break;
}
std::string msg;
if (!status_string.empty())
base::StringAppendF(&msg, "status=%s", status_string.data());
else
base::StringAppendF(&msg, "status=%#x", status);
if (!info_string.empty())
base::StringAppendF(&msg, ", info=%s",
base::SysWideToUTF8(info_string).c_str());
VLOG(2) << "WinHttp status callback:"
<< " handle=" << handle << ", " << msg;
switch (status) {
case WINHTTP_CALLBACK_STATUS_REDIRECT:
final_url_ = GURL(static_cast<base::char16*>(info));
break;
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
self_ = nullptr;
break;
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
SendRequestComplete();
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
ReceiveResponseComplete();
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
DCHECK_EQ(info_len, sizeof(uint32_t));
QueryDataAvailableComplete(*static_cast<uint32_t*>(info));
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
DCHECK_EQ(info, &read_buffer_.front());
ReadDataComplete(info_len);
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
RequestError(static_cast<const WINHTTP_ASYNC_RESULT*>(info));
break;
default:
break;
}
}
} // namespace updater