MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 404 lines 11 kB view raw
1#include "utils.h" 2#include "base64.h" 3#include "escape.h" 4#include "esm/remote.h" 5 6#include <stdio.h> 7#include <stdlib.h> 8#include <string.h> 9#include <stdint.h> 10#include <uv.h> 11#include <tlsuv/tlsuv.h> 12#include <tlsuv/http.h> 13 14#ifdef _WIN32 15#include <direct.h> 16#define WIN32_LEAN_AND_MEAN 17#include <windows.h> 18#define mkdir_p(path) _mkdir(path) 19#define PATH_SEP '\\' 20#else 21#include <sys/stat.h> 22#include <sys/types.h> 23#define mkdir_p(path) mkdir(path, 0755) 24#define PATH_SEP '/' 25#endif 26 27typedef struct { 28 char *data; 29 size_t size; 30 size_t capacity; 31 int status_code; 32 int completed; 33 int failed; 34 char *error_msg; 35 tlsuv_http_t http_client; 36 tlsuv_http_req_t *http_req; 37} esm_url_fetch_t; 38 39bool esm_is_url(const char *spec) { 40 return (strncmp(spec, "http://", 7) == 0 || strncmp(spec, "https://", 8) == 0); 41} 42 43bool esm_is_data_url(const char *spec) { 44 return strncmp(spec, "data:", 5) == 0; 45} 46 47static char *esm_percent_decode(const char *src, size_t src_len, size_t *out_len) { 48 char *out = malloc(src_len + 1); 49 if (!out) return NULL; 50 51 size_t j = 0; 52 for (size_t i = 0; i < src_len; i++) { 53 if (src[i] == '%' && i + 2 < src_len && 54 is_xdigit(src[i + 1]) && is_xdigit(src[i + 2])) { 55 out[j++] = (char)((unhex(src[i + 1]) << 4) | unhex(src[i + 2])); 56 i += 2; 57 } else out[j++] = src[i]; 58 } 59 60 out[j] = '\0'; 61 if (out_len) *out_len = j; 62 return out; 63} 64 65char *esm_parse_data_url(const char *url, size_t *out_len) { 66 if (!esm_is_data_url(url)) return NULL; 67 68 const char *comma = strchr(url + 5, ','); 69 if (!comma) return NULL; 70 71 const char *header = url + 5; 72 size_t header_len = (size_t)(comma - header); 73 74 const char *body = comma + 1; 75 size_t body_len = strlen(body); 76 77 bool is_base64 = (header_len >= 7 && strncmp(comma - 7, ";base64", 7) == 0); 78 if (is_base64) { 79 uint8_t *decoded = ant_base64_decode(body, body_len, out_len); 80 if (!decoded) return NULL; 81 82 char *result = malloc(*out_len + 1); 83 if (!result) { free(decoded); return NULL; } 84 85 memcpy(result, decoded, *out_len); 86 result[*out_len] = '\0'; 87 free(decoded); 88 89 return result; 90 } 91 92 return esm_percent_decode(body, body_len, out_len); 93} 94 95static int is_path_sep(char c) { 96 return c == '/' || c == '\\'; 97} 98 99static void esm_url_fetch_close_cb(tlsuv_http_t *client) { 100 esm_url_fetch_t *ctx = (esm_url_fetch_t *)client->data; 101 ctx->completed = 1; 102} 103 104static void esm_url_fetch_body_cb(tlsuv_http_req_t *http_req, char *body, ssize_t len) { 105 esm_url_fetch_t *ctx = (esm_url_fetch_t *)http_req->data; 106 107 if (len == UV_EOF) { 108 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 109 return; 110 } 111 112 if (len < 0) { 113 ctx->failed = 1; 114 ctx->error_msg = strdup(uv_strerror((int)len)); 115 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 116 return; 117 } 118 119 if (ctx->size + (size_t)len > ctx->capacity) { 120 size_t new_cap = ctx->capacity * 2; 121 while (new_cap < ctx->size + (size_t)len) new_cap *= 2; 122 char *new_data = realloc(ctx->data, new_cap); 123 if (!new_data) { 124 ctx->failed = 1; 125 ctx->error_msg = strdup("Out of memory"); 126 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 127 return; 128 } 129 ctx->data = new_data; 130 ctx->capacity = new_cap; 131 } 132 133 memcpy(ctx->data + ctx->size, body, (size_t)len); 134 ctx->size += (size_t)len; 135} 136 137static void esm_url_fetch_resp_cb(tlsuv_http_resp_t *resp, void *data) { 138 (void)data; 139 esm_url_fetch_t *ctx = (esm_url_fetch_t *)resp->req->data; 140 141 if (resp->code < 0) { 142 ctx->failed = 1; 143 char err_buf[256]; 144 snprintf(err_buf, sizeof(err_buf), "%s (code: %d)", uv_strerror(resp->code), resp->code); 145 ctx->error_msg = strdup(err_buf); 146 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 147 return; 148 } 149 150 ctx->status_code = resp->code; 151 resp->body_cb = esm_url_fetch_body_cb; 152} 153 154static char *esm_get_cache_path(const char *url) { 155 uint64_t hash = hash_key(url, strlen(url)); 156 157 char suffix[64]; 158 snprintf(suffix, sizeof(suffix), "remote/%016llx", (unsigned long long)hash); 159 160 size_t len = 4096; 161 char *cache_path = malloc(len); 162 if (!cache_path) return NULL; 163 164 if (ant_xdg_cache_path(cache_path, len, suffix) != 0) { 165 free(cache_path); 166 return NULL; 167 } 168 169 return cache_path; 170} 171 172static char *esm_read_cache(const char *cache_path, size_t *out_len) { 173 FILE *fp = fopen(cache_path, "rb"); 174 if (!fp) return NULL; 175 176 fseek(fp, 0, SEEK_END); 177 long size = ftell(fp); 178 fseek(fp, 0, SEEK_SET); 179 180 char *content = malloc(size + 1); 181 if (!content) { 182 fclose(fp); 183 return NULL; 184 } 185 186 fread(content, 1, size, fp); 187 fclose(fp); 188 content[size] = '\0'; 189 190 if (out_len) *out_len = (size_t)size; 191 return content; 192} 193 194static void esm_mkdir_recursive(char *path) { 195 for (char *p = path + 1; *p; p++) { 196#ifdef _WIN32 197 if (p == path + 2 && path[1] == ':') continue; 198#endif 199 if (is_path_sep(*p)) { 200 *p = '\0'; 201 mkdir_p(path); 202 *p = PATH_SEP; 203 } 204 } 205 mkdir_p(path); 206} 207 208static void esm_write_cache(const char *cache_path, const char *url, const char *content, size_t len) { 209 char *dir = strdup(cache_path); 210 if (!dir) return; 211 212 for (char *p = dir + strlen(dir) - 1; p > dir; p--) { 213 if (is_path_sep(*p)) { *p = '\0'; break; } 214 } 215 216 esm_mkdir_recursive(dir); 217 218 FILE *fp = fopen(cache_path, "wb"); 219 if (!fp) { free(dir); return; } 220 fwrite(content, 1, len, fp); 221 fclose(fp); 222 223 size_t meta_len = strlen(dir) + 16; 224 char *meta_path = malloc(meta_len); 225 if (meta_path) { 226 snprintf(meta_path, meta_len, "%s/metadata.bin", dir); 227 FILE *mfp = fopen(meta_path, "ab"); 228 if (mfp) { 229 uint64_t hash = hash_key(url, strlen(url)); 230 uint16_t url_len = (uint16_t)strlen(url); 231 fwrite(&url_len, sizeof(url_len), 1, mfp); 232 fwrite(url, 1, url_len, mfp); 233 fwrite(&hash, sizeof(hash), 1, mfp); 234 fclose(mfp); 235 } 236 free(meta_path); 237 } 238 239 free(dir); 240} 241 242char *esm_fetch_url(const char *url, size_t *out_len, char **out_error) { 243 char *cache_path = esm_get_cache_path(url); 244 if (cache_path) { 245 char *cached = esm_read_cache(cache_path, out_len); 246 if (cached) { free(cache_path); return cached; } 247 } 248 249 uv_loop_t *loop = uv_default_loop(); 250 esm_url_fetch_t ctx = {0}; 251 252 ctx.capacity = 16384; 253 ctx.data = malloc(ctx.capacity); 254 if (!ctx.data) { 255 if (out_error) *out_error = strdup("Out of memory"); 256 return NULL; 257 } 258 259 const char *scheme_end = strstr(url, "://"); 260 if (!scheme_end) { 261 free(ctx.data); 262 if (out_error) *out_error = strdup("Invalid URL: no scheme"); 263 return NULL; 264 } 265 266 const char *host_start = scheme_end + 3; 267 const char *path_start = strchr(host_start, '/'); 268 const char *at_in_host = NULL; 269 270 for (const char *p = host_start; p < (path_start ? path_start : host_start + strlen(host_start)); p++) { 271 if (*p == '@') at_in_host = p; 272 } 273 if (at_in_host) host_start = at_in_host + 1; 274 275 size_t scheme_len = scheme_end - url; 276 size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start); 277 const char *path = path_start ? path_start : "/"; 278 279 char *host_url = calloc(1, scheme_len + 3 + host_len + 1); 280 snprintf(host_url, scheme_len + 3 + host_len + 1, "%.*s://%.*s", (int)scheme_len, url, (int)host_len, host_start); 281 int rc = tlsuv_http_init(loop, &ctx.http_client, host_url); 282 free(host_url); 283 284 if (rc != 0) { 285 free(ctx.data); 286 if (out_error) *out_error = strdup("Failed to initialize HTTP client"); 287 return NULL; 288 } 289 290 ctx.http_client.data = &ctx; 291 ctx.http_req = tlsuv_http_req(&ctx.http_client, "GET", path, esm_url_fetch_resp_cb, &ctx); 292 293 if (!ctx.http_req) { 294 free(ctx.data); 295 tlsuv_http_close(&ctx.http_client, NULL); 296 if (out_error) *out_error = strdup("Failed to create HTTP request"); 297 return NULL; 298 } 299 300 ctx.http_req->data = &ctx; 301 while (!ctx.completed) uv_run(loop, UV_RUN_ONCE); 302 303 if (ctx.failed || ctx.status_code < 200 || ctx.status_code >= 400) { 304 if (out_error) { 305 if (ctx.error_msg) { 306 *out_error = ctx.error_msg; 307 ctx.error_msg = NULL; 308 } else { 309 char err_buf[64]; 310 snprintf(err_buf, sizeof(err_buf), "HTTP error: %d", ctx.status_code); 311 *out_error = strdup(err_buf); 312 } 313 } 314 free(ctx.data); 315 if (ctx.error_msg) free(ctx.error_msg); 316 if (cache_path) free(cache_path); 317 return NULL; 318 } 319 320 ctx.data[ctx.size] = '\0'; 321 if (out_len) *out_len = ctx.size; 322 if (ctx.error_msg) free(ctx.error_msg); 323 324 if (cache_path) { 325 esm_write_cache(cache_path, url, ctx.data, ctx.size); 326 free(cache_path); 327 } 328 329 return ctx.data; 330} 331 332char *esm_resolve_url(const char *specifier, const char *base_url) { 333 if (esm_is_url(specifier)) { 334 return strdup(specifier); 335 } 336 337 if (specifier[0] == '/') { 338 const char *scheme_end = strstr(base_url, "://"); 339 if (!scheme_end) return NULL; 340 341 const char *host_start = scheme_end + 3; 342 const char *path_start = strchr(host_start, '/'); 343 344 const char *at_in_host = NULL; 345 for (const char *p = host_start; p < (path_start ? path_start : host_start + strlen(host_start)); p++) { 346 if (*p == '@') at_in_host = p; 347 } 348 if (at_in_host) host_start = at_in_host + 1; 349 350 size_t scheme_len = scheme_end - base_url; 351 size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start); 352 353 size_t len = scheme_len + 3 + host_len + strlen(specifier) + 1; 354 char *result = malloc(len); 355 if (!result) return NULL; 356 357 snprintf( 358 result, len, "%.*s://%.*s%s", 359 (int)scheme_len, base_url, 360 (int)host_len, host_start, 361 specifier 362 ); 363 364 return result; 365 } 366 367 if (specifier[0] == '.' && (specifier[1] == '/' || (specifier[1] == '.' && specifier[2] == '/'))) { 368 char *base_copy = strdup(base_url); 369 if (!base_copy) return NULL; 370 371 char *last_slash = strrchr(base_copy, '/'); 372 char *scheme_end = strstr(base_copy, "://"); 373 if (scheme_end && last_slash > scheme_end + 2) { 374 *last_slash = '\0'; 375 } 376 377 const char *spec = specifier; 378 while (strncmp(spec, "../", 3) == 0) { 379 spec += 3; 380 char *prev_slash = strrchr(base_copy, '/'); 381 if (prev_slash && prev_slash > scheme_end + 2) { 382 *prev_slash = '\0'; 383 } 384 } 385 386 if (strncmp(spec, "./", 2) == 0) spec += 2; 387 size_t len = strlen(base_copy) + strlen(spec) + 2; 388 char *result = malloc(len); 389 if (!result) { free(base_copy); return NULL; } 390 391 snprintf(result, len, "%s/%s", base_copy, spec); 392 free(base_copy); 393 394 return result; 395 } 396 397 return strdup(specifier); 398} 399 400char *esm_resolve(const char *specifier, const char *base_path, char FILE_RESOLVER) { 401 if (esm_is_data_url(specifier)) return strdup(specifier); 402 if (esm_is_url(specifier) || esm_is_url(base_path)) return esm_resolve_url(specifier, base_path); 403 return file_resolver(specifier, base_path); 404}