2020-10-04 18:48:47 +00:00
|
|
|
#include "Common/Net/HTTPClient.h"
|
2020-09-29 10:19:22 +00:00
|
|
|
|
2020-08-15 18:53:08 +00:00
|
|
|
#include "Common/TimeUtil.h"
|
2020-09-29 10:19:22 +00:00
|
|
|
#include "Common/StringUtils.h"
|
2023-07-18 13:13:44 +00:00
|
|
|
#include "Common/System/OSD.h"
|
2012-06-03 17:01:08 +00:00
|
|
|
|
|
|
|
#ifndef _WIN32
|
2021-01-09 22:45:03 +00:00
|
|
|
#include <netinet/in.h>
|
2012-06-03 17:01:08 +00:00
|
|
|
#include <arpa/inet.h>
|
2016-07-04 14:34:40 +00:00
|
|
|
#include <sys/select.h>
|
2012-06-03 17:01:08 +00:00
|
|
|
#include <sys/socket.h>
|
2016-07-04 14:34:40 +00:00
|
|
|
#include <sys/types.h>
|
2021-01-09 22:45:03 +00:00
|
|
|
#include <netdb.h>
|
2012-06-03 17:01:08 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
#define closesocket close
|
|
|
|
#else
|
2021-01-09 22:45:03 +00:00
|
|
|
#ifndef NOMINMAX
|
|
|
|
#define NOMINMAX
|
|
|
|
#endif
|
2012-06-03 17:01:08 +00:00
|
|
|
#include <winsock2.h>
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
#include <io.h>
|
|
|
|
#endif
|
|
|
|
|
2016-07-04 14:34:40 +00:00
|
|
|
#include <cmath>
|
2020-09-29 11:02:02 +00:00
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
2012-06-03 17:01:08 +00:00
|
|
|
|
2020-10-04 18:48:47 +00:00
|
|
|
#include "Common/Net/Resolve.h"
|
|
|
|
#include "Common/Net/URL.h"
|
2013-05-31 21:04:42 +00:00
|
|
|
|
2020-10-04 18:48:47 +00:00
|
|
|
#include "Common/File/FileDescriptor.h"
|
2023-09-23 18:10:45 +00:00
|
|
|
#include "Common/SysError.h"
|
2020-10-01 11:05:04 +00:00
|
|
|
#include "Common/Thread/ThreadUtil.h"
|
|
|
|
#include "Common/Data/Encoding/Compression.h"
|
2021-05-01 15:36:25 +00:00
|
|
|
#include "Common/Net/NetBuffer.h"
|
2020-08-15 13:51:41 +00:00
|
|
|
#include "Common/Log.h"
|
|
|
|
|
2012-06-03 17:01:08 +00:00
|
|
|
namespace net {
|
|
|
|
|
|
|
|
Connection::~Connection() {
|
2012-10-30 12:20:55 +00:00
|
|
|
Disconnect();
|
2021-05-01 05:59:41 +00:00
|
|
|
if (resolved_ != nullptr)
|
2013-06-09 01:59:11 +00:00
|
|
|
DNSResolveFree(resolved_);
|
2012-06-03 17:01:08 +00:00
|
|
|
}
|
|
|
|
|
2013-03-21 22:19:27 +00:00
|
|
|
// For whatever crazy reason, htons isn't available on android x86 on the build server. so here we go.
|
|
|
|
|
|
|
|
// TODO: Fix for big-endian
|
|
|
|
inline unsigned short myhtons(unsigned short x) {
|
|
|
|
return (x >> 8) | (x << 8);
|
|
|
|
}
|
|
|
|
|
2020-12-19 18:34:43 +00:00
|
|
|
const char *DNSTypeAsString(DNSType type) {
|
|
|
|
switch (type) {
|
|
|
|
case DNSType::IPV4:
|
|
|
|
return "IPV4";
|
|
|
|
case DNSType::IPV6:
|
|
|
|
return "IPV6";
|
|
|
|
case DNSType::ANY:
|
|
|
|
return "ANY";
|
|
|
|
default:
|
|
|
|
return "N/A";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-01 00:06:54 +00:00
|
|
|
bool Connection::Resolve(const char *host, int port, DNSType type) {
|
2013-06-21 23:08:57 +00:00
|
|
|
if ((intptr_t)sock_ != -1) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::IO, "Resolve: Already have a socket");
|
2013-06-21 23:08:57 +00:00
|
|
|
return false;
|
|
|
|
}
|
2016-04-09 16:20:31 +00:00
|
|
|
if (!host || port < 1 || port > 65535) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::IO, "Resolve: Invalid host or port (%d)", port);
|
2016-04-09 16:20:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
2013-06-21 23:08:57 +00:00
|
|
|
|
2012-10-30 12:20:55 +00:00
|
|
|
host_ = host;
|
|
|
|
port_ = port;
|
|
|
|
|
2016-04-09 16:20:31 +00:00
|
|
|
char port_str[16];
|
2013-06-09 01:59:11 +00:00
|
|
|
snprintf(port_str, sizeof(port_str), "%d", port);
|
2013-11-18 15:27:39 +00:00
|
|
|
|
2013-06-09 01:59:11 +00:00
|
|
|
std::string err;
|
2018-05-01 00:06:54 +00:00
|
|
|
if (!net::DNSResolve(host, port_str, &resolved_, err, type)) {
|
2024-07-14 12:42:59 +00:00
|
|
|
WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type));
|
2020-12-19 18:34:43 +00:00
|
|
|
// Zero port so that future calls fail.
|
2013-03-01 07:59:32 +00:00
|
|
|
port_ = 0;
|
|
|
|
return false;
|
|
|
|
}
|
2013-06-09 01:59:11 +00:00
|
|
|
|
2012-10-30 12:20:55 +00:00
|
|
|
return true;
|
2012-06-03 17:01:08 +00:00
|
|
|
}
|
|
|
|
|
2023-09-17 15:37:45 +00:00
|
|
|
static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) {
|
|
|
|
switch (info->ai_family) {
|
|
|
|
case AF_INET:
|
|
|
|
case AF_INET6:
|
2023-09-23 18:10:45 +00:00
|
|
|
inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize);
|
2023-09-17 15:37:45 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-22 07:00:52 +00:00
|
|
|
bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
|
2013-06-25 02:38:57 +00:00
|
|
|
if (port_ <= 0) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::IO, "Bad port");
|
2013-06-25 02:38:57 +00:00
|
|
|
return false;
|
|
|
|
}
|
2016-07-04 14:34:40 +00:00
|
|
|
sock_ = -1;
|
2012-10-30 12:20:55 +00:00
|
|
|
|
2013-12-12 21:29:25 +00:00
|
|
|
for (int tries = maxTries; tries > 0; --tries) {
|
2016-07-04 14:34:40 +00:00
|
|
|
std::vector<uintptr_t> sockets;
|
|
|
|
fd_set fds;
|
|
|
|
int maxfd = 1;
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) {
|
2018-05-01 00:06:54 +00:00
|
|
|
if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6)
|
2013-06-09 01:59:11 +00:00
|
|
|
continue;
|
|
|
|
|
2018-05-01 00:06:54 +00:00
|
|
|
int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
2016-07-04 14:34:40 +00:00
|
|
|
if ((intptr_t)sock == -1) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::IO, "Bad socket");
|
2016-07-04 14:34:40 +00:00
|
|
|
continue;
|
|
|
|
}
|
2024-01-23 09:58:42 +00:00
|
|
|
// Windows sockets aren't limited by socket number, just by count, so checking FD_SETSIZE there is wrong.
|
|
|
|
#if !PPSSPP_PLATFORM(WINDOWS)
|
|
|
|
if (sock >= FD_SETSIZE) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::IO, "Socket doesn't fit in FD_SET: %d We probably have a leak.", sock);
|
2024-01-23 09:58:42 +00:00
|
|
|
closesocket(sock);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
2016-07-04 14:34:40 +00:00
|
|
|
fd_util::SetNonBlocking(sock, true);
|
|
|
|
|
|
|
|
// Start trying to connect (async with timeout.)
|
2023-09-17 15:13:18 +00:00
|
|
|
errno = 0;
|
|
|
|
if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) {
|
2023-09-23 18:10:45 +00:00
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
|
|
int errorCode = WSAGetLastError();
|
|
|
|
std::string errorString = GetStringErrorMsg(errorCode);
|
|
|
|
bool unreachable = errorCode == WSAENETUNREACH;
|
|
|
|
bool inProgress = errorCode == WSAEINPROGRESS || errorCode == WSAEWOULDBLOCK;
|
|
|
|
#else
|
|
|
|
int errorCode = errno;
|
|
|
|
std::string errorString = strerror(errno);
|
|
|
|
bool unreachable = errorCode == ENETUNREACH;
|
|
|
|
bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK;
|
|
|
|
#endif
|
|
|
|
if (!inProgress) {
|
|
|
|
char addrStr[128]{};
|
2023-09-17 15:37:45 +00:00
|
|
|
FormatAddr(addrStr, sizeof(addrStr), possible);
|
2023-09-23 18:10:45 +00:00
|
|
|
if (!unreachable) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str());
|
2023-09-17 15:37:45 +00:00
|
|
|
} else {
|
2024-07-14 12:42:59 +00:00
|
|
|
INFO_LOG(Log::HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr);
|
2023-09-17 15:37:45 +00:00
|
|
|
}
|
2023-09-17 15:23:58 +00:00
|
|
|
closesocket(sock);
|
2023-09-17 15:13:18 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2016-07-04 14:34:40 +00:00
|
|
|
sockets.push_back(sock);
|
|
|
|
FD_SET(sock, &fds);
|
|
|
|
if (maxfd < sock + 1) {
|
|
|
|
maxfd = sock + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-22 07:00:52 +00:00
|
|
|
int selectResult = 0;
|
|
|
|
long timeoutHalfSeconds = floor(2 * timeout);
|
|
|
|
while (timeoutHalfSeconds >= 0 && selectResult == 0) {
|
2024-10-10 09:55:07 +00:00
|
|
|
struct timeval tv{};
|
2017-03-22 07:00:52 +00:00
|
|
|
tv.tv_sec = 0;
|
|
|
|
if (timeoutHalfSeconds > 0) {
|
|
|
|
// Wait up to 0.5 seconds between cancel checks.
|
|
|
|
tv.tv_usec = 500000;
|
|
|
|
} else {
|
|
|
|
// Wait the remaining <= 0.5 seconds. Possibly 0, but that's okay.
|
|
|
|
tv.tv_usec = (timeout - floor(2 * timeout) / 2) * 1000000.0;
|
|
|
|
}
|
|
|
|
--timeoutHalfSeconds;
|
|
|
|
|
|
|
|
selectResult = select(maxfd, nullptr, &fds, nullptr, &tv);
|
|
|
|
if (cancelConnect && *cancelConnect) {
|
2024-07-14 12:42:59 +00:00
|
|
|
WARN_LOG(Log::HTTP, "connect: cancelled (1)");
|
2017-03-22 07:00:52 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (selectResult > 0) {
|
2016-07-04 14:34:40 +00:00
|
|
|
// Something connected. Pick the first one that did (if multiple.)
|
|
|
|
for (int sock : sockets) {
|
|
|
|
if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) {
|
|
|
|
sock_ = sock;
|
|
|
|
} else {
|
|
|
|
closesocket(sock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Great, now we're good to go.
|
|
|
|
return true;
|
2024-01-23 09:58:42 +00:00
|
|
|
} else {
|
|
|
|
// Fail. Close all the sockets.
|
|
|
|
for (int sock : sockets) {
|
|
|
|
closesocket(sock);
|
|
|
|
}
|
2013-06-09 01:59:11 +00:00
|
|
|
}
|
2017-03-22 07:00:52 +00:00
|
|
|
|
|
|
|
if (cancelConnect && *cancelConnect) {
|
2024-07-14 12:42:59 +00:00
|
|
|
WARN_LOG(Log::HTTP, "connect: cancelled (2)");
|
2017-03-22 07:00:52 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-11-21 14:25:02 +00:00
|
|
|
sleep_ms(1, "connect");
|
2012-10-30 12:20:55 +00:00
|
|
|
}
|
2013-06-25 02:38:57 +00:00
|
|
|
|
2016-07-04 14:34:40 +00:00
|
|
|
// Nothing connected, unfortunately.
|
2013-06-21 23:08:57 +00:00
|
|
|
return false;
|
2012-06-03 17:01:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Connection::Disconnect() {
|
2012-10-30 12:20:55 +00:00
|
|
|
if ((intptr_t)sock_ != -1) {
|
|
|
|
closesocket(sock_);
|
|
|
|
sock_ = -1;
|
|
|
|
}
|
2012-06-03 17:01:08 +00:00
|
|
|
}
|
|
|
|
|
2012-10-30 12:20:55 +00:00
|
|
|
} // net
|
2012-06-03 17:01:08 +00:00
|
|
|
|
|
|
|
namespace http {
|
|
|
|
|
2014-11-24 00:12:54 +00:00
|
|
|
// TODO: do something sane here
|
2023-07-14 13:24:34 +00:00
|
|
|
constexpr const char *DEFAULT_USERAGENT = "PPSSPP";
|
2023-09-24 10:58:43 +00:00
|
|
|
constexpr const char *HTTP_VERSION = "1.1";
|
2013-06-04 20:05:17 +00:00
|
|
|
|
2014-11-24 00:12:54 +00:00
|
|
|
Client::Client() {
|
2021-05-01 06:12:42 +00:00
|
|
|
userAgent_ = DEFAULT_USERAGENT;
|
2012-06-03 17:01:08 +00:00
|
|
|
}
|
2013-06-04 20:05:17 +00:00
|
|
|
|
2012-06-03 17:01:08 +00:00
|
|
|
Client::~Client() {
|
2013-05-31 21:04:42 +00:00
|
|
|
Disconnect();
|
2012-06-03 17:01:08 +00:00
|
|
|
}
|
|
|
|
|
2019-06-23 20:12:13 +00:00
|
|
|
// Ignores line folding (deprecated), but respects field combining.
|
|
|
|
// Don't use for Set-Cookie, which is a special header per RFC 7230.
|
|
|
|
bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::string &header, std::string *value) {
|
|
|
|
std::string search = header + ":";
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
value->clear();
|
|
|
|
for (const std::string &line : responseHeaders) {
|
|
|
|
auto stripped = StripSpaces(line);
|
|
|
|
if (startsWithNoCase(stripped, search)) {
|
|
|
|
size_t value_pos = search.length();
|
|
|
|
size_t after_white = stripped.find_first_not_of(" \t", value_pos);
|
|
|
|
if (after_white != stripped.npos)
|
|
|
|
value_pos = after_white;
|
|
|
|
|
|
|
|
if (!found)
|
|
|
|
*value = stripped.substr(value_pos);
|
|
|
|
else
|
|
|
|
*value += "," + stripped.substr(value_pos);
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
2013-06-04 20:05:17 +00:00
|
|
|
|
2024-05-28 09:16:10 +00:00
|
|
|
static bool DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength) {
|
|
|
|
_dbg_assert_(outbuffer->empty());
|
2013-11-29 15:31:19 +00:00
|
|
|
int dechunkedBytes = 0;
|
2013-06-04 20:05:17 +00:00
|
|
|
while (true) {
|
|
|
|
std::string line;
|
|
|
|
inbuffer->TakeLineCRLF(&line);
|
|
|
|
if (!line.size())
|
2024-05-28 09:16:10 +00:00
|
|
|
return false;
|
2022-12-11 05:09:50 +00:00
|
|
|
unsigned int chunkSize = 0;
|
2024-05-28 09:16:10 +00:00
|
|
|
if (sscanf(line.c_str(), "%x", &chunkSize) != 1) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-06-04 20:05:17 +00:00
|
|
|
if (chunkSize) {
|
|
|
|
std::string data;
|
|
|
|
inbuffer->Take(chunkSize, &data);
|
|
|
|
outbuffer->Append(data);
|
|
|
|
} else {
|
|
|
|
// a zero size chunk should mean the end.
|
|
|
|
inbuffer->clear();
|
2024-05-28 09:16:10 +00:00
|
|
|
return true;
|
2013-06-04 20:05:17 +00:00
|
|
|
}
|
2013-11-29 15:31:19 +00:00
|
|
|
dechunkedBytes += chunkSize;
|
2013-06-04 20:05:17 +00:00
|
|
|
inbuffer->Skip(2);
|
|
|
|
}
|
2024-05-28 09:16:10 +00:00
|
|
|
// Unreachable
|
|
|
|
return true;
|
2013-06-04 20:05:17 +00:00
|
|
|
}
|
2012-06-03 17:01:08 +00:00
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, net::RequestProgress *progress) {
|
2014-11-24 00:12:54 +00:00
|
|
|
const char *otherHeaders =
|
|
|
|
"Accept-Encoding: gzip\r\n";
|
2021-08-22 15:17:26 +00:00
|
|
|
int err = SendRequest("GET", req, otherHeaders, progress);
|
2014-11-24 00:12:54 +00:00
|
|
|
if (err < 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2021-05-01 15:36:25 +00:00
|
|
|
net::Buffer readbuf;
|
2021-05-01 17:19:27 +00:00
|
|
|
int code = ReadResponseHeaders(&readbuf, responseHeaders, progress);
|
2014-11-24 00:12:54 +00:00
|
|
|
if (code < 0) {
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
2021-05-01 17:19:27 +00:00
|
|
|
err = ReadResponseEntity(&readbuf, responseHeaders, output, progress);
|
2014-11-24 00:12:54 +00:00
|
|
|
if (err < 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::GET(const RequestParams &req, Buffer *output, net::RequestProgress *progress) {
|
2017-03-11 05:23:49 +00:00
|
|
|
std::vector<std::string> responseHeaders;
|
2021-08-22 15:17:26 +00:00
|
|
|
int code = GET(req, output, responseHeaders, progress);
|
2017-03-11 05:23:49 +00:00
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress) {
|
2014-11-25 16:49:05 +00:00
|
|
|
char otherHeaders[2048];
|
|
|
|
if (mime.empty()) {
|
|
|
|
snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\n", (long long)data.size());
|
|
|
|
} else {
|
|
|
|
snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\nContent-Type: %s\r\n", (long long)data.size(), mime.c_str());
|
2014-11-24 00:12:54 +00:00
|
|
|
}
|
2021-08-22 15:17:26 +00:00
|
|
|
int err = SendRequestWithData("POST", req, data, otherHeaders, progress);
|
2014-11-25 16:49:05 +00:00
|
|
|
if (err < 0) {
|
|
|
|
return err;
|
2014-11-24 00:12:54 +00:00
|
|
|
}
|
|
|
|
|
2021-05-01 15:36:25 +00:00
|
|
|
net::Buffer readbuf;
|
2014-11-25 16:49:05 +00:00
|
|
|
std::vector<std::string> responseHeaders;
|
|
|
|
int code = ReadResponseHeaders(&readbuf, responseHeaders, progress);
|
|
|
|
if (code < 0) {
|
|
|
|
return code;
|
2014-11-24 00:12:54 +00:00
|
|
|
}
|
|
|
|
|
2014-11-25 16:49:05 +00:00
|
|
|
err = ReadResponseEntity(&readbuf, responseHeaders, output, progress);
|
|
|
|
if (err < 0) {
|
|
|
|
return err;
|
2014-11-24 00:12:54 +00:00
|
|
|
}
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress) {
|
2021-08-22 15:17:26 +00:00
|
|
|
return POST(req, data, "", output, progress);
|
2014-11-24 00:12:54 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress) {
|
2021-08-22 15:17:26 +00:00
|
|
|
return SendRequestWithData(method, req, "", otherHeaders, progress);
|
2014-11-25 16:49:05 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress) {
|
2023-07-18 13:52:14 +00:00
|
|
|
progress->Update(0, 0, false);
|
2013-11-18 15:27:39 +00:00
|
|
|
|
2021-05-01 15:36:25 +00:00
|
|
|
net::Buffer buffer;
|
2013-06-04 20:05:17 +00:00
|
|
|
const char *tpl =
|
2014-11-25 16:37:59 +00:00
|
|
|
"%s %s HTTP/%s\r\n"
|
2013-06-04 20:05:17 +00:00
|
|
|
"Host: %s\r\n"
|
2014-11-25 16:37:59 +00:00
|
|
|
"User-Agent: %s\r\n"
|
2021-08-22 15:17:26 +00:00
|
|
|
"Accept: %s\r\n"
|
2013-06-04 20:05:17 +00:00
|
|
|
"Connection: close\r\n"
|
2014-11-24 00:12:54 +00:00
|
|
|
"%s"
|
2013-06-04 20:05:17 +00:00
|
|
|
"\r\n";
|
|
|
|
|
2014-12-31 17:36:00 +00:00
|
|
|
buffer.Printf(tpl,
|
2023-09-24 10:58:43 +00:00
|
|
|
method, req.resource.c_str(), HTTP_VERSION,
|
2014-12-31 17:36:00 +00:00
|
|
|
host_.c_str(),
|
2021-05-01 06:12:42 +00:00
|
|
|
userAgent_.c_str(),
|
2021-08-22 15:17:26 +00:00
|
|
|
req.acceptMime,
|
2014-12-31 17:36:00 +00:00
|
|
|
otherHeaders ? otherHeaders : "");
|
2014-11-25 16:49:05 +00:00
|
|
|
buffer.Append(data);
|
2021-05-01 17:19:27 +00:00
|
|
|
bool flushed = buffer.FlushSocket(sock(), dataTimeout_, progress->cancelled);
|
2013-05-31 21:04:42 +00:00
|
|
|
if (!flushed) {
|
|
|
|
return -1; // TODO error code.
|
|
|
|
}
|
2014-11-24 00:12:54 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2012-06-03 17:01:08 +00:00
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, net::RequestProgress *progress) {
|
2013-06-04 20:05:17 +00:00
|
|
|
// Snarf all the data we can into RAM. A little unsafe but hey.
|
2020-07-05 04:17:59 +00:00
|
|
|
static constexpr float CANCEL_INTERVAL = 0.25f;
|
|
|
|
bool ready = false;
|
2021-05-01 16:37:36 +00:00
|
|
|
double endTimeout = time_now_d() + dataTimeout_;
|
2020-07-05 04:17:59 +00:00
|
|
|
while (!ready) {
|
2021-05-01 17:19:27 +00:00
|
|
|
if (progress->cancelled && *progress->cancelled)
|
2020-07-05 04:17:59 +00:00
|
|
|
return -1;
|
|
|
|
ready = fd_util::WaitUntilReady(sock(), CANCEL_INTERVAL, false);
|
2021-05-01 16:37:36 +00:00
|
|
|
if (!ready && time_now_d() > endTimeout) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "HTTP headers timed out");
|
2021-05-01 16:37:36 +00:00
|
|
|
return -1;
|
2020-07-05 04:17:59 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// Let's hope all the headers are available in a single packet...
|
2014-11-24 00:12:54 +00:00
|
|
|
if (readbuf->Read(sock(), 4096) < 0) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Failed to read HTTP headers :(");
|
2013-05-31 21:04:42 +00:00
|
|
|
return -1;
|
2013-11-29 15:31:19 +00:00
|
|
|
}
|
2012-06-03 17:01:08 +00:00
|
|
|
|
2013-06-02 21:44:28 +00:00
|
|
|
// Grab the first header line that contains the http code.
|
|
|
|
|
2013-06-04 20:05:17 +00:00
|
|
|
std::string line;
|
2014-11-24 00:12:54 +00:00
|
|
|
readbuf->TakeLineCRLF(&line);
|
2013-06-25 02:40:24 +00:00
|
|
|
|
|
|
|
int code;
|
|
|
|
size_t code_pos = line.find(' ');
|
|
|
|
if (code_pos != line.npos) {
|
|
|
|
code_pos = line.find_first_not_of(' ', code_pos);
|
|
|
|
}
|
2013-11-29 16:33:56 +00:00
|
|
|
|
2013-06-25 02:40:24 +00:00
|
|
|
if (code_pos != line.npos) {
|
|
|
|
code = atoi(&line[code_pos]);
|
|
|
|
} else {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Could not parse HTTP status code: %s", line.c_str());
|
2013-06-25 02:40:24 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2013-06-04 20:05:17 +00:00
|
|
|
|
|
|
|
while (true) {
|
2014-11-24 00:12:54 +00:00
|
|
|
int sz = readbuf->TakeLineCRLF(&line);
|
2023-07-23 21:20:30 +00:00
|
|
|
if (!sz || sz < 0)
|
2013-06-04 20:05:17 +00:00
|
|
|
break;
|
2014-11-24 00:12:54 +00:00
|
|
|
responseHeaders.push_back(line);
|
|
|
|
}
|
|
|
|
|
2014-11-25 16:49:05 +00:00
|
|
|
if (responseHeaders.size() == 0) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "No HTTP response headers");
|
2014-11-25 16:49:05 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-11-24 00:12:54 +00:00
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, net::RequestProgress *progress) {
|
2023-07-23 09:05:08 +00:00
|
|
|
_dbg_assert_(progress->cancelled);
|
|
|
|
|
2014-11-24 00:12:54 +00:00
|
|
|
bool gzip = false;
|
|
|
|
bool chunked = false;
|
|
|
|
int contentLength = 0;
|
|
|
|
for (std::string line : responseHeaders) {
|
2014-11-25 08:30:13 +00:00
|
|
|
if (startsWithNoCase(line, "Content-Length:")) {
|
2013-06-25 02:40:24 +00:00
|
|
|
size_t size_pos = line.find_first_of(' ');
|
|
|
|
if (size_pos != line.npos) {
|
|
|
|
size_pos = line.find_first_not_of(' ', size_pos);
|
|
|
|
}
|
|
|
|
if (size_pos != line.npos) {
|
|
|
|
contentLength = atoi(&line[size_pos]);
|
|
|
|
chunked = false;
|
|
|
|
}
|
2014-11-25 08:30:13 +00:00
|
|
|
} else if (startsWithNoCase(line, "Content-Encoding:")) {
|
|
|
|
// TODO: Case folding...
|
2013-06-04 20:05:17 +00:00
|
|
|
if (line.find("gzip") != std::string::npos) {
|
|
|
|
gzip = true;
|
|
|
|
}
|
2014-11-25 08:30:13 +00:00
|
|
|
} else if (startsWithNoCase(line, "Transfer-Encoding:")) {
|
|
|
|
// TODO: Case folding...
|
2013-06-25 02:40:24 +00:00
|
|
|
if (line.find("chunked") != std::string::npos) {
|
|
|
|
chunked = true;
|
|
|
|
}
|
2013-06-04 20:05:17 +00:00
|
|
|
}
|
|
|
|
}
|
2013-06-02 21:44:28 +00:00
|
|
|
|
2020-02-06 23:09:42 +00:00
|
|
|
if (contentLength < 0) {
|
2024-07-14 12:42:59 +00:00
|
|
|
WARN_LOG(Log::HTTP, "Negative content length %d", contentLength);
|
2020-02-06 23:09:42 +00:00
|
|
|
// Just sanity checking...
|
|
|
|
contentLength = 0;
|
|
|
|
}
|
|
|
|
|
2023-07-18 13:13:44 +00:00
|
|
|
if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress))
|
|
|
|
return -1;
|
2013-11-18 15:27:39 +00:00
|
|
|
|
2013-06-04 20:05:17 +00:00
|
|
|
// output now contains the rest of the reply. Dechunk it.
|
2021-05-01 17:59:15 +00:00
|
|
|
if (!output->IsVoid()) {
|
|
|
|
if (chunked) {
|
2024-05-28 09:16:10 +00:00
|
|
|
if (!DeChunk(readbuf, output, contentLength)) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Bad chunked data, couldn't read chunk size");
|
2024-05-28 09:16:10 +00:00
|
|
|
progress->Update(0, 0, true);
|
|
|
|
return -1;
|
|
|
|
}
|
2021-05-01 17:59:15 +00:00
|
|
|
} else {
|
|
|
|
output->Append(*readbuf);
|
|
|
|
}
|
2013-06-04 20:05:17 +00:00
|
|
|
|
2021-05-01 17:59:15 +00:00
|
|
|
// If it's gzipped, we decompress it and put it back in the buffer.
|
|
|
|
if (gzip) {
|
|
|
|
std::string compressed, decompressed;
|
|
|
|
output->TakeAll(&compressed);
|
|
|
|
bool result = decompress_string(compressed, &decompressed);
|
|
|
|
if (!result) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Error decompressing using zlib");
|
2023-07-18 13:52:14 +00:00
|
|
|
progress->Update(0, 0, true);
|
2021-05-01 17:59:15 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
output->Append(decompressed);
|
2013-06-04 20:05:17 +00:00
|
|
|
}
|
|
|
|
}
|
2012-06-03 17:01:08 +00:00
|
|
|
|
2023-07-18 13:52:14 +00:00
|
|
|
progress->Update(contentLength, contentLength, true);
|
2014-11-24 00:12:54 +00:00
|
|
|
return 0;
|
2013-03-01 08:04:42 +00:00
|
|
|
}
|
|
|
|
|
2024-01-19 12:44:49 +00:00
|
|
|
HTTPRequest::HTTPRequest(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, std::string_view name)
|
2023-07-21 20:04:05 +00:00
|
|
|
: Request(method, url, name, &cancelled_, progressBarMode), postData_(postData), postMime_(postMime), outfile_(outfile) {
|
2013-05-31 21:04:42 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 20:04:05 +00:00
|
|
|
HTTPRequest::~HTTPRequest() {
|
2024-10-10 08:46:52 +00:00
|
|
|
g_OSD.RemoveProgressBar(url_, !failed_, 0.5f);
|
2023-07-18 13:52:14 +00:00
|
|
|
|
2020-08-15 13:51:41 +00:00
|
|
|
_assert_msg_(joined_, "Download destructed without join");
|
2020-06-28 08:03:03 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 20:04:05 +00:00
|
|
|
void HTTPRequest::Start() {
|
2024-10-10 08:46:52 +00:00
|
|
|
thread_ = std::thread([this] { Do(); });
|
2013-05-31 21:04:42 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 20:04:05 +00:00
|
|
|
void HTTPRequest::Join() {
|
2020-06-28 16:34:00 +00:00
|
|
|
if (joined_) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Already joined thread!");
|
2020-06-28 16:34:00 +00:00
|
|
|
}
|
2020-06-28 08:03:03 +00:00
|
|
|
thread_.join();
|
|
|
|
joined_ = true;
|
2013-06-26 17:24:49 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 20:04:05 +00:00
|
|
|
void HTTPRequest::SetFailed(int code) {
|
2013-06-25 21:27:45 +00:00
|
|
|
failed_ = true;
|
2023-07-18 13:52:14 +00:00
|
|
|
progress_.Update(0, 0, true);
|
2015-07-19 15:14:48 +00:00
|
|
|
completed_ = true;
|
2013-06-25 21:27:45 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 20:04:05 +00:00
|
|
|
int HTTPRequest::Perform(const std::string &url) {
|
2019-06-23 19:04:59 +00:00
|
|
|
Url fileUrl(url);
|
2013-05-31 21:04:42 +00:00
|
|
|
if (!fileUrl.Valid()) {
|
2019-06-23 19:04:59 +00:00
|
|
|
return -1;
|
2013-05-31 21:04:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
http::Client client;
|
2023-07-14 13:24:34 +00:00
|
|
|
if (!userAgent_.empty()) {
|
|
|
|
client.SetUserAgent(userAgent_);
|
|
|
|
}
|
|
|
|
|
2014-12-31 17:36:51 +00:00
|
|
|
if (!client.Resolve(fileUrl.Host().c_str(), fileUrl.Port())) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Failed resolving %s", url.c_str());
|
2019-06-23 19:04:59 +00:00
|
|
|
return -1;
|
2013-06-21 23:08:57 +00:00
|
|
|
}
|
2013-06-25 21:27:45 +00:00
|
|
|
|
|
|
|
if (cancelled_) {
|
2019-06-23 19:04:59 +00:00
|
|
|
return -1;
|
2013-06-25 21:27:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-23 19:04:59 +00:00
|
|
|
if (!client.Connect(2, 20.0, &cancelled_)) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Failed connecting to server or cancelled.");
|
2019-06-23 19:04:59 +00:00
|
|
|
return -1;
|
2013-05-31 21:04:42 +00:00
|
|
|
}
|
2013-06-25 21:27:45 +00:00
|
|
|
|
|
|
|
if (cancelled_) {
|
2019-06-23 19:04:59 +00:00
|
|
|
return -1;
|
2013-06-25 21:27:45 +00:00
|
|
|
}
|
|
|
|
|
2021-08-22 15:29:48 +00:00
|
|
|
RequestParams req(fileUrl.Resource(), acceptMime_);
|
2023-06-17 20:31:01 +00:00
|
|
|
if (method_ == RequestMethod::GET) {
|
|
|
|
return client.GET(req, &buffer_, responseHeaders_, &progress_);
|
|
|
|
} else {
|
|
|
|
return client.POST(req, postData_, postMime_, &buffer_, &progress_);
|
|
|
|
}
|
2019-06-23 19:04:59 +00:00
|
|
|
}
|
|
|
|
|
2024-05-28 09:16:10 +00:00
|
|
|
std::string HTTPRequest::RedirectLocation(const std::string &baseUrl) const {
|
2019-06-23 20:12:13 +00:00
|
|
|
std::string redirectUrl;
|
|
|
|
if (GetHeaderValue(responseHeaders_, "Location", &redirectUrl)) {
|
2019-06-23 19:21:56 +00:00
|
|
|
Url url(baseUrl);
|
|
|
|
url = url.Relative(redirectUrl);
|
|
|
|
redirectUrl = url.ToString();
|
|
|
|
}
|
2019-06-23 19:04:59 +00:00
|
|
|
return redirectUrl;
|
|
|
|
}
|
|
|
|
|
2023-07-21 20:04:05 +00:00
|
|
|
void HTTPRequest::Do() {
|
2023-07-20 09:25:27 +00:00
|
|
|
SetCurrentThreadName("HTTPDownload::Do");
|
2023-01-03 22:29:22 +00:00
|
|
|
|
|
|
|
AndroidJNIThreadContext jniContext;
|
2019-06-23 19:04:59 +00:00
|
|
|
resultCode_ = 0;
|
|
|
|
|
|
|
|
std::string downloadURL = url_;
|
|
|
|
while (resultCode_ == 0) {
|
2024-05-28 09:16:10 +00:00
|
|
|
// This is where the new request is performed.
|
2023-06-17 20:29:32 +00:00
|
|
|
int resultCode = Perform(downloadURL);
|
2019-06-23 19:04:59 +00:00
|
|
|
if (resultCode == -1) {
|
|
|
|
SetFailed(resultCode);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resultCode == 301 || resultCode == 302 || resultCode == 303 || resultCode == 307 || resultCode == 308) {
|
|
|
|
std::string redirectURL = RedirectLocation(downloadURL);
|
|
|
|
if (redirectURL.empty()) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Could not find Location header for redirect");
|
2019-06-23 19:04:59 +00:00
|
|
|
resultCode_ = resultCode;
|
|
|
|
} else if (redirectURL == downloadURL || redirectURL == url_) {
|
|
|
|
// Simple loop detected, bail out.
|
|
|
|
resultCode_ = resultCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the next GET.
|
2024-05-28 09:16:10 +00:00
|
|
|
if (resultCode_ == 0) {
|
2024-07-14 12:42:59 +00:00
|
|
|
INFO_LOG(Log::HTTP, "Download of %s redirected to %s", downloadURL.c_str(), redirectURL.c_str());
|
2024-05-28 09:16:10 +00:00
|
|
|
buffer_.clear();
|
|
|
|
responseHeaders_.clear();
|
|
|
|
}
|
2019-06-23 19:04:59 +00:00
|
|
|
downloadURL = redirectURL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resultCode == 200) {
|
2024-07-14 12:42:59 +00:00
|
|
|
INFO_LOG(Log::HTTP, "Completed requesting %s (storing result to %s)", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str());
|
2021-05-15 05:46:03 +00:00
|
|
|
if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Failed writing download to '%s'", outfile_.c_str());
|
2019-06-23 19:04:59 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-07-14 12:42:59 +00:00
|
|
|
ERROR_LOG(Log::HTTP, "Error requesting '%s' (storing result to '%s'): %i", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str(), resultCode);
|
2019-06-23 19:04:59 +00:00
|
|
|
}
|
|
|
|
resultCode_ = resultCode;
|
|
|
|
}
|
|
|
|
|
2013-11-29 16:33:56 +00:00
|
|
|
// Set this last to ensure no race conditions when checking Done. Users must always check
|
|
|
|
// Done before looking at the result code.
|
|
|
|
completed_ = true;
|
2013-05-31 21:04:42 +00:00
|
|
|
}
|
|
|
|
|
2012-10-30 12:20:55 +00:00
|
|
|
} // http
|