9 #include <unordered_map>
32 using std::make_unique;
33 using std::nouppercase;
37 using std::ostringstream;
41 using std::unique_ptr;
42 using std::unordered_map;
66 HTTPDownloadClient::HTTPDownloadClient(): downloadThreadMutex(
"downloadthread-mutex") {
72 ostringstream escaped;
76 for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
77 string::value_type c = (*i);
80 if (Character::isAlphaNumeric(c) ==
true || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~') {
87 escaped <<
'%' << setw(2) << int((
unsigned char) c);
88 escaped << nouppercase;
96 for (
const auto& [parameterName, parameterValue]:
getParameters) {
97 if (query.empty() ==
true) query+=
"?";
else query+=
"&";
101 string(
"GET " + relativeUrl + query +
" HTTP/1.1\r\n") +
102 string(
"User-Agent: tdme2-httpdownloadclient\r\n") +
103 string(
"Host: " + hostName +
"\r\n") +
104 string(
"Connection: close\r\n");
108 request+=
"Authorization: Basic " + base64Pass +
"\r\n";
110 for (
const auto& [headerName, headerValue]:
headers) {
111 request+= headerName +
": " + headerValue +
"\r\n";
121 auto returnHeaderSize = 0ll;
127 while (rawResponse.eof() ==
false) {
128 rawResponse.get(currentChar);
130 if (lastChar ==
'\r' && currentChar ==
'\n') {
131 if (line.empty() ==
false) {
132 if (headerIdx == 0) {
136 auto headerNameValueSeparator = StringTools::indexOf(line,
':');
137 responseHeaders[StringTools::trim(StringTools::substring(line, 0, headerNameValueSeparator))] =
138 StringTools::trim(StringTools::substring(line, headerNameValueSeparator + 1));
146 if (currentChar !=
'\r' && currentChar !=
'\n') {
149 lastChar = currentChar;
151 if (statusHeader.empty() ==
false) {
162 return returnHeaderSize;
182 class DownloadThread:
public Thread {
190 unique_ptr<TCPSocket> socket;
193 auto protocolSeparatorIdx = StringTools::indexOf(downloadClient->
url,
string(
"://"));
195 auto relativeUrl = StringTools::substring(downloadClient->
url, protocolSeparatorIdx + 3);
197 auto slashIdx = relativeUrl.find(
'/');
198 auto hostname = relativeUrl;
199 if (slashIdx != -1) hostname = StringTools::substring(relativeUrl, 0, slashIdx);
200 relativeUrl = StringTools::substring(relativeUrl, hostname.size());
202 if (StringTools::startsWith(downloadClient->
url,
"http://") ==
true) {
204 auto ip = Network::getIpByHostname(hostname);
205 if (ip.empty() ==
true) {
206 Console::println(
"HTTPDownloadClient::execute(): failed");
210 socket = make_unique<TCPSocket>();
211 socket->connect(ip, 80);
213 if (StringTools::startsWith(downloadClient->
url,
"https://") ==
true) {
214 socket = make_unique<SecureTCPSocket>();
215 socket->connect(hostname, 443);
221 socket->write((
void*)request.data(), request.length());
225 ofstream ofs(std::filesystem::u8path(downloadClient->
file +
".download"), ofstream::binary);
226 if (ofs.is_open() ==
false) {
227 throw HTTPClientException(
"Unable to open file for writing(" + to_string(errno) +
"): " + (downloadClient->
file +
".download"));
231 char rawResponseBuf[16384];
232 auto rawResponseBytesRead = 0;
233 uint64_t bytesRead = 0;
235 for (;isStopRequested() ==
false;) {
236 auto rawResponseBytesRead = socket->read(rawResponseBuf,
sizeof(rawResponseBuf));
237 ofs.write(rawResponseBuf, rawResponseBytesRead);
242 ifstream ifs(std::filesystem::u8path(downloadClient->
file +
".download"), ofstream::binary);
243 if (ifs.is_open() ==
false) {
244 throw HTTPClientException(
"Unable to open file for reading(" + to_string(errno) +
"): " + (downloadClient->
file +
".download"));
250 auto contentLengthHeaderIt = downloadClient->
responseHeaders.find(
"Content-Length");
252 const auto& contentLengthHeader = contentLengthHeaderIt->second;
254 downloadClient->
contentSize = Integer::parse(contentLengthHeader);
259 bytesRead+= rawResponseBytesRead;
273 if (downloadClient->
statusCode == 200 && isStopRequested() ==
false) {
275 ifstream ifs(std::filesystem::u8path(downloadClient->
file +
".download"), ofstream::binary);
276 if (ifs.is_open() ==
false) {
277 throw HTTPClientException(
"Unable to open file for reading(" + to_string(errno) +
"): " + (downloadClient->
file +
".download"));
280 ifs.seekg(downloadClient->
headerSize, ios::beg);
281 auto ifsHeaderSize = ifs.tellg();
282 ifs.seekg(0, ios::end);
283 auto ifsSizeTotal = ifs.tellg();
284 auto ifsSize = ifsSizeTotal - ifsHeaderSize;
285 ifs.seekg(ifsHeaderSize, ios::beg);
288 ofstream ofs(std::filesystem::u8path(downloadClient->
file), ofstream::binary);
289 if (ofs.is_open() ==
false) {
290 throw HTTPClientException(
"Unable to open file for writing(" + to_string(errno) +
"): " + downloadClient->
file);
295 auto ifsBytesToRead = 0;
296 auto ifsBytesRead = 0;
298 auto ifsBytesToRead = Math::min(
static_cast<int64_t
>(ifsSize - ifsBytesRead),
sizeof(buf));
299 ifs.read(buf, ifsBytesToRead);
300 ofs.write(buf, ifsBytesToRead);
301 ifsBytesRead+= ifsBytesToRead;
302 }
while (ifsBytesRead < ifsSize);
312 FileSystem::getStandardFileSystem()->removeFile(
".", downloadClient->
file +
".download");
323 Console::println(
string(
"HTTPDownloadClient::execute(): performed HTTP request: FAILED: ") + exception.what());
void cancel()
Cancel a started download.
unordered_map< string, string > headers
uint64_t parseHTTPResponseHeaders(ifstream &rawResponse)
Parse HTTP response headers.
unique_ptr< Thread > downloadThread
void start()
Starts the HTTP download to file.
unordered_map< string, string > getParameters
void join()
Wait until underlying thread has finished.
Mutex downloadThreadMutex
unordered_map< string, string > responseHeaders
static string urlEncode(const string &value)
Returns a URL encoded representation of value.
void reset()
Reset this HTTP client.
string createHTTPRequestHeaders(const string &hostName, const string &relativeUrl)
Create HTTP request headers.
File system singleton class.
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.