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 mir/inline-method 423 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 const char *esm_get_home_dir(void) { 105#ifdef _WIN32 106 const char *home = getenv("USERPROFILE"); 107 if (home) return home; 108 109 const char *drive = getenv("HOMEDRIVE"); 110 const char *path = getenv("HOMEPATH"); 111 if (drive && path) { 112 static char win_home[MAX_PATH]; 113 snprintf(win_home, sizeof(win_home), "%s%s", drive, path); 114 return win_home; 115 } 116#else 117 const char *home = getenv("HOME"); 118 if (home) return home; 119#endif 120 return NULL; 121} 122 123static void esm_url_fetch_body_cb(tlsuv_http_req_t *http_req, char *body, ssize_t len) { 124 esm_url_fetch_t *ctx = (esm_url_fetch_t *)http_req->data; 125 126 if (len == UV_EOF) { 127 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 128 return; 129 } 130 131 if (len < 0) { 132 ctx->failed = 1; 133 ctx->error_msg = strdup(uv_strerror((int)len)); 134 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 135 return; 136 } 137 138 if (ctx->size + (size_t)len > ctx->capacity) { 139 size_t new_cap = ctx->capacity * 2; 140 while (new_cap < ctx->size + (size_t)len) new_cap *= 2; 141 char *new_data = realloc(ctx->data, new_cap); 142 if (!new_data) { 143 ctx->failed = 1; 144 ctx->error_msg = strdup("Out of memory"); 145 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 146 return; 147 } 148 ctx->data = new_data; 149 ctx->capacity = new_cap; 150 } 151 152 memcpy(ctx->data + ctx->size, body, (size_t)len); 153 ctx->size += (size_t)len; 154} 155 156static void esm_url_fetch_resp_cb(tlsuv_http_resp_t *resp, void *data) { 157 (void)data; 158 esm_url_fetch_t *ctx = (esm_url_fetch_t *)resp->req->data; 159 160 if (resp->code < 0) { 161 ctx->failed = 1; 162 char err_buf[256]; 163 snprintf(err_buf, sizeof(err_buf), "%s (code: %d)", uv_strerror(resp->code), resp->code); 164 ctx->error_msg = strdup(err_buf); 165 tlsuv_http_close(&ctx->http_client, esm_url_fetch_close_cb); 166 return; 167 } 168 169 ctx->status_code = resp->code; 170 resp->body_cb = esm_url_fetch_body_cb; 171} 172 173static char *esm_get_cache_path(const char *url) { 174 const char *home = esm_get_home_dir(); 175 if (!home) home = "."; 176 177 uint64_t hash = hash_key(url, strlen(url)); 178 179 size_t len = strlen(home) + 48; 180 char *cache_path = malloc(len); 181 if (!cache_path) return NULL; 182 183#ifdef _WIN32 184 snprintf(cache_path, len, "%s\\.ant\\remote\\%016llx", home, (unsigned long long)hash); 185#else 186 snprintf(cache_path, len, "%s/.ant/remote/%016llx", home, (unsigned long long)hash); 187#endif 188 return cache_path; 189} 190 191static char *esm_read_cache(const char *cache_path, size_t *out_len) { 192 FILE *fp = fopen(cache_path, "rb"); 193 if (!fp) return NULL; 194 195 fseek(fp, 0, SEEK_END); 196 long size = ftell(fp); 197 fseek(fp, 0, SEEK_SET); 198 199 char *content = malloc(size + 1); 200 if (!content) { 201 fclose(fp); 202 return NULL; 203 } 204 205 fread(content, 1, size, fp); 206 fclose(fp); 207 content[size] = '\0'; 208 209 if (out_len) *out_len = (size_t)size; 210 return content; 211} 212 213static void esm_mkdir_recursive(char *path) { 214 for (char *p = path + 1; *p; p++) { 215#ifdef _WIN32 216 if (p == path + 2 && path[1] == ':') continue; 217#endif 218 if (is_path_sep(*p)) { 219 *p = '\0'; 220 mkdir_p(path); 221 *p = PATH_SEP; 222 } 223 } 224 mkdir_p(path); 225} 226 227static void esm_write_cache(const char *cache_path, const char *url, const char *content, size_t len) { 228 char *dir = strdup(cache_path); 229 if (!dir) return; 230 231 for (char *p = dir + strlen(dir) - 1; p > dir; p--) { 232 if (is_path_sep(*p)) { *p = '\0'; break; } 233 } 234 235 esm_mkdir_recursive(dir); 236 237 FILE *fp = fopen(cache_path, "wb"); 238 if (!fp) { free(dir); return; } 239 fwrite(content, 1, len, fp); 240 fclose(fp); 241 242 size_t meta_len = strlen(dir) + 16; 243 char *meta_path = malloc(meta_len); 244 if (meta_path) { 245 snprintf(meta_path, meta_len, "%s/metadata.bin", dir); 246 FILE *mfp = fopen(meta_path, "ab"); 247 if (mfp) { 248 uint64_t hash = hash_key(url, strlen(url)); 249 uint16_t url_len = (uint16_t)strlen(url); 250 fwrite(&url_len, sizeof(url_len), 1, mfp); 251 fwrite(url, 1, url_len, mfp); 252 fwrite(&hash, sizeof(hash), 1, mfp); 253 fclose(mfp); 254 } 255 free(meta_path); 256 } 257 258 free(dir); 259} 260 261char *esm_fetch_url(const char *url, size_t *out_len, char **out_error) { 262 char *cache_path = esm_get_cache_path(url); 263 if (cache_path) { 264 char *cached = esm_read_cache(cache_path, out_len); 265 if (cached) { free(cache_path); return cached; } 266 } 267 268 uv_loop_t *loop = uv_default_loop(); 269 esm_url_fetch_t ctx = {0}; 270 271 ctx.capacity = 16384; 272 ctx.data = malloc(ctx.capacity); 273 if (!ctx.data) { 274 if (out_error) *out_error = strdup("Out of memory"); 275 return NULL; 276 } 277 278 const char *scheme_end = strstr(url, "://"); 279 if (!scheme_end) { 280 free(ctx.data); 281 if (out_error) *out_error = strdup("Invalid URL: no scheme"); 282 return NULL; 283 } 284 285 const char *host_start = scheme_end + 3; 286 const char *path_start = strchr(host_start, '/'); 287 const char *at_in_host = NULL; 288 289 for (const char *p = host_start; p < (path_start ? path_start : host_start + strlen(host_start)); p++) { 290 if (*p == '@') at_in_host = p; 291 } 292 if (at_in_host) host_start = at_in_host + 1; 293 294 size_t scheme_len = scheme_end - url; 295 size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start); 296 const char *path = path_start ? path_start : "/"; 297 298 char *host_url = calloc(1, scheme_len + 3 + host_len + 1); 299 snprintf(host_url, scheme_len + 3 + host_len + 1, "%.*s://%.*s", (int)scheme_len, url, (int)host_len, host_start); 300 int rc = tlsuv_http_init(loop, &ctx.http_client, host_url); 301 free(host_url); 302 303 if (rc != 0) { 304 free(ctx.data); 305 if (out_error) *out_error = strdup("Failed to initialize HTTP client"); 306 return NULL; 307 } 308 309 ctx.http_client.data = &ctx; 310 ctx.http_req = tlsuv_http_req(&ctx.http_client, "GET", path, esm_url_fetch_resp_cb, &ctx); 311 312 if (!ctx.http_req) { 313 free(ctx.data); 314 tlsuv_http_close(&ctx.http_client, NULL); 315 if (out_error) *out_error = strdup("Failed to create HTTP request"); 316 return NULL; 317 } 318 319 ctx.http_req->data = &ctx; 320 while (!ctx.completed) uv_run(loop, UV_RUN_ONCE); 321 322 if (ctx.failed || ctx.status_code < 200 || ctx.status_code >= 400) { 323 if (out_error) { 324 if (ctx.error_msg) { 325 *out_error = ctx.error_msg; 326 ctx.error_msg = NULL; 327 } else { 328 char err_buf[64]; 329 snprintf(err_buf, sizeof(err_buf), "HTTP error: %d", ctx.status_code); 330 *out_error = strdup(err_buf); 331 } 332 } 333 free(ctx.data); 334 if (ctx.error_msg) free(ctx.error_msg); 335 if (cache_path) free(cache_path); 336 return NULL; 337 } 338 339 ctx.data[ctx.size] = '\0'; 340 if (out_len) *out_len = ctx.size; 341 if (ctx.error_msg) free(ctx.error_msg); 342 343 if (cache_path) { 344 esm_write_cache(cache_path, url, ctx.data, ctx.size); 345 free(cache_path); 346 } 347 348 return ctx.data; 349} 350 351char *esm_resolve_url(const char *specifier, const char *base_url) { 352 if (esm_is_url(specifier)) { 353 return strdup(specifier); 354 } 355 356 if (specifier[0] == '/') { 357 const char *scheme_end = strstr(base_url, "://"); 358 if (!scheme_end) return NULL; 359 360 const char *host_start = scheme_end + 3; 361 const char *path_start = strchr(host_start, '/'); 362 363 const char *at_in_host = NULL; 364 for (const char *p = host_start; p < (path_start ? path_start : host_start + strlen(host_start)); p++) { 365 if (*p == '@') at_in_host = p; 366 } 367 if (at_in_host) host_start = at_in_host + 1; 368 369 size_t scheme_len = scheme_end - base_url; 370 size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start); 371 372 size_t len = scheme_len + 3 + host_len + strlen(specifier) + 1; 373 char *result = malloc(len); 374 if (!result) return NULL; 375 376 snprintf( 377 result, len, "%.*s://%.*s%s", 378 (int)scheme_len, base_url, 379 (int)host_len, host_start, 380 specifier 381 ); 382 383 return result; 384 } 385 386 if (specifier[0] == '.' && (specifier[1] == '/' || (specifier[1] == '.' && specifier[2] == '/'))) { 387 char *base_copy = strdup(base_url); 388 if (!base_copy) return NULL; 389 390 char *last_slash = strrchr(base_copy, '/'); 391 char *scheme_end = strstr(base_copy, "://"); 392 if (scheme_end && last_slash > scheme_end + 2) { 393 *last_slash = '\0'; 394 } 395 396 const char *spec = specifier; 397 while (strncmp(spec, "../", 3) == 0) { 398 spec += 3; 399 char *prev_slash = strrchr(base_copy, '/'); 400 if (prev_slash && prev_slash > scheme_end + 2) { 401 *prev_slash = '\0'; 402 } 403 } 404 405 if (strncmp(spec, "./", 2) == 0) spec += 2; 406 size_t len = strlen(base_copy) + strlen(spec) + 2; 407 char *result = malloc(len); 408 if (!result) { free(base_copy); return NULL; } 409 410 snprintf(result, len, "%s/%s", base_copy, spec); 411 free(base_copy); 412 413 return result; 414 } 415 416 return strdup(specifier); 417} 418 419char *esm_resolve(const char *specifier, const char *base_path, char FILE_RESOLVER) { 420 if (esm_is_data_url(specifier)) return strdup(specifier); 421 if (esm_is_url(specifier) || esm_is_url(base_path)) return esm_resolve_url(specifier, base_path); 422 return file_resolver(specifier, base_path); 423}