289 lines
7.9 KiB
C
289 lines
7.9 KiB
C
#include <fcntl.h>
|
|
#include <iron_net.h>
|
|
#include <netdb.h>
|
|
#include <openssl/ssl.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
|
|
typedef struct request {
|
|
char *response;
|
|
iron_https_callback_t callback;
|
|
void *callbackdata;
|
|
struct request *next;
|
|
} request_t;
|
|
|
|
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;
|
|
|
|
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);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
|
|
while ((n = SSL_read(ssl, header_buf + pos, header_buf_size - pos - 1)) > 0) {
|
|
pos += n;
|
|
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);
|
|
}
|
|
}
|
|
free(header_buf);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|