#include "qjs_blob.h" #include "picohttpparser.h" #include #include #include #include #include #include #include #include // Simple dynamic buffer for reading the response typedef struct { char *data; size_t size; size_t capacity; } buffer_t; static void buffer_init(buffer_t *b) { b->data = NULL; b->size = 0; b->capacity = 0; } static int buffer_append(buffer_t *b, const void *p, size_t len) { if (b->size + len > b->capacity) { size_t new_cap = b->capacity ? b->capacity * 2 : 4096; while (new_cap < b->size + len) new_cap *= 2; char *nx = realloc(b->data, new_cap); if (!nx) return -1; b->data = nx; b->capacity = new_cap; } memcpy(b->data + b->size, p, len); b->size += len; return 0; } static void buffer_free(buffer_t *b) { free(b->data); b->data = NULL; b->size = 0; b->capacity = 0; } // Parses "http://host:port/path" or "https://host:port/path" static int parse_url(const char *url, int *use_ssl, char **host, char **port, char **path) { *host = *port = *path = NULL; *use_ssl = 0; const char *p = url; if (strncmp(p, "https://", 8) == 0) { *use_ssl = 1; p += 8; } else if (strncmp(p, "http://", 7) == 0) { *use_ssl = 0; p += 7; } else { return -1; } // find path const char *path_start = strchr(p, '/'); size_t hostport_len = path_start ? (size_t)(path_start - p) : strlen(p); *path = path_start ? strdup(path_start) : strdup("/"); if (!*path) return -1; // check for explicit port const char *colon = memmem(p, hostport_len, ":", 1); if (colon) { size_t hlen = (size_t)(colon - p); *host = strndup(p, hlen); size_t plen = hostport_len - hlen - 1; *port = strndup(colon + 1, plen); } else { *host = strndup(p, hostport_len); *port = strdup(*use_ssl ? "443" : "80"); } if (!*host || !*port) { free(*host); free(*port); free(*path); return -1; } return 0; } // Builds a GET request into buf static int build_get_request(buffer_t *buf, const char *host, const char *port, const char *path) { // HTTP/1.1 requires Host header; Connection: close for simplicity char tmp[512]; int n = snprintf(tmp, sizeof(tmp), "GET %s HTTP/1.1\r\n" "Host: %s:%s\r\n" "Connection: close\r\n" "\r\n", path, host, port); if (n < 0) return -1; return buffer_append(buf, tmp, (size_t)n); } // Performs a blocking HTTP GET and returns a QuickJS Blob of the body static JSValue js_fetch_picoparser(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1 || !JS_IsString(argv[0])) return JS_ThrowTypeError(ctx, "fetch: URL string required"); const char *url = JS_ToCString(ctx, argv[0]); if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL"); int use_ssl = 0; char *host = NULL, *port = NULL, *path = NULL; if (parse_url(url, &use_ssl, &host, &port, &path) < 0) { JS_FreeCString(ctx, url); return JS_ThrowTypeError(ctx, "fetch: URL parse error"); } // Prepare mbedTLS structures mbedtls_net_context net_ctx; mbedtls_ssl_context ssl; mbedtls_ssl_config conf; mbedtls_ctr_drbg_context drbg; mbedtls_entropy_context entropy; mbedtls_net_init(&net_ctx); mbedtls_ssl_init(&ssl); mbedtls_ssl_config_init(&conf); mbedtls_ctr_drbg_init(&drbg); mbedtls_entropy_init(&entropy); int ret; const char *pers = "js_fetch"; if (use_ssl) { if ((ret = mbedtls_ctr_drbg_seed(&drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers))) != 0) goto cleanup_tls; if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) goto cleanup_tls; mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &drbg); if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) goto cleanup_tls; if ((ret = mbedtls_ssl_set_hostname(&ssl, host)) != 0) goto cleanup_tls; } // Connect to host:port if ((ret = mbedtls_net_connect(&net_ctx, host, port, MBEDTLS_NET_PROTO_TCP)) != 0) goto cleanup_tls; if (use_ssl) { mbedtls_ssl_set_bio(&ssl, &net_ctx, mbedtls_net_send, mbedtls_net_recv, NULL); while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) goto cleanup_all; } } // Build and send GET request buffer_t req = {0}, resp = {0}; buffer_init(&req); buffer_init(&resp); if (build_get_request(&req, host, port, path) < 0) goto cleanup_all; // Write request size_t off = 0; while (off < req.size) { if (use_ssl) { ret = mbedtls_ssl_write(&ssl, (const unsigned char *)req.data + off, req.size - off); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) continue; } else { ret = mbedtls_net_send(&net_ctx, (unsigned char *)req.data + off, req.size - off); } if (ret <= 0) goto cleanup_all; off += (size_t)ret; } buffer_free(&req); // Read entire response into resp.data unsigned char buf[4096]; do { if (use_ssl) { ret = mbedtls_ssl_read(&ssl, buf, sizeof(buf)); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) continue; } else { ret = mbedtls_net_recv(&net_ctx, buf, sizeof(buf)); } if (ret > 0) { if (buffer_append(&resp, buf, (size_t)ret) < 0) goto cleanup_all; } } while (ret > 0); // Parse status-line + headers with picohttpparser struct phr_header hdrv[32]; size_t hdr_count = sizeof(hdrv)/sizeof(hdrv[0]); int minor, status; const char *reason = NULL; size_t reason_len = 0; int pret = phr_parse_response( resp.data, resp.size, &minor, &status, &reason, &reason_len, /* provide pointers, even if unused */ hdrv, &hdr_count, 0); if (pret < 0) goto cleanup_all; size_t header_len = (size_t)pret; char *body_ptr = resp.data + header_len; size_t body_len = resp.size - header_len; // Check for chunked encoding int is_chunked = 0; for (size_t i = 0; i < hdr_count; i++) { if ((hdrv[i].name_len == 17 && !strncasecmp(hdrv[i].name, "Transfer-Encoding", 17) && memmem(hdrv[i].value, hdrv[i].value_len, "chunked", 7))) { is_chunked = 1; break; } } // If chunked, decode in-place if (is_chunked) { struct phr_chunked_decoder cd = {0}; size_t blen = body_len; ssize_t rest = phr_decode_chunked(&cd, body_ptr, &blen); if (rest < 0) goto cleanup_all; // blen = decoded length; rest = trailer start offset (ignored) body_len = blen; } // Return a Blob wrapping the body buffer (no extra copy) JSValue blob = js_new_blob_stoned_copy(ctx, (uint8_t *)body_ptr, body_len); cleanup_all: buffer_free(&resp); mbedtls_ssl_close_notify(&ssl); cleanup_tls: mbedtls_net_free(&net_ctx); mbedtls_ssl_free(&ssl); mbedtls_ssl_config_free(&conf); mbedtls_ctr_drbg_free(&drbg); mbedtls_entropy_free(&entropy); JS_FreeCString(ctx, url); free(host); free(port); free(path); return blob; } // Export the function as “fetch2” (for example) static const JSCFunctionListEntry js_http_funcs[] = { JS_CFUNC_DEF("fetch", 2, js_fetch_picoparser), }; JSValue js_http_use(JSContext *js) { JSValue obj = JS_NewObject(js); JS_SetPropertyFunctionList(js, obj, js_http_funcs, sizeof(js_http_funcs)/sizeof(js_http_funcs[0])); return obj; }