9 #include <unordered_map>
31 using std::make_unique;
32 using std::nouppercase;
36 using std::ostringstream;
40 using std::unique_ptr;
41 using std::unordered_map;
64 HTTPDownloadClient::HTTPDownloadClient(): downloadThreadMutex(
"downloadthread-mutex") {
70 ostringstream escaped;
74 for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
75 string::value_type c = (*i);
78 if (Character::isAlphaNumeric(c) ==
true || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~') {
85 escaped <<
'%' << setw(2) << int((
unsigned char) c);
86 escaped << nouppercase;
94 for (
const auto& [parameterName, parameterValue]:
getParameters) {
95 if (query.empty() ==
true) query+=
"?";
else query+=
"&";
99 string(
"GET " + relativeUrl + query +
" HTTP/1.1\r\n") +
100 string(
"User-Agent: tdme2-httpdownloadclient\r\n") +
101 string(
"Host: " + hostName +
"\r\n") +
102 string(
"Connection: close\r\n");
106 request+=
"Authorization: Basic " + base64Pass +
"\r\n";
108 for (
const auto& [headerName, headerValue]:
headers) {
109 request+= headerName +
": " + headerValue +
"\r\n";
111 request+= string(
"\r\n");
118 auto returnHeaderSize = 0ll;
124 while (rawResponse.eof() ==
false) {
125 rawResponse.get(currentChar);
127 if (lastChar ==
'\r' && currentChar ==
'\n') {
128 if (line.empty() ==
false) {
129 if (headerIdx == 0) {
133 auto headerNameValueSeparator = StringTools::indexOf(line,
':');
134 responseHeaders[StringTools::trim(StringTools::substring(line, 0, headerNameValueSeparator))] =
135 StringTools::trim(StringTools::substring(line, headerNameValueSeparator + 1));
143 if (currentChar !=
'\r' && currentChar !=
'\n') {
146 lastChar = currentChar;
148 if (statusHeader.empty() ==
false) {
159 return returnHeaderSize;
179 class DownloadThread:
public Thread {
187 unique_ptr<TCPSocket> socket;
190 auto protocolSeparatorIdx = StringTools::indexOf(downloadClient->
url,
string(
"://"));
192 auto relativeUrl = StringTools::substring(downloadClient->
url, protocolSeparatorIdx + 3);
194 auto slashIdx = relativeUrl.find(
'/');
195 auto hostname = relativeUrl;
196 if (slashIdx != -1) hostname = StringTools::substring(relativeUrl, 0, slashIdx);
197 relativeUrl = StringTools::substring(relativeUrl, hostname.size());
199 if (StringTools::startsWith(downloadClient->
url,
"http://") ==
true) {
201 auto ip = Network::getIpByHostname(hostname);
202 if (ip.empty() ==
true) {
203 Console::printLine(
"HTTPDownloadClient::execute(): failed");
207 socket = make_unique<TCPSocket>();
208 socket->connect(ip, 80);
210 if (StringTools::startsWith(downloadClient->
url,
"https://") ==
true) {
211 socket = make_unique<SecureTCPSocket>();
212 socket->connect(hostname, 443);
218 socket->write((
void*)request.data(), request.length());
222 ofstream ofs(std::filesystem::path((
const char8_t*)(downloadClient->
file +
".download").c_str()), ofstream::binary);
223 if (ofs.is_open() ==
false) {
224 throw HTTPClientException(
"Unable to open file for writing(" + to_string(errno) +
"): " + (downloadClient->
file +
".download"));
228 char rawResponseBuf[16384];
229 auto rawResponseBytesRead = 0;
230 uint64_t bytesRead = 0;
232 for (;isStopRequested() ==
false;) {
233 auto rawResponseBytesRead = socket->read(rawResponseBuf,
sizeof(rawResponseBuf));
234 ofs.write(rawResponseBuf, rawResponseBytesRead);
239 ifstream ifs(std::filesystem::path((
const char8_t*)(downloadClient->
file +
".download").c_str()), ofstream::binary);
240 if (ifs.is_open() ==
false) {
241 throw HTTPClientException(
"Unable to open file for reading(" + to_string(errno) +
"): " + (downloadClient->
file +
".download"));
247 auto contentLengthHeaderIt = downloadClient->
responseHeaders.find(
"Content-Length");
249 const auto& contentLengthHeader = contentLengthHeaderIt->second;
251 downloadClient->
contentSize = Integer::parse(contentLengthHeader);
256 bytesRead+= rawResponseBytesRead;
272 ifstream ifs(std::filesystem::path((
const char8_t*)(downloadClient->
file +
".download").c_str()), ofstream::binary);
273 if (ifs.is_open() ==
false) {
274 throw HTTPClientException(
"Unable to open file for reading(" + to_string(errno) +
"): " + (downloadClient->
file +
".download"));
278 ifs.seekg(downloadClient->
headerSize, ios::beg);
279 auto ifsHeaderSize = ifs.tellg();
280 ifs.seekg(0, ios::end);
281 auto ifsSizeTotal = ifs.tellg();
282 auto ifsSize = ifsSizeTotal - ifsHeaderSize;
283 ifs.seekg(ifsHeaderSize, ios::beg);
286 ofstream ofs(std::filesystem::path((
const char8_t*)downloadClient->
file.c_str()), ofstream::binary);
287 if (ofs.is_open() ==
false) {
288 throw HTTPClientException(
"Unable to open file for writing(" + to_string(errno) +
"): " + downloadClient->
file);
293 auto ifsBytesToRead = 0;
294 auto ifsBytesRead = 0;
296 auto ifsBytesToRead = Math::min(
static_cast<int64_t
>(ifsSize - ifsBytesRead),
sizeof(buf));
297 ifs.read(buf, ifsBytesToRead);
298 ofs.write(buf, ifsBytesToRead);
299 ifsBytesRead+= ifsBytesToRead;
300 }
while (ifsBytesRead < ifsSize);
310 FileSystem::removeFile(
".", downloadClient->
file +
".download");
320 if (socket !=
nullptr) socket->shutdown();
322 Console::printLine(
string(
"HTTPDownloadClient::execute(): performed HTTP request: FAILED: ") + exception.what());
void cancel()
Cancel a started download.
unordered_map< string, string > headers
_Mutex downloadThreadMutex
uint64_t parseHTTPResponseHeaders(ifstream &rawResponse)
Parse HTTP response headers.
void start()
Starts the HTTP download to file.
unordered_map< string, string > getParameters
void join()
Wait until underlying thread has finished.
unique_ptr< _Thread > downloadThread
const string createHTTPRequestHeaders(const string &hostName, const string &relativeUrl)
Create HTTP request headers.
unordered_map< string, string > responseHeaders
static string urlEncode(const string &value)
Returns a URL encoded representation of value.
void reset()
Reset this HTTP client.
Network socket closed exception.
Class representing a secure TCP socket.
Class representing a TCP socket.
void unlock()
Unlocks this mutex.
void lock()
Locks the mutex, additionally mutex locks will block until other locks have been unlocked.
Base64 encoding/decoding class.
const string & nextToken()
void tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
std::exception Exception
Exception base class.