Files
nearby/internal/platform/implementation/windows/http_loader.cc
T
2023-01-06 02:53:57 -08:00

412 lines
12 KiB
C++

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/http_loader.h"
#include <iostream>
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "internal/platform/logging.h"
namespace nearby {
namespace windows {
namespace {
constexpr DWORD kSchemaMaximumLength = 10;
constexpr DWORD kHostNameMaximumLength = 256;
using ::nearby::api::WebResponse;
} // namespace
absl::StatusOr<WebResponse> HttpLoader::GetResponse() {
absl::Status status;
status = ParseUrl();
if (!status.ok()) {
return status;
}
status = ConnectWebServer();
if (!status.ok()) {
return status;
}
// Sends request to web server.
status = SendRequest();
if (!status.ok()) {
return status;
}
// Processes response from web server
absl::StatusOr<WebResponse> result = ProcessResponse();
if (!result.ok()) {
return result;
}
DisconnectWebServer();
return result;
}
absl::StatusOr<int> HttpLoader::QueryStatusCode(HINTERNET file_handle) {
int result;
std::string status_code;
absl::Status status;
status = QueryResponseInfo(file_handle, HTTP_QUERY_STATUS_CODE, status_code);
if (!status.ok()) {
return status;
}
if (!absl::SimpleAtoi(
absl::string_view(status_code.data(), status_code.size() - 1),
&result)) {
return absl::InternalError("Failed to parse status code.");
}
if (result < 0) {
return absl::InternalError("Invalid status code.");
}
return result;
}
absl::StatusOr<std::string> HttpLoader::QueryStatusText(
HINTERNET request_handle) {
absl::Status status;
std::string status_text;
status =
QueryResponseInfo(request_handle, HTTP_QUERY_STATUS_TEXT, status_text);
if (!status.ok()) {
return status;
}
// status_text is ended with char '\0', remove it in returned result.
return status_text.substr(0, status_text.size() - 1);
}
absl::StatusOr<std::multimap<std::string, std::string>>
HttpLoader::QueryResponseHeaders(HINTERNET request_handle) {
absl::Status status;
std::string headers_string;
status = QueryResponseInfo(request_handle, HTTP_QUERY_RAW_HEADERS_CRLF,
headers_string);
if (!status.ok()) {
return status;
}
std::multimap<std::string, std::string> headers;
// Parse headers in response
size_t start = 0;
size_t pos = 0;
while ((pos = headers_string.find("\r\n", start)) != std::string::npos) {
std::string header = headers_string.substr(start, pos - start);
// Get key and value in header
size_t split_pos = 0;
if ((split_pos = header.find(": ")) != std::string::npos) {
std::string key = header.substr(0, split_pos);
std::string value = header.substr(split_pos + 2);
headers.emplace(key, value);
}
start = pos + 2;
}
return headers;
}
absl::Status HttpLoader::QueryResponseInfo(HINTERNET request_handle,
DWORD info_level,
std::string& info) {
DWORD buffer_size = 0;
BOOL query_result = HttpQueryInfoA(request_handle, info_level, nullptr,
&buffer_size, nullptr);
if (query_result) {
return absl::OkStatus();
}
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
info.resize(buffer_size);
query_result = HttpQueryInfoA(request_handle, info_level, info.data(),
&buffer_size, nullptr);
if (query_result) {
return absl::OkStatus();
}
}
return absl::InvalidArgumentError("Failed to query HTTP information.");
}
absl::Status HttpLoader::ParseUrl() {
const std::string& url = request_.url;
URL_COMPONENTSA url_components;
char schema[kSchemaMaximumLength];
char host_name[kHostNameMaximumLength];
std::string url_path;
url_path.resize(url.size());
memset(&url_components, 0, sizeof(URL_COMPONENTSA));
url_components.dwStructSize = sizeof(URL_COMPONENTSA);
url_components.lpszScheme = schema;
url_components.dwSchemeLength = kSchemaMaximumLength;
url_components.lpszHostName = host_name;
url_components.dwHostNameLength = kHostNameMaximumLength;
url_components.lpszUrlPath = url_path.data();
url_components.dwUrlPathLength = url.size();
if (!InternetCrackUrlA(url.data(), url.size(), 0, &url_components)) {
return absl::InvalidArgumentError("Invalid URL.");
}
schema_.assign(url_components.lpszScheme, url_components.dwSchemeLength);
if (!(schema_ == "http" || schema_ == "https")) {
return absl::InvalidArgumentError("URL supports HTTP and HTTPS only.");
}
if (schema_ == "https") {
is_secure_ = true;
}
host_.assign(url_components.lpszHostName, url_components.dwHostNameLength);
path_.assign(url_components.lpszUrlPath, url_components.dwUrlPathLength);
port_ = url_components.nPort;
return absl::OkStatus();
}
absl::Status HttpLoader::ConnectWebServer() {
internet_handle_ = InternetOpenA("Mozilla/5.0", /*Agent*/
INTERNET_OPEN_TYPE_PRECONFIG, /*Access Type*/
nullptr, /*Proxy*/
nullptr, /*Proxy bypass*/
0); /*Flags*/
if (internet_handle_ == nullptr) {
NEARBY_LOGS(ERROR) << "Failed to open internet with error "
<< GetLastError() << ".";
return absl::FailedPreconditionError(absl::StrCat(GetLastError()));
}
connect_handle_ = InternetConnectA(internet_handle_, /*Internet*/
host_.c_str(), /*Server name*/
port_, /*Port*/
nullptr, /*User name*/
nullptr, /*Password*/
INTERNET_SERVICE_HTTP, /*Service*/
0, /*Flags*/
0); /*Context*/
if (connect_handle_ == nullptr) {
NEARBY_LOGS(ERROR) << "Failed to connect remote web server with error "
<< GetLastError() << ".";
InternetCloseHandle(internet_handle_);
return absl::FailedPreconditionError(absl::StrCat(GetLastError()));
}
return absl::OkStatus();
}
absl::Status HttpLoader::SendRequest() {
DWORD flags = INTERNET_FLAG_NO_AUTO_REDIRECT;
if (is_secure_) {
flags |= INTERNET_FLAG_SECURE;
}
request_handle_ =
HttpOpenRequestA(connect_handle_, request_.method.c_str(), /*Method*/
path_.c_str(), /*Path*/
nullptr, /*HTTP version*/
nullptr, /*Referrer*/
nullptr, /*Accept types*/
flags, /*Internet options*/
0);
if (request_handle_ == nullptr) {
NEARBY_LOGS(ERROR)
<< "Failed to open request to remote web server with error "
<< GetLastError() << ".";
InternetCloseHandle(internet_handle_);
InternetCloseHandle(connect_handle_);
return absl::FailedPreconditionError(absl::StrCat(GetLastError()));
}
// Prepare headers
LPCSTR headers_ptr = nullptr;
DWORD headers_size = 0;
std::string request_headers;
for (const auto& header : request_.headers) {
if (header.second.empty()) {
continue;
}
request_headers.append(header.first + ": " + header.second + "\r\n");
}
if (!request_headers.empty()) {
headers_ptr = request_headers.data();
headers_size = request_headers.size();
}
LPVOID data_ptr = nullptr;
DWORD data_size = 0;
// Prepare request data
if (!request_.body.empty()) {
data_ptr = (LPVOID)(request_.body.data());
data_size = request_.body.size();
}
BOOL result = HttpSendRequestA(request_handle_, /*Request*/
headers_ptr, /*Headers*/
headers_size, /*Header size*/
data_ptr, /*Data*/
data_size); /*Data size*/
if (result == FALSE) {
NEARBY_LOGS(ERROR)
<< "Failed to send request to remote web server with error "
<< GetLastError() << ".";
InternetCloseHandle(request_handle_);
InternetCloseHandle(connect_handle_);
InternetCloseHandle(internet_handle_);
return absl::FailedPreconditionError(absl::StrCat(GetLastError()));
}
return absl::OkStatus();
}
absl::StatusOr<WebResponse> HttpLoader::ProcessResponse() {
absl::Status status;
WebResponse web_response;
auto status_code = QueryStatusCode(request_handle_);
if (!status_code.ok()) {
return absl::InternalError("Failed to read HTTP status");
}
web_response.status_code = status_code.value();
auto status_text = QueryStatusText(request_handle_);
if (!status_text.ok()) {
return absl::InternalError("Failed to read HTTP status");
}
web_response.status_text = status_text.value();
auto headers = QueryResponseHeaders(request_handle_);
if (!headers.ok()) {
headers.status();
}
web_response.headers = *headers;
// Get response data
char buffer[kReceiveBufferSize];
while (true) {
DWORD read_size;
BOOL read_result = InternetReadFile(request_handle_, buffer,
kReceiveBufferSize, &read_size);
if (read_result) {
if (read_size == 0) {
break;
} else {
// Append data to response
web_response.body.append(buffer, read_size);
}
} else {
NEARBY_LOGS(ERROR)
<< "Failed to read response from remote web server with error "
<< GetLastError() << ".";
InternetCloseHandle(request_handle_);
InternetCloseHandle(connect_handle_);
InternetCloseHandle(internet_handle_);
return absl::FailedPreconditionError(absl::StrCat(GetLastError()));
}
}
status = HTTPCodeToStatus(web_response.status_code, web_response.status_text);
if (!status.ok()) {
return status;
}
return web_response;
}
void HttpLoader::DisconnectWebServer() {
if (request_handle_ != nullptr) {
InternetCloseHandle(request_handle_);
request_handle_ = nullptr;
}
if (connect_handle_ != nullptr) {
InternetCloseHandle(connect_handle_);
connect_handle_ = nullptr;
}
if (internet_handle_ != nullptr) {
InternetCloseHandle(internet_handle_);
internet_handle_ = nullptr;
}
}
absl::Status HttpLoader::HTTPCodeToStatus(int status_code,
absl::string_view status_message) {
switch (status_code) {
case 400:
return absl::InvalidArgumentError(status_message);
case 401:
return absl::UnauthenticatedError(status_message);
case 403:
return absl::PermissionDeniedError(status_message);
case 404:
return absl::NotFoundError(status_message);
case 409:
return absl::AbortedError(status_message);
case 416:
return absl::OutOfRangeError(status_message);
case 429:
return absl::ResourceExhaustedError(status_message);
case 499:
return absl::CancelledError(status_message);
case 504:
return absl::DeadlineExceededError(status_message);
case 501:
return absl::UnimplementedError(status_message);
case 503:
return absl::UnavailableError(status_message);
default:
break;
}
if (status_code >= 200 && status_code < 300) {
return absl::OkStatus();
} else if (status_code >= 400 && status_code < 500) {
return absl::FailedPreconditionError(status_message);
} else if (status_code >= 500 && status_code < 600) {
return absl::InternalError(status_message);
}
return absl::UnknownError(status_message);
}
} // namespace windows
} // namespace nearby