fix blob and http

This commit is contained in:
2025-05-31 17:57:17 -05:00
parent a442cf5a4d
commit 06108df3d4
3 changed files with 222 additions and 21 deletions

View File

@@ -56,6 +56,7 @@ int blob_write_fit(blob *b, int64_t fit, int length);
int blob_write_kim(blob *b, int64_t value);
int blob_write_pad(blob *b, int block_size);
int blob_write_text(blob *b, const char *text);
int blob_write_text_raw(blob *b, const char *text);
int blob_write_bytes(blob *b, void *data, size_t length);
@@ -66,6 +67,7 @@ int blob_read_dec64(const blob *b, size_t from, double *out_value);
int blob_read_fit(const blob *b, size_t from, int length, int64_t *out_value);
int blob_read_kim(const blob *b, size_t from, int64_t *out_value, size_t *bits_read);
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read);
int blob_read_text_raw(const blob *b, size_t from, char **out_text, size_t *bits_read);
int blob_pad_check(const blob *b, size_t from, int block_size);
// Utility functions
@@ -388,6 +390,13 @@ int blob_write_text(blob *b, const char *text) {
return 0;
}
int blob_write_text_raw(blob *b, const char *text) {
if (!b || !text || b->is_stone) return -1;
size_t len = strlen(text);
return blob_write_bytes(b, (void *)text, len);
}
int blob_write_bytes(blob *b, void *data, size_t length) {
if (!b || !data || b->is_stone) return -1;
@@ -538,6 +547,27 @@ int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_rea
return 0;
}
int blob_read_text_raw(const blob *b, size_t from, char **out_text, size_t *bits_read) {
if (!b || !b->is_stone || !out_text || !bits_read) return -1;
if (from >= b->bit_length) return -1;
// Check that from is byte-aligned
if (from % 8 != 0) return -1;
size_t from_byte = from / 8;
size_t length_bytes = (b->bit_length - from) / 8;
char *str = malloc(length_bytes + 1);
if (!str) return -1;
memcpy(str, b->data + from_byte, length_bytes);
str[length_bytes] = '\0';
*out_text = str;
*bits_read = length_bytes * 8;
return 0;
}
int blob_pad_check(const blob *b, size_t from, int block_size) {
if (!b || !b->is_stone) return 0;
if (block_size <= 0) return 0;

View File

@@ -289,6 +289,30 @@ static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
return JS_UNDEFINED;
}
// blob.write_text_raw(text)
static JSValue js_blob_write_text_raw(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "write_text_raw(text) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "write_text_raw: not called on a blob");
}
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return JS_EXCEPTION;
int result = blob_write_text_raw(bd, str);
JS_FreeCString(ctx, str);
if (result < 0) {
return JS_ThrowTypeError(ctx, "write_text_raw: cannot write to stone blob or OOM");
}
return JS_UNDEFINED;
}
// blob.read_logical(from)
static JSValue js_blob_read_logical(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
@@ -433,9 +457,6 @@ static JSValue js_blob_read_kim(JSContext *ctx, JSValueConst this_val,
// blob.read_text(from)
static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "read_text(from) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "read_text: not called on a blob");
@@ -464,6 +485,39 @@ static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
return obj;
}
// blob.read_text_raw(from)
static JSValue js_blob_read_text_raw(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "read_text_raw: not called on a blob");
}
if (!bd->is_stone) {
return JS_ThrowTypeError(ctx, "read_text_raw: blob must be stone");
}
int64_t from = 0;
if (argc >= 1) {
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
}
char *text;
size_t bits_read;
if (blob_read_text_raw(bd, from, &text, &bits_read) < 0) {
return JS_ThrowRangeError(ctx, "read_text_raw: out of range or not byte-aligned");
}
JSValue result = JS_NewString(ctx, text);
free(text);
// Return object with text and total bits read
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "text", result);
JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read));
return obj;
}
// blob.pad?(from, block_size)
static JSValue js_blob_pad_q(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
@@ -557,7 +611,7 @@ static const JSCFunctionListEntry js_blob_funcs[] = {
JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit),
JS_CFUNC_DEF("write_kim", 1, js_blob_write_kim),
JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad),
JS_CFUNC_DEF("write_text", 1, js_blob_write_text),
JS_CFUNC_DEF("write_text", 1, js_blob_write_text_raw),
// Read methods
JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical),
@@ -565,7 +619,7 @@ static const JSCFunctionListEntry js_blob_funcs[] = {
JS_CFUNC_DEF("read_dec64", 1, js_blob_read_dec64),
JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit),
JS_CFUNC_DEF("read_kim", 1, js_blob_read_kim),
JS_CFUNC_DEF("read_text", 1, js_blob_read_text),
JS_CFUNC_DEF("read_text", 1, js_blob_read_text_raw),
JS_CFUNC_DEF("pad?", 2, js_blob_pad_q),
// Other methods

View File

@@ -254,16 +254,79 @@ exit:
return ret;
}
// Extract body from HTTP response
static char *extract_body(const char *response, size_t response_len, size_t *body_len) {
const char *body_start = strstr(response, "\r\n\r\n");
if (!body_start) {
return NULL;
}
body_start += 4;
*body_len = response_len - (body_start - response);
return (char *)body_start;
/* ---------------------------------------------------------------------
Very small, non-streaming de-chunker.
Removes the hex size lines and keeps the actual data.
Returns 0 on success, -1 on format errors.
-------------------------------------------------------------------*/
static int decode_chunked(buffer_t *out,
const char *src, size_t src_len)
{
printf("[CHUNK] Starting decode_chunked, input len: %zu\n", src_len);
int chunk_count = 0;
while (src_len) {
/* read hex size line */
size_t n = 0;
while (n < src_len && src[n] != '\r') { n++; }
if (n == src_len || n + 1 >= src_len || src[n+1] != '\n') {
printf("[CHUNK] Error: incomplete chunk size line\n");
return -1;
}
char size_buf[32];
if (n >= sizeof(size_buf)) {
printf("[CHUNK] Error: chunk size line too long (%zu)\n", n);
return -1;
}
memcpy(size_buf, src, n); size_buf[n] = 0;
char *endptr;
long chunk_len = strtol(size_buf, &endptr, 16);
if (endptr == size_buf || chunk_len < 0) {
printf("[CHUNK] Error: invalid chunk size '%s'\n", size_buf);
return -1;
}
printf("[CHUNK] Chunk %d: size=%ld (0x%lx)\n", chunk_count++, chunk_len, chunk_len);
if (chunk_len == 0) {
printf("[CHUNK] Found terminating chunk, done\n");
return 0; /* done */
}
/* skip "<hex>\r\n" */
src += n + 2;
src_len -= n + 2;
if ((size_t)chunk_len > src_len) {
printf("[CHUNK] Error: chunk size %ld exceeds remaining data %zu\n", chunk_len, src_len);
return -1;
}
if (buffer_append(out, src, chunk_len) < 0) return -1;
/* skip chunk data */
src += chunk_len;
src_len -= chunk_len;
/* skip trailing \r\n after chunk data */
if (src_len < 2 || src[0] != '\r' || src[1] != '\n') {
printf("[CHUNK] Error: missing CRLF after chunk data (remaining=%zu)\n", src_len);
if (src_len > 0) {
printf("[CHUNK] Next bytes: ");
for (size_t i = 0; i < 10 && i < src_len; i++) {
printf("\\x%02x", (unsigned char)src[i]);
}
printf("\n");
}
return -1;
}
src += 2;
src_len -= 2;
}
printf("[CHUNK] Finished without terminating chunk\n");
return 0;
}
// Parse HTTP headers
@@ -790,22 +853,76 @@ static JSValue js_fetch(JSContext *ctx, JSValueConst this_val, int argc, JSValue
}
if (ret == 0 && response_buf.data) {
// Extract body
size_t body_len;
char *body = extract_body(response_buf.data, response_buf.size, &body_len);
if (body) {
// Return body as ArrayBuffer
result = js_new_blob_stoned_copy(ctx, (uint8_t *)body, body_len);
} else {
// Split headers / body
char *body = strstr(response_buf.data, "\r\n\r\n");
if (!body) {
result = JS_ThrowInternalError(ctx, "Failed to parse HTTP response");
goto cleanup;
}
size_t header_len = body + 4 - response_buf.data;
size_t body_len = response_buf.size - header_len;
body += 4;
// Look for "Transfer-Encoding: chunked"
int is_chunked = (strstr(response_buf.data, "Transfer-Encoding: chunked") ||
strstr(response_buf.data, "transfer-encoding: chunked"));
printf("[HTTP] Total response size: %zu bytes\n", response_buf.size);
printf("[HTTP] Header length: %zu bytes\n", header_len);
printf("[HTTP] Body length before decoding: %zu bytes\n", body_len);
printf("[HTTP] Is chunked: %s\n", is_chunked ? "yes" : "no");
// Print first 100 chars of body before decoding
printf("[HTTP] First 100 chars of raw body: ");
for (size_t i = 0; i < 100 && i < body_len; i++) {
if (body[i] >= 32 && body[i] <= 126) {
printf("%c", body[i]);
} else {
printf("\\x%02x", (unsigned char)body[i]);
}
}
printf("\n");
buffer_t clean_body;
buffer_init(&clean_body);
if (is_chunked) {
if (decode_chunked(&clean_body, body, body_len) < 0) {
buffer_free(&clean_body);
result = JS_ThrowInternalError(ctx, "Failed to decode chunked response");
goto cleanup;
}
body = clean_body.data;
body_len = clean_body.size;
} else {
// not chunked → just copy
buffer_append(&clean_body, body, body_len);
body = clean_body.data;
body_len = clean_body.size;
}
printf("[HTTP] Body length after decoding: %zu bytes\n", body_len);
printf("[HTTP] First 100 chars of decoded body: ");
for (size_t i = 0; i < 100 && i < body_len; i++) {
if (body[i] >= 32 && body[i] <= 126) {
printf("%c", body[i]);
} else {
printf("\\x%02x", (unsigned char)body[i]);
}
}
printf("\n");
// build blob from *body / body_len* exactly as before
result = js_new_blob_stoned_copy(ctx, (uint8_t *)body, body_len);
buffer_free(&clean_body);
} else {
char error_buf[256];
mbedtls_strerror(ret, error_buf, sizeof(error_buf));
result = JS_ThrowInternalError(ctx, "Request failed: %s", error_buf);
}
cleanup:
// Cleanup
JS_FreeCString(ctx, url);
free(host);