mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
412 lines
12 KiB
C++
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
|