From fc8740f7d519650acaeabe218363c679e5538e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Wed, 13 Mar 2019 20:57:31 +0100 Subject: [PATCH] Add curl, rework Request and RequestManager a bit --- SConscript | 4 + src/client/http/HTTP.cpp | 1396 ---------------------------- src/client/http/HTTP.h | 54 -- src/client/http/Request.cpp | 442 +++++---- src/client/http/Request.h | 79 +- src/client/http/RequestManager.cpp | 314 ++++--- src/client/http/RequestManager.h | 58 +- src/tasks/AbandonableTask.cpp | 5 +- 8 files changed, 547 insertions(+), 1805 deletions(-) delete mode 100644 src/client/http/HTTP.cpp delete mode 100644 src/client/http/HTTP.h diff --git a/SConscript b/SConscript index d7893db7b..ce9c75392 100644 --- a/SConscript +++ b/SConscript @@ -332,6 +332,10 @@ def findLibs(env, conf): if not conf.CheckLib(['z', 'zlib']): FatalError("libz not found or not installed") + #Look for libcurl + if not conf.CheckLib(['curl', 'libcurl']): + FatalError("libcurl not found or not installed") + #Look for pthreads if not conf.CheckLib(['pthread', 'pthreadVC2']): FatalError("pthreads development library not found or not installed") diff --git a/src/client/http/HTTP.cpp b/src/client/http/HTTP.cpp deleted file mode 100644 index 95a0734a7..000000000 --- a/src/client/http/HTTP.cpp +++ /dev/null @@ -1,1396 +0,0 @@ -/** - * Powder Toy - HTTP Library - * - * Copyright (c) 2008 - 2010 Stanislaw Skowronek. - * Copyright (c) 2010 Simon Robertshaw - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - - -#include "common/String.h" -#include -#include -#include -#include -#ifndef WIN -#include -#endif -#if !defined(MACOSX) && !defined(BSD) -#include -#endif -#include -#ifdef WIN -#define _WIN32_WINNT 0x0501 -//#include -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include -#endif - -#include "Config.h" -#include "Misc.h" -#include "HTTP.h" -#include "../MD5.h" -#include "Platform.h" - -#ifdef WIN -#define PERROR SOCKET_ERROR -#define PERRNO WSAGetLastError() -#define PEAGAIN WSAEWOULDBLOCK -#define PEINTR WSAEINTR -#define PEINPROGRESS WSAEINPROGRESS -#define PEALREADY WSAEALREADY -#define PCLOSE closesocket -#else -#define PERROR -1 -#define PERRNO errno -#define PEAGAIN EAGAIN -#define PEINTR EINTR -#define PEINPROGRESS EINPROGRESS -#define PEALREADY EALREADY -#define PCLOSE close -#endif - -#ifdef _MSC_VER -#include //for SSIZE_T -typedef SSIZE_T ssize_t; -#endif - -char * userAgent; -static int http_up = 0; -static int http_use_proxy = 0; -static struct sockaddr_in http_proxy; - -static char * eatwhitespace(char * s) -{ - while(*s) - { - if(!(*s == ' ' || *s == '\t')) - break; - s++; - } - return s; -} - -static int splituri(const char *uri, char **host, char **path) -{ - const char *q; - char *x,*y; - if (!strncmp(uri, "http://", 7)) - uri += 7; - q = strchr(uri, '/'); - if (!q) - q = uri + strlen(uri); - x = (char *)malloc(q-uri+1); - if (*q) - y = mystrdup(q); - else - { - y = mystrdup("/"); - } - strncpy(x, uri, q-uri); - x[q-uri] = 0; - if (q==uri || x[q-uri-1]==':') - { - free(x); - free(y); - return 1; - } - *host = x; - *path = y; - return 0; -} - -static char *getserv(char *host) -{ - char *q, *x = mystrdup(host); - q = strchr(x, ':'); - if (q) - *q = 0; - return x; -} - -static char *getport(char *host) -{ - char *p, *q; - q = strchr(host, ':'); - if (q) - p = mystrdup(q+1); - else - p = mystrdup("80"); - return p; -} - -static int resolve(char *dns, char *srv, struct sockaddr_in *addr) -{ - struct addrinfo hnt, *res = 0; - if (http_use_proxy) - { - memcpy(addr, &http_proxy, sizeof(struct sockaddr_in)); - return 0; - } - memset(&hnt, 0, sizeof(hnt)); - hnt.ai_family = AF_INET; - hnt.ai_socktype = SOCK_STREAM; - if (getaddrinfo(dns, srv, &hnt, &res)) - return 1; - if (res) - { - if (res->ai_family != AF_INET) - { - freeaddrinfo(res); - return 1; - } - memcpy(addr, res->ai_addr, res->ai_addrlen); - freeaddrinfo(res); - return 0; - } - return 1; -} - -void http_init(char *proxy) -{ - char *host, *port; -#ifdef WIN - WSADATA wsadata; - if (!WSAStartup(MAKEWORD(2,2), &wsadata)) - http_up = 1; -#else - signal(SIGPIPE, SIG_IGN); - http_up = 1; -#endif - if (proxy) - { - host = getserv(proxy); - port = getport(proxy); - if (resolve(host, port, &http_proxy)) - http_up = 0; - else - http_use_proxy = 1; - free(host); - free(port); - } - ByteString newUserAgent = ByteString::Build("PowderToy/", SAVE_VERSION, ".", MINOR_VERSION, " (", IDENT_PLATFORM, "; ", IDENT_BUILD, "; M", MOD_ID, ") TPTPP/", SAVE_VERSION, ".", MINOR_VERSION, ".", BUILD_NUM, IDENT_RELTYPE, ".", SNAPSHOT_ID); - userAgent = new char[newUserAgent.length()+1]; - std::copy(newUserAgent.begin(), newUserAgent.end(), userAgent); - userAgent[newUserAgent.length()] = 0; - //"User-Agent: PowderToy/%d.%d (%s; %s; M%d) TPTPP/%d.%d.%d%s.%d\n", SAVE_VERSION, MINOR_VERSION, IDENT_PLATFORM, IDENT_BUILD, 0, SAVE_VERSION, MINOR_VERSION, BUILD_NUM, IDENT_RELTYPE, SNAPSHOT_ID - //User-Agent: PowderToy/94.1 (MACOSX; 342; M0) TPTPP/94.1.342R.0 -} - -void http_done(void) -{ -#ifdef WIN - WSACleanup(); -#endif - http_up = 0; -} - -#define CHUNK 4096 - -#define HTS_STRT 0 -#define HTS_RSLV 1 -#define HTS_CONN 2 -#define HTS_IDLE 3 -#define HTS_XMIT 4 -#define HTS_RECV 5 -#define HTS_DONE 6 -struct http_ctx -{ - int state; - time_t last; - int keep; - int ret; - char *host, *path; - char *thdr; - int thlen; - char *txd; - int txdl; - struct sockaddr_in addr; - char *tbuf; - int tlen, tptr; - char *hbuf; - int hlen, hptr; - char *rbuf; - int rlen, rptr; - int chunked, chunkhdr, rxtogo, contlen, cclose; - int fd; - char *fdhost; -}; -void *http_async_req_start(void *ctx, const char *uri, const char *data, int dlen, int keep) -{ - struct http_ctx *cx = (http_ctx *)ctx; - if (cx && time(NULL) - cx->last > http_timeout) - { - http_force_close(ctx); - http_async_req_close(ctx); - ctx = NULL; - } - if (!ctx) - { - ctx = calloc(1, sizeof(struct http_ctx)); - cx = (http_ctx *)ctx; - cx->fd = PERROR; - } - - if (!cx->hbuf) - { - cx->hbuf = (char *)malloc(256); - cx->hlen = 256; - } - - if (!http_up) - { - cx->ret = 604; - cx->state = HTS_DONE; - return ctx; - } - - if (cx->state!=HTS_STRT && cx->state!=HTS_IDLE) - { - fprintf(stderr, "HTTP: unclean request restart state.\n"); - exit(1); - } - - cx->keep = keep; - cx->ret = 600; - if (splituri(uri, &cx->host, &cx->path)) - { - cx->ret = 601; - cx->state = HTS_DONE; - return ctx; - } - if (http_use_proxy) - { - free(cx->path); - cx->path = mystrdup(uri); - } - if (cx->fdhost && strcmp(cx->host, cx->fdhost)) - { - free(cx->fdhost); - cx->fdhost = NULL; - PCLOSE(cx->fd); - cx->fd = PERROR; - cx->state = HTS_STRT; - } - if (data) - { - if (!dlen) - dlen = strlen(data); - cx->txd = (char *)malloc(dlen); - memcpy(cx->txd, data, dlen); - cx->txdl = dlen; - } - else - cx->txdl = 0; - - cx->contlen = 0; - cx->chunked = 0; - cx->chunkhdr = 0; - cx->rxtogo = 0; - cx->cclose = 0; - - cx->tptr = 0; - cx->tlen = 0; - - cx->last = time(NULL); - - return ctx; -} - -void http_async_add_header(void *ctx, const char *name, const char *data) -{ - struct http_ctx *cx = (http_ctx *)ctx; - cx->thdr = (char *)realloc(cx->thdr, cx->thlen + strlen(name) + strlen(data) + 5); - cx->thlen += sprintf(cx->thdr+cx->thlen, "%s: %s\r\n", name, data); -} - -static void process_header(struct http_ctx *cx, char *str) -{ - char *p; - if (cx->chunkhdr) - { - p = strchr(str, ';'); - if (p) - *p = 0; - cx->rxtogo = strtoul(str, NULL, 16); - cx->chunkhdr = 0; - if (!cx->rxtogo) - cx->chunked = 0; - } - if (!str[0]) - { - cx->rxtogo = cx->contlen; - cx->chunkhdr = cx->chunked; - if (!cx->contlen && !cx->chunked && cx->ret!=100) - cx->state = HTS_DONE; - return; - } - if (!strncmp(str, "http/", 5)) - { - p = strchr(str, ' '); - if (!p) - { - cx->ret = 603; - cx->state = HTS_DONE; - return; - } - p++; - cx->ret = atoi(p); - return; - } - if (!strncmp(str, "content-length: ", 16)) - { - str = eatwhitespace(str+16); - cx->contlen = atoi(str); - return; - } - if (!strncmp(str, "transfer-encoding: ", 19)) - { - str = eatwhitespace(str+19); - if(!strncmp(str, "chunked", 8)) - { - cx->chunked = 1; - } - return; - } - if (!strncmp(str, "connection: ", 12)) - { - str = eatwhitespace(str+12); - if(!strncmp(str, "close", 6)) - { - cx->cclose = 1; - } - return; - } -} - -static void process_byte(struct http_ctx *cx, char ch) -{ - if (cx->rxtogo) - { - cx->rxtogo--; - - if (!cx->rbuf) - { - cx->rbuf = (char *)malloc(256); - cx->rlen = 256; - } - if (cx->rptr >= cx->rlen-1) - { - cx->rlen *= 2; - cx->rbuf = (char *)realloc(cx->rbuf, cx->rlen); - } - cx->rbuf[cx->rptr++] = ch; - - if (!cx->rxtogo && !cx->chunked) - cx->state = HTS_DONE; - } - else - { - if (ch == '\n') - { - cx->hbuf[cx->hptr] = 0; - process_header(cx, cx->hbuf); - cx->hptr = 0; - } - else if (ch != '\r') - { - if (cx->hptr >= cx->hlen-1) - { - cx->hlen *= 2; - cx->hbuf = (char *)realloc(cx->hbuf, cx->hlen); - } - cx->hbuf[cx->hptr++] = tolower(ch); - } - } -} - -int http_async_req_status(void *ctx) -{ - struct http_ctx *cx = (http_ctx *)ctx; - char *dns,*srv,buf[CHUNK]; - int tmp, i; - time_t now = time(NULL); -#ifdef WIN - unsigned long tmp2; -#endif - - switch (cx->state) - { - case HTS_STRT: - dns = getserv(cx->host); - srv = getport(cx->host); - if (resolve(dns, srv, &cx->addr)) - { - free(dns); - free(srv); - cx->state = HTS_DONE; - cx->ret = 602; - return 1; - } - free(dns); - free(srv); - cx->state = HTS_RSLV; - return 0; - case HTS_RSLV: - cx->state = HTS_CONN; - cx->last = now; - return 0; - case HTS_CONN: - if (cx->fd == PERROR) - { - cx->fd = socket(AF_INET, SOCK_STREAM, 0); - if (cx->fd == PERROR) - goto fail; - cx->fdhost = mystrdup(cx->host); -#ifdef WIN - tmp2 = 1; - if (ioctlsocket(cx->fd, FIONBIO, &tmp2) == SOCKET_ERROR) - goto fail; -#else - tmp = fcntl(cx->fd, F_GETFL); - if (tmp < 0) - goto fail; - if (fcntl(cx->fd, F_SETFL, tmp|O_NONBLOCK) < 0) - goto fail; -#endif - } - if (!connect(cx->fd, (struct sockaddr *)&cx->addr, sizeof(cx->addr))) - cx->state = HTS_IDLE; -#ifdef WIN - else if (PERRNO==WSAEISCONN) - cx->state = HTS_IDLE; -#endif -#if defined(MACOSX) || defined(BSD) - else if (PERRNO==EISCONN) - cx->state = HTS_IDLE; -#endif - else if (PERRNO!=PEINPROGRESS && PERRNO!=PEALREADY -#ifdef WIN - && PERRNO!=PEAGAIN && PERRNO!=WSAEINVAL -#endif - ) - goto fail; - if (now-cx->last>http_timeout) - goto timeout; - return 0; - case HTS_IDLE: - if (cx->txdl) - { - // generate POST - cx->tbuf = (char *)malloc(strlen(cx->host) + strlen(cx->path) + 132 + strlen(userAgent) + cx->txdl + cx->thlen); - cx->tptr = 0; - cx->tlen = 0; - cx->tlen += sprintf(cx->tbuf+cx->tlen, "POST %s HTTP/1.1\r\n", cx->path); - cx->tlen += sprintf(cx->tbuf+cx->tlen, "Host: %s\r\n", cx->host); - if (!cx->keep) - cx->tlen += sprintf(cx->tbuf+cx->tlen, "Connection: close\r\n"); - if (cx->thdr) - { - memcpy(cx->tbuf+cx->tlen, cx->thdr, cx->thlen); - cx->tlen += cx->thlen; - free(cx->thdr); - cx->thdr = NULL; - cx->thlen = 0; - } - cx->tlen += sprintf(cx->tbuf+cx->tlen, "Content-Length: %d\r\n", cx->txdl); - cx->tlen += sprintf(cx->tbuf+cx->tlen, "User-Agent: %s\r\n", userAgent); - cx->tlen += sprintf(cx->tbuf+cx->tlen, "\r\n"); - memcpy(cx->tbuf+cx->tlen, cx->txd, cx->txdl); - cx->tlen += cx->txdl; - free(cx->txd); - cx->txd = NULL; - cx->txdl = 0; - } - else - { - // generate GET - cx->tbuf = (char *)malloc(strlen(cx->host) + strlen(cx->path) + 98 + strlen(userAgent) + cx->thlen); - cx->tptr = 0; - cx->tlen = 0; - cx->tlen += sprintf(cx->tbuf+cx->tlen, "GET %s HTTP/1.1\r\n", cx->path); - cx->tlen += sprintf(cx->tbuf+cx->tlen, "Host: %s\r\n", cx->host); - if (cx->thdr) - { - memcpy(cx->tbuf+cx->tlen, cx->thdr, cx->thlen); - cx->tlen += cx->thlen; - free(cx->thdr); - cx->thdr = NULL; - cx->thlen = 0; - } - if (!cx->keep) - cx->tlen += sprintf(cx->tbuf+cx->tlen, "Connection: close\r\n"); - cx->tlen += sprintf(cx->tbuf+cx->tlen, "User-Agent: %s\r\n", userAgent); - cx->tlen += sprintf(cx->tbuf+cx->tlen, "\r\n"); - } - cx->state = HTS_XMIT; - cx->last = now; - return 0; - case HTS_XMIT: - tmp = send(cx->fd, cx->tbuf+cx->tptr, cx->tlen-cx->tptr, 0); - if (tmp==PERROR && PERRNO!=PEAGAIN && PERRNO!=PEINTR) - goto fail; - if (tmp!=PERROR && tmp) - { - cx->tptr += tmp; - if (cx->tptr == cx->tlen) - { - cx->tptr = 0; - cx->tlen = 0; - if (cx->tbuf) - { - free(cx->tbuf); - cx->tbuf = NULL; - } - cx->state = HTS_RECV; - } - cx->last = now; - } - if (now-cx->last>http_timeout) - goto timeout; - return 0; - case HTS_RECV: - tmp = recv(cx->fd, buf, CHUNK, 0); - if (tmp==PERROR && PERRNO!=PEAGAIN && PERRNO!=PEINTR) - goto fail; - if (tmp!=PERROR && tmp) - { - for (i=0; istate == HTS_DONE) - return 1; - } - cx->last = now; - } - if (now-cx->last>http_timeout) - goto timeout; - return 0; - case HTS_DONE: - return 1; - } - return 0; - -fail: - cx->ret = 600; - cx->state = HTS_DONE; - return 1; - -timeout: - cx->ret = 605; - cx->state = HTS_DONE; - return 1; -} - -char *http_async_req_stop(void *ctx, int *ret, int *len) -{ - struct http_ctx *cx = (http_ctx *)ctx; - char *rxd; - - if (cx->state != HTS_DONE) - while (!http_async_req_status(ctx)) - Platform::Millisleep(1); - - if (cx->host) - { - free(cx->host); - cx->host = NULL; - } - if (cx->path) - { - free(cx->path); - cx->path = NULL; - } - if (cx->txd) - { - free(cx->txd); - cx->txd = NULL; - cx->txdl = 0; - } - if (cx->tbuf) - { - free(cx->tbuf); - cx->tbuf = NULL; - } - if (cx->hbuf) - { - free(cx->hbuf); - cx->hbuf = NULL; - } - if (cx->thdr) - { - free(cx->thdr); - cx->thdr = NULL; - cx->thlen = 0; - } - - if (ret) - *ret = cx->ret; - if (len) - *len = cx->rptr; - if (cx->rbuf) - cx->rbuf[cx->rptr] = 0; - rxd = cx->rbuf; - cx->rbuf = NULL; - cx->rlen = 0; - cx->rptr = 0; - cx->contlen = 0; - - if (!cx->keep) - http_async_req_close(ctx); - else if (cx->cclose) - { - PCLOSE(cx->fd); - cx->fd = PERROR; - if (cx->fdhost) - { - free(cx->fdhost); - cx->fdhost = NULL; - } - cx->state = HTS_STRT; - } - else - cx->state = HTS_IDLE; - - return rxd; -} - -void http_async_get_length(void *ctx, int *total, int *done) -{ - struct http_ctx *cx = (http_ctx *)ctx; - if (done) - *done = cx->rptr; - if (total) - *total = cx->contlen; -} - -void http_force_close(void *ctx) -{ - struct http_ctx *cx = (struct http_ctx*)ctx; - cx->state = HTS_DONE; -} - -void http_async_req_close(void *ctx) -{ - struct http_ctx *cx = (http_ctx *)ctx; - void *tmp; - if (cx->host) - { - cx->keep = 1; - tmp = http_async_req_stop(ctx, NULL, NULL); - free(tmp); - } - free(cx->fdhost); - PCLOSE(cx->fd); - free(ctx); -} - -char *http_simple_get(const char *uri, int *ret, int *len) -{ - void *ctx = http_async_req_start(NULL, uri, NULL, 0, 0); - if (!ctx) - { - if (ret) - *ret = 600; - if (len) - *len = 0; - return NULL; - } - return http_async_req_stop(ctx, ret, len); -} -void http_auth_headers(void *ctx, const char *user, const char *pass, const char *session_id) -{ - char *tmp; - int i; - unsigned char hash[16]; - struct md5_context md5; - - if (user && strlen(user)) - { - if (pass) - { - md5_init(&md5); - md5_update(&md5, (unsigned char *)user, strlen(user)); - md5_update(&md5, (unsigned char *)"-", 1); - - md5_update(&md5, (unsigned char *)pass, strlen(pass)); - md5_final(hash, &md5); - tmp = (char *)malloc(33); - for (i=0; i<16; i++) - { - tmp[i*2] = hexChars[hash[i]>>4]; - tmp[i*2+1] = hexChars[hash[i]&15]; - } - tmp[32] = 0; - http_async_add_header(ctx, "X-Auth-Hash", tmp); - free(tmp); - } - if (session_id && strlen(session_id)) - { - http_async_add_header(ctx, "X-Auth-User-Id", user); - http_async_add_header(ctx, "X-Auth-Session-Key", session_id); - } - else - { - http_async_add_header(ctx, "X-Auth-User", user); - } - } -} -char *http_auth_get(const char *uri, const char *user, const char *pass, const char *session_id, int *ret, int *len) -{ - void *ctx = http_async_req_start(NULL, uri, NULL, 0, 0); - - if (!ctx) - { - if (ret) - *ret = 600; - if (len) - *len = 0; - return NULL; - } - http_auth_headers(ctx, user, pass, session_id); - return http_async_req_stop(ctx, ret, len); -} - -char *http_simple_post(const char *uri, const char *data, int dlen, int *ret, int *len) -{ - void *ctx = http_async_req_start(NULL, uri, data, dlen, 0); - if (!ctx) - { - if (ret) - *ret = 600; - if (len) - *len = 0; - return NULL; - } - return http_async_req_stop(ctx, ret, len); -} - -const char *http_ret_text(int ret) -{ - switch (ret) - { - case 0: - return "Status code 0 (bug?)"; - - case 100: - return "Continue"; - case 101: - return "Switching Protocols"; - case 102: - return "Processing"; - - case 200: - return "OK"; - case 201: - return "Created"; - case 202: - return "Accepted"; - case 203: - return "Non-Authoritative Information"; - case 204: - return "No Content"; - case 205: - return "Reset Content"; - case 206: - return "Partial Content"; - case 207: - return "Multi-Status"; - - case 300: - return "Multiple Choices"; - case 301: - return "Moved Permanently"; - case 302: - return "Found"; - case 303: - return "See Other"; - case 304: - return "Not Modified"; - case 305: - return "Use Proxy"; - case 306: - return "Switch Proxy"; - case 307: - return "Temporary Redirect"; - - case 400: - return "Bad Request"; - case 401: - return "Unauthorized"; - case 402: - return "Payment Required"; - case 403: - return "Forbidden"; - case 404: - return "Not Found"; - case 405: - return "Method Not Allowed"; - case 406: - return "Not Acceptable"; - case 407: - return "Proxy Authentication Required"; - case 408: - return "Request Timeout"; - case 409: - return "Conflict"; - case 410: - return "Gone"; - case 411: - return "Length Required"; - case 412: - return "Precondition Failed"; - case 413: - return "Request Entity Too Large"; - case 414: - return "Request URI Too Long"; - case 415: - return "Unsupported Media Type"; - case 416: - return "Requested Range Not Satisfiable"; - case 417: - return "Expectation Failed"; - case 418: - return "I'm a teapot"; - case 422: - return "Unprocessable Entity"; - case 423: - return "Locked"; - case 424: - return "Failed Dependency"; - case 425: - return "Unordered Collection"; - case 426: - return "Upgrade Required"; - case 444: - return "No Response"; - case 450: - return "Blocked by Windows Parental Controls"; - case 499: - return "Client Closed Request"; - - case 500: - return "Internal Server Error"; - case 501: - return "Not Implemented"; - case 502: - return "Bad Gateway"; - case 503: - return "Service Unavailable"; - case 504: - return "Gateway Timeout"; - case 505: - return "HTTP Version Not Supported"; - case 506: - return "Variant Also Negotiates"; - case 507: - return "Insufficient Storage"; - case 509: - return "Bandwidth Limit Exceeded"; - case 510: - return "Not Extended"; - - case 600: - return "Internal Client Error"; - case 601: - return "Unsupported Protocol"; - case 602: - return "Server Not Found"; - case 603: - return "Malformed Response"; - case 604: - return "Network Not Available"; - case 605: - return "Request Timed Out"; - default: - return "Unknown Status Code"; - } -} - -// Find the boundary used in the multipart POST request -// the boundary is a string that never appears in any of the parts, ex. 'A92' -// keeps looking recursively until it finds one -ByteString FindBoundary(std::map parts, ByteString boundary) -{ - // we only look for a-zA-Z0-9 chars - unsigned int map[62]; - size_t blen = boundary.length(); - std::fill(&map[0], &map[62], 0); - for (std::map::iterator iter = parts.begin(); iter != parts.end(); iter++) - { - // loop through every character in each part and search for the substring, adding 1 to map for every character found (character after the substring) - for (ssize_t j = 0; j < (ssize_t)((*iter).second.length()-blen); j++) - if (!blen || (*iter).second.Substr(j, blen) == boundary) - { - unsigned char ch = (*iter).second[j+blen]; - if (ch >= '0' && ch <= '9') - map[ch-'0']++; - else if (ch >= 'A' && ch <= 'Z') - map[ch-'A'+10]++; - else if (ch >= 'a' && ch <= 'z') - map[ch-'a'+36]++; - } - } - // find which next character occurs the least (preferably it occurs 0 times which means we have a match) - unsigned int lowest = 0; - for (unsigned int i = 1; i < 62; i++) - { - if (!map[lowest]) - break; - if (map[i] < map[lowest]) - lowest = i; - } - - // add the least frequent character to our boundary - if (lowest < 10) - boundary += '0'+lowest; - else if (lowest < 36) - boundary += 'A'+(lowest-10); - else - boundary += 'a'+(lowest-36); - - if (map[lowest]) - return FindBoundary(parts, boundary); - else - return boundary; -} - -// Generates a MIME multipart message to be used in POST requests -// see https://en.wikipedia.org/wiki/MIME#Multipart_messages -// this function used in Request class, and eventually all http requests -ByteString GetMultipartMessage(std::map parts, ByteString boundary) -{ - ByteStringBuilder data; - - // loop through each part, adding it - for (std::map::iterator iter = parts.begin(); iter != parts.end(); iter++) - { - ByteString name = (*iter).first; - ByteString value = (*iter).second; - - data << "--" << boundary << "\r\n"; - data << "Content-transfer-encoding: binary" << "\r\n"; - - // colon p - if (ByteString::Split split = name.SplitBy(':')) - { - // used to upload files (save data) - data << "content-disposition: form-data; name=\"" << split.Before() << "\""; - data << "; filename=\"" << split.After() << "\""; - } - else - { - data << "content-disposition: form-data; name=\"" << name << "\""; - } - data << "\r\n\r\n"; - data << value; - data << "\r\n"; - } - data << "--" << boundary << "--\r\n"; - return data.Build(); -} - -// add the header needed to make POSTS work -void http_add_multipart_header(void *ctx, ByteString boundary) -{ - ByteString header = "multipart/form-data; boundary=" + boundary; - http_async_add_header(ctx, "Content-type", header.c_str()); -} - -char *http_multipart_post(const char *uri, const char *const *names, const char *const *parts, size_t *plens, const char *user, const char *pass, const char *session_id, int *ret, int *len) -{ - void *ctx; - char *data = NULL, *tmp; - int dlen = 0, i, j; - unsigned char hash[16]; - unsigned char boundary[32], ch; - int blen = 0; - unsigned int map[62], m; - struct md5_context md5; - //struct md5_context md52; - int own_plen = 0; - - if (names) - { - if (!plens) - { - own_plen = 1; - for (i=0; names[i]; i++) ; - plens = (size_t *)calloc(i, sizeof(size_t)); - for (i=0; names[i]; i++) - plens[i] = strlen(parts[i]); - } - -retry: - if (blen >= 31) - goto fail; - memset(map, 0, 62*sizeof(int)); - for (i=0; names[i]; i++) - { - for (ssize_t j=0; j<(ssize_t)plens[i]-blen; j++) - if (!blen || !memcmp(parts[i]+j, boundary, blen)) - { - ch = parts[i][j+blen]; - if (ch>='0' && ch<='9') - map[ch-'0']++; - else if (ch>='A' && ch<='Z') - map[ch-'A'+10]++; - else if (ch>='a' && ch<='z') - map[ch-'a'+36]++; - } - } - m = ~0; - j = 61; - for (i=0; i<62; i++) - if (map[i]>4]; - tmp[i*2+1] = hexChars[hash[i]&15]; - } - tmp[32] = 0; - http_async_add_header(ctx, "X-Auth-Hash", tmp); - free(tmp); - } - if (session_id) - { - http_async_add_header(ctx, "X-Auth-User-Id", user); - http_async_add_header(ctx, "X-Auth-Session-Key", session_id); - } - else - { - http_async_add_header(ctx, "X-Auth-User", user); - } - } - - if (data) - { - tmp = (char *)malloc(32+strlen((char *)boundary)); - sprintf(tmp, "multipart/form-data; boundary=%s", boundary); - http_async_add_header(ctx, "Content-type", tmp); - free(tmp); - free(data); - } - - if (own_plen) - free(plens); - return http_async_req_stop(ctx, ret, len); - -fail: - free(data); - if (own_plen) - free(plens); - if (ret) - *ret = 600; - if (len) - *len = 0; - return NULL; -} - - -void *http_multipart_post_async(const char *uri, const char *const *names, const char *const *parts, int *plens, const char *user, const char *pass, const char *session_id) -{ - void *ctx; - char *data = NULL, *tmp; - int dlen = 0, i, j; - unsigned char hash[16]; - unsigned char boundary[32], ch; - int blen = 0; - unsigned int map[62], m; - struct md5_context md5; - //struct md5_context md52; - int own_plen = 0; - - if (names) - { - if (!plens) - { - own_plen = 1; - for (i=0; names[i]; i++) ; - plens = (int *)calloc(i, sizeof(int)); - for (i=0; names[i]; i++) - plens[i] = strlen(parts[i]); - } - -retry: - if (blen >= 31) - goto fail; - memset(map, 0, 62*sizeof(int)); - for (i=0; names[i]; i++) - { - for (j=0; j='0' && ch<='9') - map[ch-'0']++; - else if (ch>='A' && ch<='Z') - map[ch-'A'+10]++; - else if (ch>='a' && ch<='z') - map[ch-'a'+36]++; - } - } - m = ~0; - j = 61; - for (i=0; i<62; i++) - if (map[i]>4]; - tmp[i*2+1] = hexChars[hash[i]&15]; - } - tmp[32] = 0; - http_async_add_header(ctx, "X-Auth-Hash", tmp); - free(tmp); - } - if (session_id) - { - http_async_add_header(ctx, "X-Auth-User-Id", user); - http_async_add_header(ctx, "X-Auth-Session-Key", session_id); - } - else - { - http_async_add_header(ctx, "X-Auth-User", user); - } - } - - if (data) - { - tmp = (char *)malloc(32+strlen((char *)boundary)); - sprintf(tmp, "multipart/form-data; boundary=%s", boundary); - http_async_add_header(ctx, "Content-type", tmp); - free(tmp); - free(data); - } - - if (own_plen) - free(plens); - - return ctx; - -fail: - free(data); - if (own_plen) - free(plens); - //if (ret) - // *ret = 600; - //if (len) - // *len = 0; - return NULL; -} diff --git a/src/client/http/HTTP.h b/src/client/http/HTTP.h deleted file mode 100644 index 531342ad2..000000000 --- a/src/client/http/HTTP.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Powder Toy - HTTP Library (Header) - * - * Copyright (c) 2008 - 2010 Stanislaw Skowronek. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ -#ifndef HTTP_H -#define HTTP_H - -#include -#include "common/String.h" - -static const char hexChars[] = "0123456789abcdef"; -static const long http_timeout = 15; - -void http_init(char *proxy); -void http_done(void); - -char *http_simple_get(const char *uri, int *ret, int *len); -char *http_auth_get(const char *uri, const char *user, const char *pass, const char *session_id, int *ret, int *len); -char *http_simple_post(const char *uri, const char *data, int dlen, int *ret, int *len); - -void http_auth_headers(void *ctx, const char *user, const char *pass, const char *session_id); - -void *http_async_req_start(void *ctx, const char *uri, const char *data, int dlen, int keep); -void http_async_add_header(void *ctx, const char *name, const char *data); -int http_async_req_status(void *ctx); -void http_async_get_length(void *ctx, int *total, int *done); -char *http_async_req_stop(void *ctx, int *ret, int *len); -void http_async_req_close(void *ctx); -void http_force_close(void *ctx); - -ByteString FindBoundary(std::map, ByteString boundary); -ByteString GetMultipartMessage(std::map, ByteString boundary); -void http_add_multipart_header(void *ctx, ByteString boundary); -char *http_multipart_post(const char *uri, const char *const *names, const char *const *parts, size_t *plens, const char *user, const char *pass, const char * session_id, int *ret, int *len); -void *http_multipart_post_async(const char *uri, const char *const *names, const char *const *parts, int *plens, const char *user, const char *pass, const char *session_id); - -const char *http_ret_text(int ret); - -#endif diff --git a/src/client/http/Request.cpp b/src/client/http/Request.cpp index 7d6b44a47..68876854d 100644 --- a/src/client/http/Request.cpp +++ b/src/client/http/Request.cpp @@ -1,174 +1,300 @@ -#include #include "Request.h" #include "RequestManager.h" -#include "HTTP.h" #include "Platform.h" namespace http { -Request::Request(ByteString uri_, bool keepAlive): - http(NULL), - keepAlive(keepAlive), - requestData(NULL), - requestSize(0), - requestStatus(0), - postData(""), - postDataBoundary(""), - userID(""), - userSession(""), - requestFinished(false), - requestCanceled(false), - requestStarted(false) -{ - uri = ByteString(uri_); - RequestManager::Ref().AddRequest(this); -} - -// called by request thread itself if request was canceled -Request::~Request() -{ - if (http && (keepAlive || requestCanceled)) - http_async_req_close(http); - if (requestData) - free(requestData); -} - -// add post data to a request -void Request::AddPostData(std::map data) -{ - postDataBoundary = FindBoundary(data, ""); - postData = GetMultipartMessage(data, postDataBoundary); -} -void Request::AddPostData(std::pair data) -{ - std::map postData; - postData.insert(data); - AddPostData(postData); -} - -// add userID and sessionID headers to the request. Must be done after request starts for some reason -void Request::AuthHeaders(ByteString ID, ByteString session) -{ - if (ID != "0") - userID = ID; - userSession = session; -} - -// start the request thread -void Request::Start() -{ - if (CheckStarted() || CheckDone()) - return; - http = http_async_req_start(http, uri.c_str(), postData.c_str(), postData.length(), keepAlive ? 1 : 0); - // add the necessary headers - if (userID.length() || userSession.length()) - http_auth_headers(http, userID.c_str(), NULL, userSession.c_str()); - if (postDataBoundary.length()) - http_add_multipart_header(http, postDataBoundary); - RequestManager::Ref().Lock(); - requestStarted = true; - RequestManager::Ref().Unlock(); -} - - -// finish the request (if called before the request is done, this will block) -ByteString Request::Finish(int *status) -{ - if (CheckCanceled()) - return ""; // shouldn't happen but just in case - while (!CheckDone()); // block - RequestManager::Ref().Lock(); - requestStarted = false; - if (status) - *status = requestStatus; - ByteString ret; - if (requestData) + Request::Request(ByteString uri_): + uri(uri_), + rm_total(0), + rm_done(0), + rm_finished(false), + rm_canceled(false), + rm_started(false), + added_to_multi(false), + status(0), + headers(NULL), + post_fields(NULL) { - ret = ByteString(requestData, requestData + requestSize); - free(requestData); + pthread_cond_init(&done_cv, NULL); + pthread_mutex_init(&rm_mutex, NULL); + easy = curl_easy_init(); + RequestManager::Ref().AddRequest(this); } - requestData = NULL; - if (!keepAlive) - requestCanceled = true; - RequestManager::Ref().Unlock(); - return ret; -} -// returns the request size and progress (if the request has the correct length headers) -void Request::CheckProgress(int *total, int *done) -{ - RequestManager::Ref().Lock(); - if (!requestFinished && http) - http_async_get_length(http, total, done); - else - *total = *done = 0; - RequestManager::Ref().Unlock(); -} - -// returns true if the request has finished -bool Request::CheckDone() -{ - RequestManager::Ref().Lock(); - bool ret = requestFinished; - RequestManager::Ref().Unlock(); - return ret; -} - -// returns true if the request was canceled -bool Request::CheckCanceled() -{ - RequestManager::Ref().Lock(); - bool ret = requestCanceled; - RequestManager::Ref().Unlock(); - return ret; -} - -// returns true if the request is running -bool Request::CheckStarted() -{ - RequestManager::Ref().Lock(); - bool ret = requestStarted; - RequestManager::Ref().Unlock(); - return ret; - -} - -// cancels the request, the request thread will delete the Request* when it finishes (do not use Request in any way after canceling) -void Request::Cancel() -{ - RequestManager::Ref().Lock(); - requestCanceled = true; - RequestManager::Ref().Unlock(); -} - -ByteString Request::Simple(ByteString uri, int *status, std::map post_data) -{ - Request *request = new Request(uri); - request->AddPostData(post_data); - request->Start(); - while(!request->CheckDone()) + Request::~Request() { - Platform::Millisleep(1); + curl_easy_cleanup(easy); + curl_mime_free(post_fields); + curl_slist_free_all(headers); + pthread_mutex_destroy(&rm_mutex); + pthread_cond_destroy(&done_cv); } - return request->Finish(status); -} -ByteString Request::SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map post_data) -{ - Request *request = new Request(uri); - request->AddPostData(post_data); - request->AuthHeaders(ID, session); - request->Start(); - while(!request->CheckDone()) + void Request::AddHeader(ByteString name, ByteString value) { - Platform::Millisleep(1); + headers = curl_slist_append(headers, (name + ": " + value).c_str()); + } + + // add post data to a request + void Request::AddPostData(std::map data) + { + if (!data.size()) + { + return; + } + + if (easy) + { + if (!post_fields) + { + post_fields = curl_mime_init(easy); + } + + for (auto &field : data) + { + curl_mimepart *part = curl_mime_addpart(post_fields); + curl_mime_data(part, &field.second[0], field.second.size()); + if (auto split = field.first.SplitBy(':')) + { + curl_mime_name(part, split.Before().c_str()); + curl_mime_filename(part, split.After().c_str()); + } + else + { + curl_mime_name(part, field.first.c_str()); + } + } + } + } + + // add userID and sessionID headers to the request + void Request::AuthHeaders(ByteString ID, ByteString session) + { + if (ID.size()) + { + if (session.size()) + { + AddHeader("X-Auth-User-Id", ID); + AddHeader("X-Auth-Session-Key", session); + } + else + { + AddHeader("X-Auth-User", ID); + } + } + } + + // start the request thread + void Request::Start() + { + if (CheckStarted() || CheckDone()) + { + return; + } + + if (easy) + { + if (post_fields) + { + curl_easy_setopt(easy, CURLOPT_MIMEPOST, post_fields); + } + else + { + curl_easy_setopt(easy, CURLOPT_HTTPGET, 1); + } + + curl_easy_setopt(easy, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt(easy, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(easy, CURLOPT_URL, uri.c_str()); + + if (proxy.size()) + { + curl_easy_setopt(easy, CURLOPT_PROXY, proxy.c_str()); + } + + curl_easy_setopt(easy, CURLOPT_PRIVATE, this); + curl_easy_setopt(easy, CURLOPT_USERAGENT, user_agent.c_str()); + curl_easy_setopt(easy, CURLOPT_NOSIGNAL, 1); + + curl_easy_setopt(easy, CURLOPT_WRITEDATA, this); + curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, (size_t (*)(char *ptr, size_t size, size_t count, void *userdata))([](char *ptr, size_t size, size_t count, void *userdata) -> size_t { + Request *req = (Request *)userdata; + auto actual_size = size * count; + req->response_body.append(ptr, actual_size); + return actual_size; + })); // curl_easy_setopt does something really ugly with parameters; I have to cast the lambda explicitly to the right kind of function pointer for some reason + } + + pthread_mutex_lock(&rm_mutex); + rm_started = true; + pthread_mutex_unlock(&rm_mutex); + } + + + // finish the request (if called before the request is done, this will block) + ByteString Request::Finish(int *status_out) + { + if (CheckCanceled()) + { + return ""; // shouldn't happen but just in case + } + + pthread_mutex_lock(&rm_mutex); + while (!rm_finished) + { + pthread_cond_wait(&done_cv, &rm_mutex); + } + rm_started = false; + rm_canceled = true; // signals to RequestManager that the Request can be deleted + ByteString response_out = std::move(response_body); + if (status_out) + { + *status_out = status; + } + pthread_mutex_unlock(&rm_mutex); + + return response_out; + } + + void Request::CheckProgress(int *total, int *done) + { + pthread_mutex_lock(&rm_mutex); + if (total) + { + *total = rm_total; + } + if (done) + { + *done = rm_done; + } + pthread_mutex_unlock(&rm_mutex); + } + + // returns true if the request has finished + bool Request::CheckDone() + { + pthread_mutex_lock(&rm_mutex); + bool ret = rm_finished; + pthread_mutex_unlock(&rm_mutex); + return ret; + } + + // returns true if the request was canceled + bool Request::CheckCanceled() + { + pthread_mutex_lock(&rm_mutex); + bool ret = rm_canceled; + pthread_mutex_unlock(&rm_mutex); + return ret; + } + + // returns true if the request is running + bool Request::CheckStarted() + { + pthread_mutex_lock(&rm_mutex); + bool ret = rm_started; + pthread_mutex_unlock(&rm_mutex); + return ret; + + } + + // cancels the request, the request thread will delete the Request* when it finishes (do not use Request in any way after canceling) + void Request::Cancel() + { + pthread_mutex_lock(&rm_mutex); + rm_canceled = true; + pthread_mutex_unlock(&rm_mutex); + } + + ByteString Request::Simple(ByteString uri, int *status, std::map post_data) + { + return SimpleAuth(uri, status, "", "", post_data); + } + + ByteString Request::SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map post_data) + { + Request *request = new Request(uri); + request->AddPostData(post_data); + request->AuthHeaders(ID, session); + request->Start(); + return request->Finish(status); + } + + const char *StatusText(int ret) + { + switch (ret) + { + case 0: return "Status code 0 (bug?)"; + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "Switch Proxy"; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Unordered Collection"; + case 426: return "Upgrade Required"; + case 444: return "No Response"; + case 450: return "Blocked by Windows Parental Controls"; + case 499: return "Client Closed Request"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 509: return "Bandwidth Limit Exceeded"; + case 510: return "Not Extended"; + case 600: return "Internal Client Error"; + case 601: return "Unsupported Protocol"; + case 602: return "Server Not Found"; + case 603: return "Malformed Response"; + case 604: return "Network Not Available"; + case 605: return "Request Timed Out"; + case 606: return "Malformed URL"; + case 607: return "Connection Refused"; + case 608: return "Proxy Server Not Found"; + case 609: return "SSL Failure"; + case 610: return "Cancelled by Shutdown"; + default: return "Unknown Status Code"; + } } - return request->Finish(status); -} - -const char *StatusText(int code) -{ - return http_ret_text(code); -} } diff --git a/src/client/http/Request.h b/src/client/http/Request.h index 77398417a..e7bf34130 100644 --- a/src/client/http/Request.h +++ b/src/client/http/Request.h @@ -1,54 +1,63 @@ #ifndef REQUEST_H #define REQUEST_H + #include +#include #include "common/String.h" namespace http { -class RequestManager; -class Request -{ - ByteString uri; - void *http; - bool keepAlive; + class RequestManager; + class Request + { + ByteString uri; + ByteString response_body; - char *requestData; - int requestSize; - int requestStatus; + CURL *easy; - ByteString postData; - ByteString postDataBoundary; + volatile curl_off_t rm_total; + volatile curl_off_t rm_done; + volatile bool rm_finished; + volatile bool rm_canceled; + volatile bool rm_started; + pthread_mutex_t rm_mutex; - ByteString userID; - ByteString userSession; + bool added_to_multi; + int status; - volatile bool requestFinished; - volatile bool requestCanceled; - volatile bool requestStarted; + struct curl_slist *headers; + curl_mime *post_fields; -public: - Request(ByteString uri, bool keepAlive = false); - virtual ~Request(); + pthread_cond_t done_cv; - void AddPostData(std::map data); - void AddPostData(std::pair data); - void AuthHeaders(ByteString ID, ByteString session); - void Start(); - ByteString Finish(int *status); - void Cancel(); + public: + Request(ByteString uri); + virtual ~Request(); - void CheckProgress(int *total, int *done); - bool CheckDone(); - bool CheckCanceled(); - bool CheckStarted(); + void AddHeader(ByteString name, ByteString value); + void AddPostData(std::map data); + void AuthHeaders(ByteString ID, ByteString session); - friend class RequestManager; + void Start(); + ByteString Finish(int *status); + void Cancel(); - static ByteString Simple(ByteString uri, int *status, std::map post_data = std::map{}); - static ByteString SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map post_data = std::map{}); -}; + void CheckProgress(int *total, int *done); + bool CheckDone(); + bool CheckCanceled(); + bool CheckStarted(); -const char *StatusText(int code); + friend class RequestManager; + + static ByteString Simple(ByteString uri, int *status, std::map post_data = std::map{}); + static ByteString SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map post_data = std::map{}); + }; + + const char *StatusText(int code); + + extern const long timeout; + extern ByteString proxy; + extern ByteString user_agent; } -#endif +#endif // REQUEST_H diff --git a/src/client/http/RequestManager.cpp b/src/client/http/RequestManager.cpp index 1bfcd32c9..5c4d5083d 100644 --- a/src/client/http/RequestManager.cpp +++ b/src/client/http/RequestManager.cpp @@ -1,164 +1,212 @@ #include "RequestManager.h" #include "Request.h" -#include "HTTP.h" #include "Config.h" #include "Platform.h" +const int curl_multi_wait_timeout_ms = 100; +const long curl_max_host_connections = 6; + namespace http { -RequestManager::RequestManager(): - threadStarted(false), - lastUsed(time(NULL)), - managerRunning(false), - managerShutdown(false), - requests(std::vector()), - requestsAddQueue(std::vector()) -{ - pthread_mutex_init(&requestLock, NULL); - pthread_mutex_init(&requestAddLock, NULL); -} + const long timeout = 15; + ByteString proxy; + ByteString user_agent; -RequestManager::~RequestManager() -{ - -} - -void RequestManager::Shutdown() -{ - pthread_mutex_lock(&requestLock); - pthread_mutex_lock(&requestAddLock); - for (std::vector::iterator iter = requests.begin(); iter != requests.end(); ++iter) + RequestManager::RequestManager(): + rt_shutting_down(false), + multi(NULL) { - Request *request = (*iter); - if (request->http) - http_force_close(request->http); - request->requestCanceled = true; - delete request; + pthread_cond_init(&rt_cv, NULL); + pthread_mutex_init(&rt_mutex, NULL); } - requests.clear(); - requestsAddQueue.clear(); - managerShutdown = true; - pthread_mutex_unlock(&requestAddLock); - pthread_mutex_unlock(&requestLock); - if (threadStarted) - pthread_join(requestThread, NULL); - - http_done(); -} -//helper function for request -TH_ENTRY_POINT void* RequestManagerHelper(void* obj) -{ - RequestManager *temp = (RequestManager*)obj; - temp->Update(); - return NULL; -} - -void RequestManager::Initialise(ByteString Proxy) -{ - proxy = Proxy; - if (proxy.length()) + RequestManager::~RequestManager() { - http_init((char *)proxy.c_str()); + pthread_mutex_destroy(&rt_mutex); + pthread_cond_destroy(&rt_cv); } - else + + void RequestManager::Shutdown() { - http_init(NULL); + pthread_mutex_lock(&rt_mutex); + rt_shutting_down = true; + pthread_cond_signal(&rt_cv); + pthread_mutex_unlock(&rt_mutex); + + pthread_join(worker_thread, NULL); + + curl_multi_cleanup(multi); + multi = NULL; + curl_global_cleanup(); } -} -void RequestManager::Start() -{ - managerRunning = true; - lastUsed = time(NULL); - pthread_create(&requestThread, NULL, &RequestManagerHelper, this); -} - -void RequestManager::Update() -{ - unsigned int numActiveRequests = 0; - while (!managerShutdown) + TH_ENTRY_POINT void *RequestManager::RequestManagerHelper(void *obj) { - pthread_mutex_lock(&requestAddLock); - if (requestsAddQueue.size()) + ((RequestManager *)obj)->Worker(); + return NULL; + } + + void RequestManager::Initialise(ByteString Proxy) + { + curl_global_init(CURL_GLOBAL_DEFAULT); + multi = curl_multi_init(); + if (multi) { - for (size_t i = 0; i < requestsAddQueue.size(); i++) - { - requests.push_back(requestsAddQueue[i]); - } - requestsAddQueue.clear(); + curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, curl_max_host_connections); } - pthread_mutex_unlock(&requestAddLock); - if (requests.size()) + + proxy = Proxy; + + user_agent = ByteString::Build("PowderToy/", SAVE_VERSION, ".", MINOR_VERSION, " (", IDENT_PLATFORM, "; ", IDENT_BUILD, "; M", MOD_ID, ") TPTPP/", SAVE_VERSION, ".", MINOR_VERSION, ".", BUILD_NUM, IDENT_RELTYPE, ".", SNAPSHOT_ID); + + pthread_create(&worker_thread, NULL, &RequestManager::RequestManagerHelper, this); + } + + void RequestManager::Worker() + { + bool shutting_down = false; + while (!shutting_down) { - numActiveRequests = 0; - pthread_mutex_lock(&requestLock); - for (size_t i = 0; i < requests.size(); i++) + for (Request *request : requests_to_remove) { - Request *request = requests[i]; - if (request->requestCanceled) + requests.erase(request); + if (multi && request->easy && request->added_to_multi) { - if (request->http && request->requestStarted) - http_force_close(request->http); - delete request; - requests.erase(requests.begin()+i); - i--; + curl_multi_remove_handle(multi, request->easy); + request->added_to_multi = false; } - else if (request->requestStarted && !request->requestFinished) + delete request; + } + requests_to_remove.clear(); + + pthread_mutex_lock(&rt_mutex); + shutting_down = rt_shutting_down; + for (Request *request : requests_to_add) + { + request->status = 0; + requests.insert(request); + } + requests_to_add.clear(); + if (requests.empty()) + { + while (!rt_shutting_down && requests_to_add.empty()) { - if (http_async_req_status(request->http) != 0) + pthread_cond_wait(&rt_cv, &rt_mutex); + } + } + pthread_mutex_unlock(&rt_mutex); + + if (multi && !requests.empty()) + { + int dontcare; + struct CURLMsg *msg; + + curl_multi_wait(multi, nullptr, 0, curl_multi_wait_timeout_ms, &dontcare); + curl_multi_perform(multi, &dontcare); + while ((msg = curl_multi_info_read(multi, &dontcare))) + { + if (msg->msg == CURLMSG_DONE) { - request->requestData = http_async_req_stop(request->http, &request->requestStatus, &request->requestSize); - request->requestFinished = true; - if (!request->keepAlive) - request->http = NULL; + Request *request; + curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &request); + + int finish_with = 600; + + switch (msg->data.result) + { + case CURLE_OK: + long code; + curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code); + finish_with = (int)code; + break; + + case CURLE_UNSUPPORTED_PROTOCOL: finish_with = 601; break; + case CURLE_COULDNT_RESOLVE_HOST: finish_with = 602; break; + case CURLE_OPERATION_TIMEDOUT: finish_with = 605; break; + case CURLE_URL_MALFORMAT: finish_with = 606; break; + case CURLE_COULDNT_CONNECT: finish_with = 607; break; + case CURLE_COULDNT_RESOLVE_PROXY: finish_with = 608; break; + + case CURLE_SSL_CONNECT_ERROR: + case CURLE_SSL_ENGINE_NOTFOUND: + case CURLE_SSL_ENGINE_SETFAILED: + case CURLE_SSL_CERTPROBLEM: + case CURLE_SSL_CIPHER: + case CURLE_SSL_ENGINE_INITFAILED: + case CURLE_SSL_CACERT_BADFILE: + case CURLE_SSL_CRL_BADFILE: + case CURLE_SSL_ISSUER_ERROR: + case CURLE_SSL_PINNEDPUBKEYNOTMATCH: + case CURLE_SSL_INVALIDCERTSTATUS: finish_with = 609; break; + + case CURLE_HTTP2: + case CURLE_HTTP2_STREAM: + + case CURLE_FAILED_INIT: + case CURLE_NOT_BUILT_IN: + default: + break; + } + + request->status = finish_with; } - lastUsed = time(NULL); - numActiveRequests++; - } + }; } - pthread_mutex_unlock(&requestLock); - } - if (time(NULL) > lastUsed+http_timeout*2 && !numActiveRequests) - { - pthread_mutex_lock(&requestLock); - managerRunning = false; - pthread_mutex_unlock(&requestLock); - return; - } - Platform::Millisleep(1); - } -} -void RequestManager::EnsureRunning() -{ - pthread_mutex_lock(&requestLock); - if (!managerRunning) + for (Request *request : requests) + { + pthread_mutex_lock(&request->rm_mutex); + + if (shutting_down) + { + // In the weird case that a http::Request::Simple* call is + // waiting on this Request, we should fail the request + // instead of cancelling it ourselves. + request->status = 610; + } + + if (request->rm_canceled) + { + requests_to_remove.insert(request); + } + + if (!request->rm_canceled && request->rm_started && !request->added_to_multi) + { + if (multi && request->easy) + { + curl_multi_add_handle(multi, request->easy); + request->added_to_multi = true; + } + else + { + request->status = 604; + } + } + + if (!request->rm_canceled && request->rm_started && !request->rm_finished) + { + if (multi && request->easy) + { + curl_easy_getinfo(request->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &request->rm_total); + curl_easy_getinfo(request->easy, CURLINFO_SIZE_DOWNLOAD_T, &request->rm_done); + } + if (request->status) + { + request->rm_finished = true; + pthread_cond_signal(&request->done_cv); + } + } + + pthread_mutex_unlock(&request->rm_mutex); + } + } + } + + void RequestManager::AddRequest(Request *request) { - if (threadStarted) - pthread_join(requestThread, NULL); - else - threadStarted = true; - Start(); + pthread_mutex_lock(&rt_mutex); + requests_to_add.insert(request); + pthread_cond_signal(&rt_cv); + pthread_mutex_unlock(&rt_mutex); } - pthread_mutex_unlock(&requestLock); -} - -void RequestManager::AddRequest(Request *request) -{ - pthread_mutex_lock(&requestAddLock); - requestsAddQueue.push_back(request); - pthread_mutex_unlock(&requestAddLock); - EnsureRunning(); -} - -void RequestManager::Lock() -{ - pthread_mutex_lock(&requestAddLock); -} - -void RequestManager::Unlock() -{ - pthread_mutex_unlock(&requestAddLock); -} } diff --git a/src/client/http/RequestManager.h b/src/client/http/RequestManager.h index 918e417c5..356495e04 100644 --- a/src/client/http/RequestManager.h +++ b/src/client/http/RequestManager.h @@ -1,46 +1,48 @@ #ifndef REQUESTMANAGER_H #define REQUESTMANAGER_H + #include "common/tpt-thread.h" #include -#include +#include +#include #include "common/Singleton.h" #include "common/String.h" namespace http { -class Request; -class RequestManager : public Singleton -{ -private: - pthread_t requestThread; - pthread_mutex_t requestLock; - pthread_mutex_t requestAddLock; - bool threadStarted; - ByteString proxy; + class Request; + class RequestManager : public Singleton + { + pthread_t worker_thread; + std::set requests; - int lastUsed; - volatile bool managerRunning; - volatile bool managerShutdown; - std::vector requests; - std::vector requestsAddQueue; + std::set requests_to_add; + std::set requests_to_remove; + bool rt_shutting_down; + pthread_mutex_t rt_mutex; + pthread_cond_t rt_cv; - void Start(); -public: - RequestManager(); - ~RequestManager(); + CURLM *multi; - void Initialise(ByteString proxy); + void Start(); + void Worker(); + void AddRequest(Request *request); - void Shutdown(); - void Update(); - void EnsureRunning(); + static TH_ENTRY_POINT void *RequestManagerHelper(void *obj); - void AddRequest(Request *request); - void RemoveRequest(int id); + public: + RequestManager(); + ~RequestManager(); - void Lock(); - void Unlock(); -}; + void Initialise(ByteString proxy); + void Shutdown(); + + friend class Request; + }; + + extern const long timeout; + extern ByteString proxy; + extern ByteString user_agent; } #endif // REQUESTMANAGER_H diff --git a/src/tasks/AbandonableTask.cpp b/src/tasks/AbandonableTask.cpp index dafb14580..3244d480c 100644 --- a/src/tasks/AbandonableTask.cpp +++ b/src/tasks/AbandonableTask.cpp @@ -42,7 +42,6 @@ void AbandonableTask::Start() thAbandoned = false; progress = 0; status = ""; - //taskMutex = PTHREAD_MUTEX_INITIALIZER; before(); pthread_mutex_init (&taskMutex, NULL); pthread_create(&doWorkThread, 0, &AbandonableTask::doWork_helper, this); @@ -79,6 +78,10 @@ TH_ENTRY_POINT void * AbandonableTask::doWork_helper(void * ref) void AbandonableTask::Finish() { + // note to self: if you make this wait for a condition variable, + // lock the corresponding mutex before calling GetDone, otherwise + // the CV may be signalled between the call and the locking of the + // mutex. -- LBPHacker while (!GetDone()) { Poll();