#include "downloader.h" #include "defines.h" #include "timing.h" namespace HTTP{ /// Returns a reference to the internal HTTP::Parser body element std::string &Downloader::data(){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){ HTTP::URL uri(link); return get(uri); } /// 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;} /// Sends a request for the given URL, does no waiting. void Downloader::doRequest(const HTTP::URL &link){ if (link.protocol != "http"){ FAIL_MSG("Protocol not supported: %s", link.protocol.c_str()); return; } INFO_MSG("Retrieving %s", link.getUrl().c_str()); H.Clean(); // Reconnect if needed if (!S || link.host != connectedHost || link.getPort() != connectedPort){ S.close(); connectedHost = link.host; connectedPort = link.getPort(); S = Socket::Connection(connectedHost, connectedPort, true); } H.url = "/" + 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); } H.SetHeader("User-Agent", "MistServer " PACKAGE_VERSION); H.SetHeader("X-Version", PACKAGE_VERSION); H.SetHeader("Accept", "*/*"); if (extraHeaders.size()){ for (std::map::iterator it = extraHeaders.begin(); it != extraHeaders.end(); ++it){ H.SetHeader(it->first, it->second); } } H.SendRequest(S); H.Clean(); } /// 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){ if (!link.host.size()){return false;} if (link.protocol != "http"){ FAIL_MSG("Protocol not supported: %s", link.protocol.c_str()); return false; } unsigned int loop = 6; // max 5 attempts while (--loop){// loop while we are unsuccessful doRequest(link); uint64_t reqTime = Util::bootSecs(); while (S && Util::bootSecs() < reqTime + 5){ // No data? Wait for a second or so. if (!S.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(S)){ if (getStatusCode() >= 300 && getStatusCode() < 400){ // follow redirect std::string location = getHeader("Location"); if (maxRecursiveDepth == 0){ FAIL_MSG("Maximum redirect depth reached: %s", location.c_str()); return false; }else{ FAIL_MSG("Following redirect to %s", location.c_str()); return get(link.link(location), maxRecursiveDepth--); } } return true; // Success! } // reset the 5 second timeout reqTime = Util::bootSecs(); } if (S){ 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; } }