yuzu/src/web_service/web_backend.cpp
Lioncash 39c8d18feb core/CMakeLists: Make some warnings errors
Makes our error coverage a little more consistent across the board by
applying it to Linux side of things as well. This also makes it more
consistent with the warning settings in other libraries in the project.

This also updates httplib to 0.7.9, as there are several warning
cleanups made that allow us to enable several warnings as errors.
2020-10-13 13:16:49 -04:00

206 lines
7.4 KiB
C++

// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cstdlib>
#include <mutex>
#include <string>
#include <LUrlParser.h>
#include <fmt/format.h>
#include <httplib.h>
#include "common/logging/log.h"
#include "web_service/web_backend.h"
#include "web_service/web_result.h"
namespace WebService {
constexpr std::array<const char, 1> API_VERSION{'1'};
constexpr int HTTP_PORT = 80;
constexpr int HTTPS_PORT = 443;
constexpr std::size_t TIMEOUT_SECONDS = 30;
struct Client::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
std::lock_guard lock{jwt_cache.mutex};
if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
}
/// A generic function handles POST, GET and DELETE request together
WebResult GenericRequest(const std::string& method, const std::string& path,
const std::string& data, bool allow_anonymous,
const std::string& accept) {
if (jwt.empty()) {
UpdateJWT();
}
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return WebResult{WebResult::Code::CredentialsMissing, "Credentials needed", ""};
}
auto result = GenericRequest(method, path, data, accept, jwt);
if (result.result_string == "401") {
// Try again with new JWT
UpdateJWT();
result = GenericRequest(method, path, data, accept, jwt);
}
return result;
}
/**
* A generic function with explicit authentication method specified
* JWT is used if the jwt parameter is not empty
* username + token is used if jwt is empty but username and token are
* not empty anonymous if all of jwt, username and token are empty
*/
WebResult GenericRequest(const std::string& method, const std::string& path,
const std::string& data, const std::string& accept,
const std::string& jwt = "", const std::string& username = "",
const std::string& token = "") {
if (cli == nullptr) {
const auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
int port{};
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = HTTP_PORT;
}
} else if (parsedUrl.m_Scheme == "https") {
if (!parsedUrl.GetPort(&port)) {
port = HTTPS_PORT;
}
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
return WebResult{WebResult::Code::InvalidURL, "Bad URL scheme", ""};
}
cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
}
cli->set_connection_timeout(TIMEOUT_SECONDS);
cli->set_read_timeout(TIMEOUT_SECONDS);
cli->set_write_timeout(TIMEOUT_SECONDS);
httplib::Headers params;
if (!jwt.empty()) {
params = {
{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
};
} else if (!username.empty()) {
params = {
{std::string("x-username"), username},
{std::string("x-token"), token},
};
}
params.emplace(std::string("api-version"),
std::string(API_VERSION.begin(), API_VERSION.end()));
if (method != "GET") {
params.emplace(std::string("Content-Type"), std::string("application/json"));
}
httplib::Request request;
request.method = method;
request.path = path;
request.headers = params;
request.body = data;
httplib::Response response;
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
return WebResult{WebResult::Code::LibError, "Null response", ""};
}
if (response.status >= 400) {
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
response.status);
return WebResult{WebResult::Code::HttpError, std::to_string(response.status), ""};
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end()) {
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
return WebResult{WebResult::Code::WrongContent, "", ""};
}
if (content_type->second.find(accept) == std::string::npos) {
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
content_type->second);
return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
}
return WebResult{WebResult::Code::Success, "", response.body};
}
// Retrieve a new JWT from given username and token
void UpdateJWT() {
if (username.empty() || token.empty()) {
return;
}
auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token);
if (result.result_code != WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
} else {
std::lock_guard lock{jwt_cache.mutex};
jwt_cache.username = username;
jwt_cache.token = token;
jwt_cache.jwt = jwt = result.returned_data;
}
}
std::string host;
std::string username;
std::string token;
std::string jwt;
std::unique_ptr<httplib::Client> cli;
struct JWTCache {
std::mutex mutex;
std::string username;
std::string token;
std::string jwt;
};
static inline JWTCache jwt_cache;
};
Client::Client(std::string host, std::string username, std::string token)
: impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
Client::~Client() = default;
WebResult Client::PostJson(const std::string& path, const std::string& data, bool allow_anonymous) {
return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json");
}
WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json");
}
WebResult Client::DeleteJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json");
}
WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain");
}
WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png");
}
WebResult Client::GetExternalJWT(const std::string& audience) {
return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false,
"text/html");
}
} // namespace WebService