#include "defines.h" #include "downloader.h" #include "encode.h" #include "timing.h" namespace HTTP{ Downloader::Downloader(){ progressCallback = 0; connectedPort = 0; dataTimeout = 5; retryCount = 5; ssl = false; proxied = false; char *p = getenv("http_proxy"); if (p){ proxyUrl = HTTP::URL(p); proxied = true; MEDIUM_MSG("Proxying through %s", proxyUrl.getUrl().c_str()); } } bool Downloader::isProxied() const{return proxied;} /// Returns a reference to the internal HTTP::Parser body element std::string &Downloader::data(){return H.body;} /// Returns a const reference to the internal HTTP::Parser body element const std::string &Downloader::const_data() const{return H.body;} /// Returns the status text of the HTTP Request. std::string &Downloader::getStatusText(){return H.method;} /// Returns the status code of the HTTP Request. uint32_t Downloader::getStatusCode(){return atoi(H.url.c_str());} /// Returns true if the HTTP Request is OK bool Downloader::isOk(){return (getStatusCode() == 200);} /// Returns the given header from the response, or empty string if it does not exist. std::string Downloader::getHeader(const std::string &headerName){ return H.GetHeader(headerName); } /// Simply turns link into a HTTP::URL and calls get(const HTTP::URL&) bool Downloader::get(const std::string &link, Util::DataCallback &cb){ HTTP::URL uri(link); return get(uri, 6, cb); } /// Sets an extra (or overridden) header to be sent with outgoing requests. void Downloader::setHeader(const std::string &name, const std::string &val){ extraHeaders[name] = val; } /// Clears all extra/override headers for outgoing requests. void Downloader::clearHeaders(){extraHeaders.clear();} /// Returns a reference to the internal HTTP class instance. Parser &Downloader::getHTTP(){return H;} /// Returns a reference to the internal Socket::Connection class instance. Socket::Connection &Downloader::getSocket(){return S;} const Socket::Connection &Downloader::getSocket() const{return S;} Downloader::~Downloader(){S.close();} /// Prepares a request for the given URL, does not send anything void Downloader::prepareRequest(const HTTP::URL &link, const std::string &method){ if (!canRequest(link)){return;} bool needSSL = (link.protocol == "https"); H.Clean(); // Reconnect if needed if (!proxied || needSSL){ if (!getSocket() || link.host != connectedHost || link.getPort() != connectedPort || needSSL != ssl){ getSocket().close(); connectedHost = link.host; connectedPort = link.getPort(); #ifdef SSL if (needSSL){ S.open(connectedHost, connectedPort, true, true); }else{ S.open(connectedHost, connectedPort, true); } #else S.open(connectedHost, connectedPort, true); #endif } }else{ if (!getSocket() || proxyUrl.host != connectedHost || proxyUrl.getPort() != connectedPort || needSSL != ssl){ getSocket().close(); connectedHost = proxyUrl.host; connectedPort = proxyUrl.getPort(); S.open(connectedHost, connectedPort, true); } } ssl = needSSL; if (!getSocket()){ H.method = S.getError(); return; // socket is closed } if (proxied && !ssl){ H.url = link.getProxyUrl(); if (link.port.size()){ H.SetHeader("Host", link.host + ":" + link.port); }else{ H.SetHeader("Host", link.host); } }else{ H.url = "/" + Encodings::URL::encode(link.path, "/:=@[]"); if (link.args.size()){H.url += "?" + link.args;} if (link.port.size()){ H.SetHeader("Host", link.host + ":" + link.port); }else{ H.SetHeader("Host", link.host); } } if (method.size()){H.method = method;} H.SetHeader("User-Agent", "MistServer " PACKAGE_VERSION); H.SetHeader("X-Version", PACKAGE_VERSION); H.SetHeader("Accept", "*/*"); if (authStr.size() && (link.user.size() || link.pass.size())){ H.auth(link.user, link.pass, authStr); } if (proxyAuthStr.size() && (proxyUrl.user.size() || proxyUrl.pass.size())){ H.auth(proxyUrl.user, proxyUrl.pass, proxyAuthStr, "Proxy-Authorization"); } if (extraHeaders.size()){ for (std::map::iterator it = extraHeaders.begin(); it != extraHeaders.end(); ++it){ H.SetHeader(it->first, it->second); } } nbLink = link; } /// Sends a request for the given URL, does no waiting. void Downloader::doRequest(const HTTP::URL &link, const std::string &method, const void *body, const size_t bodyLen){ prepareRequest(link, method); H.sendRequest(getSocket(), body, bodyLen, false); H.Clean(); } /// Sends a request for the given URL, does no waiting. void Downloader::doRequest(const HTTP::URL &link, const std::string &method, const std::string &body){ doRequest(link, method, body.data(), body.size()); } /// Do a HEAD request to download the HTTP headers only, returns true on success bool Downloader::head(const HTTP::URL &link, uint8_t maxRecursiveDepth){ if (!canRequest(link)){return false;} size_t loop = retryCount + 1; // max 5 attempts while (--loop){// loop while we are unsuccessful MEDIUM_MSG("Retrieving %s (%zu/%" PRIu32 ")", link.getUrl().c_str(), retryCount - loop + 1, retryCount); doRequest(link, "HEAD"); if (!getSocket()){ FAIL_MSG("Could not retrieve %s: %s", link.getUrl().c_str(), getSocket().getError().c_str()); return false; } H.headerOnly = true; uint64_t reqTime = Util::bootSecs(); while (getSocket() && Util::bootSecs() < reqTime + dataTimeout){ // No data? Wait for a second or so. if (!getSocket().spool()){ if (progressCallback != 0){ if (!progressCallback()){ WARN_MSG("Download aborted by callback"); H.headerOnly = false; return false; } } Util::sleep(250); continue; } // Data! Check if we can parse it... if (H.Read(getSocket())){ H.headerOnly = false; if (shouldContinue()){ if (maxRecursiveDepth == 0){ FAIL_MSG("Maximum recursion depth reached"); return false; } if (!canContinue(link)){return false;} if (getStatusCode() >= 300 && getStatusCode() < 400){ return head(link.link(getHeader("Location")), --maxRecursiveDepth); }else{ return head(link, --maxRecursiveDepth); } } if (H.protocol == "HTTP/1.0"){getSocket().close();} H.headerOnly = false; return true; // Success! } // reset the data timeout if (reqTime != Util::bootSecs()){ if (progressCallback != 0){ if (!progressCallback()){ WARN_MSG("Download aborted by callback"); H.headerOnly = false; return false; } } reqTime = Util::bootSecs(); } } H.headerOnly = false; if (getSocket()){ FAIL_MSG("Timeout while retrieving %s (%zu/%" PRIu32 ")", link.getUrl().c_str(), retryCount - loop + 1, retryCount); H.Clean(); getSocket().close(); }else{ if (retryCount - loop + 1 > 2){ INFO_MSG("Lost connection while retrieving %s (%zu/%" PRIu32 ")", link.getUrl().c_str(), retryCount - loop + 1, retryCount); }else{ MEDIUM_MSG("Lost connection while retrieving %s (%zu/%" PRIu32 ")", link.getUrl().c_str(), retryCount - loop + 1, retryCount); } H.Clean(); } Util::sleep(500); // wait a bit before retrying } FAIL_MSG("Could not retrieve %s", link.getUrl().c_str()); return false; } bool Downloader::getRangeNonBlocking(const HTTP::URL &link, size_t byteStart, size_t byteEnd, Util::DataCallback &cb){ char tmp[32]; if (byteEnd <= 0){// get range from byteStart til eof sprintf(tmp, "bytes=%zu-", byteStart); }else{ sprintf(tmp, "bytes=%zu-%zu", byteStart, byteEnd - 1); } setHeader("Range", tmp); return getNonBlocking(link, 6); } bool Downloader::getRange(const HTTP::URL &link, size_t byteStart, size_t byteEnd, Util::DataCallback &cb){ char tmp[32]; if (byteEnd <= 0){// get range from byteStart til eof sprintf(tmp, "bytes=%zu-", byteStart); }else{ sprintf(tmp, "bytes=%zu-%zu", byteStart, byteEnd - 1); } setHeader("Range", tmp); return get(link, 6, cb); } /// Downloads the given URL into 'H', returns true on success. /// Makes at most 5 attempts, and will wait no longer than 5 seconds without receiving data. bool Downloader::get(const HTTP::URL &link, uint8_t maxRecursiveDepth, Util::DataCallback &cb){ if (!getNonBlocking(link, maxRecursiveDepth)){return false;} while (!continueNonBlocking(cb)){Util::sleep(100);} if (isComplete){return true;} FAIL_MSG("Could not retrieve %s", link.getUrl().c_str()); return false; } // prepare a request to be handled in a nonblocking fashion by the continueNonbBocking() bool Downloader::getNonBlocking(const HTTP::URL &link, uint8_t maxRecursiveDepth){ if (!canRequest(link)){return false;} nbLink = link; nbMaxRecursiveDepth = maxRecursiveDepth; nbLoop = retryCount + 1; // max 5 attempts isComplete = false; doRequest(nbLink); nbReqTime = Util::bootSecs(); return true; } const HTTP::URL &Downloader::lastURL(){return nbLink;} // continue handling a request, originally set up by the getNonBlocking() function // returns true if the request is complete bool Downloader::continueNonBlocking(Util::DataCallback &cb){ while (true){ if (!getSocket() && !isComplete){ if (nbLoop < 2){ FAIL_MSG("Exceeded retry limit while retrieving %s (%zu/%" PRIu32 ")", nbLink.getUrl().c_str(), retryCount - nbLoop + 1, retryCount); Util::sleep(1000); return true; } nbLoop--; if (nbLoop == retryCount){ MEDIUM_MSG("Retrieving %s (%zu/%" PRIu32 ")", nbLink.getUrl().c_str(), retryCount - nbLoop + 1, retryCount); }else{ if (retryCount - nbLoop + 1 > 2){ INFO_MSG("Lost connection while retrieving %s (%zu/%" PRIu32 ")", nbLink.getUrl().c_str(), retryCount - nbLoop + 1, retryCount); }else{ MEDIUM_MSG("Lost connection while retrieving %s (%zu/%" PRIu32 ")", nbLink.getUrl().c_str(), retryCount - nbLoop + 1, retryCount); } } if (H.hasHeader("Accept-Ranges") && getHeader("Accept-Ranges").size() > 0){ getRangeNonBlocking(nbLink, H.currentLength, 0, cb); return true; }else{ doRequest(nbLink); } if (!getSocket()){ WARN_MSG("Aborting download: could not open connection"); return true; } nbReqTime = Util::bootSecs(); } if (Util::bootSecs() >= nbReqTime + dataTimeout){ FAIL_MSG("Timeout while retrieving %s (%zu/%" PRIu32 ")", nbLink.getUrl().c_str(), retryCount - nbLoop + 1, retryCount); getSocket().close(); return false; // because we may have retries left } // No data? Wait for a second or so. if (!getSocket().spool()){ if (progressCallback != 0){ if (!progressCallback()){ WARN_MSG("Download aborted by callback"); return true; } } return false; } // Data! Check if we can parse it... if (H.Read(getSocket(), cb)){ if (shouldContinue()){ if (nbMaxRecursiveDepth == 0){ FAIL_MSG("Maximum recursion depth reached"); return true; } if (!canContinue(nbLink)){return false;} --nbMaxRecursiveDepth; if (getStatusCode() >= 300 && getStatusCode() < 400){ nbLink = nbLink.link(getHeader("Location")); } doRequest(nbLink); return false; } isComplete = true; // Success return true; } // reset the data timeout if (nbReqTime != Util::bootSecs()){ if (progressCallback != 0){ if (!progressCallback()){ WARN_MSG("Download aborted by callback"); return true; } } nbReqTime = Util::bootSecs(); } } WARN_MSG("Invalid connection state for HTTP request"); return false; // we should never get here } bool Downloader::post(const HTTP::URL &link, const std::string &payload, bool sync, uint8_t maxRecursiveDepth){ return post(link, payload.data(), payload.size(), sync, maxRecursiveDepth); } bool Downloader::post(const HTTP::URL &link, const void *payload, const size_t payloadLen, bool sync, uint8_t maxRecursiveDepth){ if (!canRequest(link)){return false;} size_t loop = retryCount; // max 5 attempts while (--loop){// loop while we are unsuccessful MEDIUM_MSG("Posting to %s (%zu/%" PRIu32 ")", link.getUrl().c_str(), retryCount - loop + 1, retryCount); doRequest(link, "POST", payload, payloadLen); // Not synced? Ignore the response and immediately return true. if (!sync){return true;} uint64_t reqTime = Util::bootSecs(); while (getSocket() && Util::bootSecs() < reqTime + dataTimeout){ // No data? Wait for a second or so. if (!getSocket().spool()){ if (progressCallback != 0){ if (!progressCallback()){ WARN_MSG("Download aborted by callback"); return false; } } Util::sleep(250); continue; } // Data! Check if we can parse it... if (H.Read(getSocket())){ if (shouldContinue()){ if (maxRecursiveDepth == 0){ FAIL_MSG("Maximum recursion depth reached"); return false; } if (!canContinue(link)){return false;} if (getStatusCode() >= 300 && getStatusCode() < 400){ return post(link.link(getHeader("Location")), payload, payloadLen, sync, --maxRecursiveDepth); }else{ return post(link, payload, payloadLen, sync, --maxRecursiveDepth); } } return true; // Success! } // reset the data timeout if (reqTime != Util::bootSecs()){ if (progressCallback != 0){ if (!progressCallback()){ WARN_MSG("Download aborted by callback"); return false; } } reqTime = Util::bootSecs(); } } if (getSocket()){ FAIL_MSG("Timeout while retrieving %s", link.getUrl().c_str()); return false; } Util::sleep(500); // wait a bit before retrying } FAIL_MSG("Could not retrieve %s", link.getUrl().c_str()); return false; } bool Downloader::canRequest(const HTTP::URL &link){ if (!link.host.size()){return false;} if (link.protocol != "http" && link.protocol != "https"){ FAIL_MSG("Protocol not supported: %s", link.protocol.c_str()); return false; } #ifndef SSL if (link.protocol == "https"){ FAIL_MSG("Protocol not supported: %s", link.protocol.c_str()); return false; } #endif return true; } bool Downloader::shouldContinue(){ if (H.hasHeader("Set-Cookie")){ std::string cookie = H.GetHeader("Set-Cookie"); setHeader("Cookie", cookie.substr(0, cookie.find(';'))); } uint32_t sCode = getStatusCode(); if (sCode == 401 || sCode == 407 || (sCode >= 300 && sCode < 400)){return true;} return false; } bool Downloader::canContinue(const HTTP::URL &link){ if (getStatusCode() == 401){ // retry with authentication if (H.hasHeader("WWW-Authenticate")){authStr = H.GetHeader("WWW-Authenticate");} if (H.hasHeader("Www-Authenticate")){authStr = H.GetHeader("Www-Authenticate");} if (!authStr.size()){ FAIL_MSG("Authentication required but no WWW-Authenticate header present"); return false; } if (!link.user.size() && !link.pass.size()){ FAIL_MSG("Authentication required but not included in URL"); return false; } INFO_MSG("Authenticating..."); return true; } if (getStatusCode() == 407){ // retry with authentication if (H.hasHeader("Proxy-Authenticate")){proxyAuthStr = H.GetHeader("Proxy-Authenticate");} if (!proxyAuthStr.size()){ FAIL_MSG("Proxy authentication required but no Proxy-Authenticate header present"); return false; } if (!proxyUrl.user.size() && !proxyUrl.pass.size()){ FAIL_MSG("Proxy authentication required but not included in URL"); return false; } INFO_MSG("Authenticating proxy..."); return true; } if (getStatusCode() >= 300 && getStatusCode() < 400){ // follow redirect std::string location = getHeader("Location"); if (!location.size()){return false;} INFO_MSG("Following redirect to %s", location.c_str()); return true; } return false; } }// namespace HTTP