diff --git a/base/project.js b/base/project.js index 9de07035..ead7fa61 100644 --- a/base/project.js +++ b/base/project.js @@ -87,6 +87,7 @@ else if (platform == "linux") { project.add_lib("Xcursor"); project.add_lib("Xrandr"); project.add_lib("ssl"); + project.add_lib("crypto"); project.add_lib("vulkan"); if (flags.with_audio) { project.add_lib("asound"); diff --git a/base/sources/backends/posix_net.c b/base/sources/backends/posix_net.c index 0fc62d63..aeecc48c 100644 --- a/base/sources/backends/posix_net.c +++ b/base/sources/backends/posix_net.c @@ -1,76 +1,288 @@ +#include #include #include #include +#include #include #include #include #include #include -static SSL_CTX *ctx = NULL; -static char *buf = NULL; -static int buf_len = 1024 * 1024; +typedef struct { + SSL *ssl; + int sock_fd; + FILE *fp; + char *buf; + int buf_len; + iron_https_callback_t callback; + void *callbackdata; + char *url_base; + char *url_path; + int port; +} async_context_t; -void iron_net_request(const char *url_base, const char *url_path, const char *data, int port, int method, iron_https_callback_t callback, void *callbackdata, const char *dst_path) { +typedef struct request { + char *response; + iron_https_callback_t callback; + void *callbackdata; + struct request *next; +} request_t; - int sock_fd = socket(AF_INET, SOCK_STREAM, 0); - struct hostent *server = gethostbyname(url_base); - struct sockaddr_in server_addr = {0}; - server_addr.sin_family = AF_INET; - memmove(&server_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length); - server_addr.sin_port = htons(port); +volatile uint64_t iron_net_bytes_downloaded = 0; +static SSL_CTX *ctx = NULL; +static pthread_mutex_t ctx_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t pending_mutex = PTHREAD_MUTEX_INITIALIZER; +static request_t *pending_head = NULL; +static request_t *pending_tail = NULL; - if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { - close(sock_fd); - callback(NULL, callbackdata); - return; +static void finish_request(async_context_t *async_ctx, char *response) { + if (async_ctx->fp) { + fflush(async_ctx->fp); + fclose(async_ctx->fp); + } + if (async_ctx->ssl) { + SSL_shutdown(async_ctx->ssl); + SSL_free(async_ctx->ssl); + } + if (async_ctx->sock_fd >= 0) { + close(async_ctx->sock_fd); + } + if (async_ctx->buf) { + free(async_ctx->buf); + } + if (async_ctx->url_base) { + free(async_ctx->url_base); + } + if (async_ctx->url_path) { + free(async_ctx->url_path); + } + request_t *req = malloc(sizeof(request_t)); + req->response = response; + req->callback = async_ctx->callback; + req->callbackdata = async_ctx->callbackdata; + req->next = NULL; + pthread_mutex_lock(&pending_mutex); + if (pending_tail == NULL) { + pending_head = pending_tail = req; + } + else { + pending_tail->next = req; + pending_tail = req; + } + pthread_mutex_unlock(&pending_mutex); + free(async_ctx); +} + +static void parse_url(const char *url, char **host, char **path, int *port) { + const char *start = url + 8; + *port = 443; + const char *slash = strchr(start, '/'); + if (slash) { + *host = strndup(start, slash - start); + *path = strdup(slash + 1); + } + else { + *host = strdup(start); + *path = strdup(""); + } + char *colon = strchr(*host, ':'); + if (colon) { + *port = atoi(colon + 1); + *colon = '\0'; + } +} + +static int do_request(async_context_t *async_ctx) { + struct addrinfo hints = {0}; + struct addrinfo *res = NULL; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + char port_str[6]; + + snprintf(port_str, sizeof(port_str), "%d", async_ctx->port); + if (getaddrinfo(async_ctx->url_base, port_str, &hints, &res) != 0) { + return 0; } + int sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (connect(sock_fd, res->ai_addr, res->ai_addrlen) < 0) { + close(sock_fd); + freeaddrinfo(res); + return 0; + } + freeaddrinfo(res); + + pthread_mutex_lock(&ctx_mutex); if (ctx == NULL) { ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); } + pthread_mutex_unlock(&ctx_mutex); SSL *ssl = SSL_new(ctx); SSL_set_fd(ssl, sock_fd); - + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + SSL_set_tlsext_host_name(ssl, async_ctx->url_base); if (SSL_connect(ssl) <= 0) { SSL_free(ssl); close(sock_fd); - callback(NULL, callbackdata); - return; + return 0; } - // For HTTP/1.1, implement "transfer-encoding: chunked" - char request[1024]; - int request_len = snprintf(request, sizeof(request), "GET /%s HTTP/1.0\r\nHost: %s:%d\r\nConnection: close\r\n\r\n", url_path, url_base, port); + async_ctx->ssl = ssl; + async_ctx->sock_fd = sock_fd; + char request[4096]; + int request_len = snprintf(request, sizeof(request), "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", async_ctx->url_path, async_ctx->url_base); SSL_write(ssl, request, request_len); + int pos = 0; + int n; + int header_buf_size = 16384; + char *header_buf = malloc(header_buf_size); - // Read - if (buf == NULL) { - buf = malloc(buf_len); - } - int pos = 0; - int n; - while ((n = SSL_read(ssl, buf + pos, buf_len - pos - 1)) > 0) { + while ((n = SSL_read(ssl, header_buf + pos, header_buf_size - pos - 1)) > 0) { pos += n; - if (pos == buf_len - 1) { - buf_len *= 2; - buf = realloc(buf, buf_len); + header_buf[pos] = '\0'; + char *headers_end = strstr(header_buf, "\r\n\r\n"); + + if (strncmp(header_buf, "HTTP/1.1 302", 12) == 0) { + char *location = strstr(header_buf, "Location: "); + if (location) { + location += 10; + char *location_end = strstr(location, "\r\n"); + *location_end = '\0'; + char *new_host = NULL; + char *new_path = NULL; + int new_port = 0; + parse_url(location, &new_host, &new_path, &new_port); + SSL_shutdown(async_ctx->ssl); + SSL_free(async_ctx->ssl); + close(async_ctx->sock_fd); + async_ctx->ssl = NULL; + async_ctx->sock_fd = -1; + free(async_ctx->url_base); + free(async_ctx->url_path); + async_ctx->url_base = new_host; + async_ctx->url_path = new_path; + async_ctx->port = new_port; + free(header_buf); + return do_request(async_ctx); + } + } + + char *body_start = headers_end + 4; + int body_len = pos - (body_start - header_buf); + int is_chunked = strstr(header_buf, "transfer-encoding: chunked") != NULL; + + if (async_ctx->fp) { + // No chunked support for fp yet + if (body_len > 0) { + fwrite(body_start, 1, body_len, async_ctx->fp); + iron_net_bytes_downloaded += body_len; + } + while ((n = SSL_read(ssl, async_ctx->buf, 64 * 1024)) > 0) { + fwrite(async_ctx->buf, 1, n, async_ctx->fp); + iron_net_bytes_downloaded += n; + } + free(header_buf); + finish_request(async_ctx, NULL); + return 1; + } + else { + if (body_len > 0) { + if (body_len >= async_ctx->buf_len) { + async_ctx->buf_len = body_len + 1; + async_ctx->buf = realloc(async_ctx->buf, async_ctx->buf_len); + } + memcpy(async_ctx->buf, body_start, body_len); + pos = body_len; + } + else { + pos = 0; + } + while ((n = SSL_read(ssl, async_ctx->buf + pos, async_ctx->buf_len - pos - 1)) > 0) { + pos += n; + if (pos >= async_ctx->buf_len - 1) { + async_ctx->buf_len *= 2; + async_ctx->buf = realloc(async_ctx->buf, async_ctx->buf_len); + } + } + async_ctx->buf[pos] = '\0'; + free(header_buf); + if (is_chunked) { + char *chunk_ptr = async_ctx->buf; + char *out_ptr = async_ctx->buf; + while (1) { + char *end_ptr; + int chunk_size = strtol(chunk_ptr, &end_ptr, 16); + if (chunk_size == 0) + break; + if (end_ptr == chunk_ptr) + break; + chunk_ptr = end_ptr + 2; // \r\n + memmove(out_ptr, chunk_ptr, chunk_size); + out_ptr += chunk_size; + chunk_ptr += chunk_size + 2; // \r\n + } + *out_ptr = '\0'; + } + char *response = strdup(async_ctx->buf); + finish_request(async_ctx, response); + return 1; + } + + if (pos >= header_buf_size - 1) { + header_buf_size *= 2; + header_buf = realloc(header_buf, header_buf_size); } } - buf[pos] = '\0'; + free(header_buf); + return 0; +} - // Parse - const char *body_ptr = ""; - char *headers_end = strstr(buf, "\r\n\r\n"); - body_ptr = headers_end + 4; - callback(body_ptr, callbackdata); +static void *download_thread(void *arg) { + async_context_t *async_ctx = (async_context_t *)arg; + if (!do_request(async_ctx)) { + finish_request(async_ctx, NULL); + } + return NULL; +} - SSL_free(ssl); - close(sock_fd); +void iron_net_request(const char *url_base, const char *url_path, const char *data, int port, int method, iron_https_callback_t callback, void *callbackdata, + const char *dst_path) { + async_context_t *async_ctx = calloc(1, sizeof(async_context_t)); + async_ctx->callback = callback; + async_ctx->callbackdata = callbackdata; + async_ctx->buf_len = 1024 * 1024; + async_ctx->buf = malloc(async_ctx->buf_len); + async_ctx->fp = NULL; + async_ctx->sock_fd = -1; + async_ctx->ssl = NULL; + async_ctx->url_base = strdup(url_base); + async_ctx->url_path = strdup(url_path); + async_ctx->port = port; + if (dst_path) { + async_ctx->fp = fopen(dst_path, "wb"); + setvbuf(async_ctx->fp, NULL, _IOFBF, 256 * 1024); + } + pthread_t thread; + pthread_create(&thread, NULL, download_thread, async_ctx); + pthread_detach(thread); } void iron_net_update() { + request_t *current; + pthread_mutex_lock(&pending_mutex); + current = pending_head; + pending_head = pending_tail = NULL; + pthread_mutex_unlock(&pending_mutex); + while (current) { + request_t *next = current->next; + current->callback(current->response, current->callbackdata); + if (current->response) { + free(current->response); + } + free(current); + current = next; + } } - -volatile uint64_t iron_net_bytes_downloaded = 0;