// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #if defined(_WIN32) #define __USE_W32_SOCKETS #include #endif #include "futils.hpp" #include "http.hpp" #include #include #include #include //////////////////////////////////////// // platform specific code #ifndef _WIN32 //////////////////////////////////////// // Unix or Mac #define closesocket close #include #include #include #include #include #include #include #define fopen_S(f, n, o) f = fopen(n, o) #define WINAPI using DWORD = unsigned long; #define SOCKET_ERROR (-1) #define WSAEWOULDBLOCK EINPROGRESS #define WSAENOTCONN EAGAIN static int WSAGetLastError() { return errno; } #endif //////////////////////////////////////// // code static constexpr auto httpTemplate = "%s %s HTTP/%s\r\n" // $verb $page $version "User-Agent: exiv2http/1.0.0\r\n" "Accept: */*\r\n" "Host: %s\r\n" // $servername "%s" // $header "\r\n"; #define white(c) (((c) == ' ') || ((c) == '\t')) #define FINISH (-999) #define OK(s) (200 <= (s) && (s) < 300) static constexpr std::array blankLines{ "\r\n\r\n", // this is the standard "\n\n", // this is commonly sent by CGI scripts }; static constexpr auto snooze = std::chrono::milliseconds::zero(); static auto sleep_ = std::chrono::milliseconds(1000); static int forgive(int n, int& err) { err = WSAGetLastError(); if (!n && !err) return FINISH; #ifndef _WIN32 if (n == 0) return FINISH; // server hungup #endif bool bForgive = err == WSAEWOULDBLOCK || err == WSAENOTCONN; bool bError = n == SOCKET_ERROR; if (bError && bForgive) return 0; return n; } static int error(std::string& errors, const char* msg, const char* x = nullptr, const char* y = nullptr, int z = 0) { static const size_t buffer_size = 512; char buffer[buffer_size] = {}; snprintf(buffer, buffer_size, msg, x, y, z); if (errno) { perror(buffer); } else { fprintf(stderr, "%s\n", buffer); } errors += std::string(msg) + '\n'; return -1; } static void flushBuffer(const char* buffer, size_t start, int& end, std::string& file) { file += std::string(buffer + start, end - start); end = 0; } static Exiv2::Dictionary stringToDict(const std::string& s) { Exiv2::Dictionary result; std::string token; size_t i = 0; while (i < s.length()) { if (s[i] != ',') { if (s[i] != ' ') token += s[i]; } else { result[token] = token; token.clear(); } i++; } result[token] = token; return result; } static int makeNonBlocking(int sockfd) { #if defined(_WIN32) ULONG ioctl_opt = 1; return ioctlsocket(sockfd, FIONBIO, &ioctl_opt); #else int result = fcntl(sockfd, F_SETFL, O_NONBLOCK); return result >= 0 ? result : SOCKET_ERROR; #endif } int Exiv2::http(Exiv2::Dictionary& request, Exiv2::Dictionary& response, std::string& errors) { if (request.find("verb") == request.end()) request["verb"] = "GET"; if (request.find("header") == request.end()) request["header"] = ""; if (request.find("version") == request.end()) request["version"] = "1.0"; if (request.find("port") == request.end()) request["port"] = ""; std::string file; errors = ""; int result = 0; //////////////////////////////////// // Windows specific code #if defined(_WIN32) WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif const char* servername = request["server"].c_str(); const char* page = request["page"].c_str(); const char* verb = request["verb"].c_str(); const char* header = request["header"].c_str(); const char* version = request["version"].c_str(); const char* port = request["port"].c_str(); const char* servername_p = servername; const char* port_p = port; std::string url = std::string("http://") + request["server"] + request["page"]; // parse and change server if using a proxy const char* PROXI = "HTTP_PROXY"; const char* proxi = "http_proxy"; const char* PROXY = getenv(PROXI); const char* proxy = getenv(proxi); bool bProx = PROXY || proxy; const char* prox = bProx ? (proxy ? proxy : PROXY) : ""; Exiv2::Uri Proxy = Exiv2::Uri::Parse(prox); // find the dictionary of no_proxy servers const char* NO_PROXI = "NO_PROXY"; const char* no_proxi = "no_proxy"; const char* NO_PROXY = getenv(NO_PROXI); const char* no_proxy = getenv(no_proxi); bool bNoProxy = NO_PROXY || no_proxy; auto no_prox = std::string(bNoProxy ? (no_proxy ? no_proxy : NO_PROXY) : ""); Exiv2::Dictionary noProxy = stringToDict(no_prox + ",localhost,127.0.0.1"); // if the server is on the no_proxy list ... ignore the proxy! if (noProxy.count(servername)) bProx = false; if (bProx) { servername_p = Proxy.Host.c_str(); port_p = Proxy.Port.c_str(); page = url.c_str(); } if (!port[0]) port = "80"; if (!port_p[0]) port_p = "80"; //////////////////////////////////// // open the socket auto sockfd = static_cast(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); if (sockfd < 0) return error(errors, "unable to create socket\n", nullptr, nullptr, 0); // connect the socket to the server int server = -1; // fill in the address sockaddr_in serv_addr = {}; int serv_len = sizeof(serv_addr); serv_addr.sin_addr.s_addr = inet_addr(servername_p); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(atoi(port_p)); // convert unknown servername into IP address // http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/rzab6/rzab6uafinet.htm if (serv_addr.sin_addr.s_addr == static_cast(INADDR_NONE)) { auto host = gethostbyname(servername_p); if (!host) { closesocket(sockfd); return error(errors, "no such host", servername_p); } memcpy(&serv_addr.sin_addr, host->h_addr, sizeof(serv_addr.sin_addr)); } makeNonBlocking(sockfd); //////////////////////////////////// // and connect server = connect(sockfd, reinterpret_cast(&serv_addr), serv_len); if (server == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { closesocket(sockfd); return error(errors, "error - unable to connect to server = %s port = %s wsa_error = %d", servername_p, port_p, WSAGetLastError()); } char buffer[32 * 1024 + 1]; size_t buff_l = sizeof buffer - 1; //////////////////////////////////// // format the request int n = snprintf(buffer, buff_l, httpTemplate, verb, page, version, servername, header); buffer[n] = 0; response["requestheaders"] = std::string(buffer, n); //////////////////////////////////// // send the header (we'll have to wait for the connection by the non-blocking socket) while (sleep_ >= std::chrono::milliseconds::zero() && send(sockfd, buffer, n, 0) == SOCKET_ERROR /* && WSAGetLastError() == WSAENOTCONN */) { std::this_thread::sleep_for(snooze); sleep_ -= snooze; } if (sleep_ < std::chrono::milliseconds::zero()) { closesocket(sockfd); return error(errors, "error - timeout connecting to server = %s port = %s wsa_error = %d", servername, port, WSAGetLastError()); } int end = 0; // write position in buffer bool bSearching = true; // looking for headers in the response int status = 200; // assume happiness //////////////////////////////////// // read and process the response int err = 0; n = forgive(recv(sockfd, buffer, static_cast(buff_l), 0), err); while (n >= 0 && OK(status)) { if (n) { end += n; buffer[end] = 0; size_t body = 0; // start of body if (bSearching) { // search for the body for (auto&& line : blankLines) { if (!bSearching) break; const char* blankLinePos = strstr(buffer, line); if (blankLinePos) { bSearching = false; body = blankLinePos - buffer + strlen(line); const char* firstSpace = strchr(buffer, ' '); if (firstSpace) { status = atoi(firstSpace); } } } // parse response headers char* h = buffer; char C = ':'; char N = '\n'; int i = 0; // initial byte in buffer while (buffer[i] == N) i++; h = strchr(h + i, N); if (!h) { status = 0; break; } h++; response[""] = std::string(buffer + i).substr(0, h - buffer - 2); const char* firstSpace = strchr(buffer, ' '); if (!firstSpace) { status = 0; break; } result = atoi(firstSpace); auto c = strchr(h, C); char* first_newline = strchr(h, N); while (c && first_newline && c < first_newline && h < buffer + body) { std::string key(h); std::string value(c + 1); key.resize(c - h); value.resize(first_newline - c - 1); response[key] = value; h = first_newline + 1; c = strchr(h, C); first_newline = strchr(h, N); } } // if the buffer's full and we're still searching - give up! // this handles the possibility that there are no headers if (bSearching && buff_l - end < 10) { bSearching = false; body = 0; } if (!bSearching && OK(status)) { flushBuffer(buffer, body, end, file); } } n = forgive(recv(sockfd, buffer + end, static_cast(buff_l - end), 0), err); if (!n) { std::this_thread::sleep_for(snooze); sleep_ -= snooze; if (sleep_ < std::chrono::milliseconds::zero()) n = FINISH; } } if (n != FINISH || !OK(status)) { snprintf(buffer, sizeof buffer, "wsa_error = %d,n = %d,sleep_ = %d status = %d", WSAGetLastError(), n, static_cast(sleep_.count()), status); error(errors, buffer, nullptr, nullptr, 0); } else if (bSearching && OK(status)) { if (end) { // we finished OK without finding headers, flush the buffer flushBuffer(buffer, 0, end, file); } else { closesocket(sockfd); return error(errors, "error - no response from server = %s port = %s wsa_error = %d", servername, port, WSAGetLastError()); } } //////////////////////////////////// // close sockets closesocket(server); closesocket(sockfd); response["body"] = file; return result; } // That's all Folks