net-utils.c (7142B)
1 #include <limits.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <sys/stat.h> 6 #include <unistd.h> 7 8 #include "emmanuel-marty/em_inflate.h" 9 #include "erkkah/naett.h" 10 #include "rxi/microtar.h" 11 #include "tidwall/json.h" 12 13 /* Forward declarations for helpers */ 14 static int mem_read(mtar_t *tar, void *data, unsigned size); 15 static int mem_seek(mtar_t *tar, unsigned pos); 16 static int mem_close(mtar_t *tar); 17 18 /* Membuffer structure used by the tar memory stream */ 19 typedef struct { 20 char *data; 21 size_t size; 22 size_t pos; 23 } membuffer_t; 24 25 /* Check if a string looks like a URL */ 26 int is_url(const char *str) { 27 return strncmp(str, "http://", 7) == 0 || strncmp(str, "https://", 8) == 0; 28 } 29 30 static int mkdir_recursive(const char *path) { 31 char tmp[PATH_MAX]; 32 char *p = NULL; 33 size_t len; 34 35 snprintf(tmp, sizeof(tmp), "%s", path); 36 len = strlen(tmp); 37 if (tmp[len - 1] == '/') { 38 tmp[len - 1] = '\0'; 39 } 40 41 for (p = tmp + 1; *p; p++) { 42 if (*p == '/') { 43 *p = '\0'; 44 mkdir(tmp, 0755); 45 *p = '/'; 46 } 47 } 48 return mkdir(tmp, 0755); 49 } 50 51 /* Static helper for downloading with retries */ 52 static char *download_url_with_retry(const char *url, size_t *out_size, int retries) { 53 static int naett_initialized = 0; 54 55 if (!naett_initialized) { 56 naettInit(NULL); 57 naett_initialized = 1; 58 } 59 60 naettReq *req = naettRequest(url, naettMethod("GET"), naettTimeout(30000)); 61 if (!req) { 62 fprintf(stderr, "Error: failed to create request\n"); 63 return NULL; 64 } 65 66 naettRes *res = naettMake(req); 67 if (!res) { 68 naettFree(req); 69 fprintf(stderr, "Error: failed to make request\n"); 70 return NULL; 71 } 72 73 while (!naettComplete(res)) { 74 usleep(10000); 75 } 76 77 int status = naettGetStatus(res); 78 const char *remaining = naettGetHeader(res, "X-RateLimit-Remaining"); 79 int body_size = 0; 80 const void *body = naettGetBody(res, &body_size); 81 82 if (status == 403 || status == 429 || status == 404) { 83 if (remaining && strcmp(remaining, "0") == 0) { 84 if (retries > 0) { 85 fprintf(stderr, "Rate limited, waiting 5 seconds before retry...\n"); 86 naettClose(res); 87 naettFree(req); 88 sleep(5); 89 return download_url_with_retry(url, out_size, retries - 1); 90 } 91 } 92 } 93 94 if (status != 200) { 95 fprintf(stderr, "Error: HTTP status %d for %s\n", status, url); 96 naettClose(res); 97 naettFree(req); 98 return NULL; 99 } 100 101 if (!body || body_size == 0) { 102 if (retries > 0) { 103 fprintf(stderr, "Empty response, waiting 5 seconds before retry...\n"); 104 naettClose(res); 105 naettFree(req); 106 sleep(5); 107 return download_url_with_retry(url, out_size, retries - 1); 108 } 109 fprintf(stderr, "Error: empty response body\n"); 110 naettClose(res); 111 naettFree(req); 112 return NULL; 113 } 114 115 char *data = malloc(body_size); 116 if (data) { 117 memcpy(data, body, body_size); 118 *out_size = body_size; 119 } 120 121 naettClose(res); 122 naettFree(req); 123 124 return data; 125 } 126 127 /* Public download URL function */ 128 char *download_url(const char *url, size_t *out_size) { 129 return download_url_with_retry(url, out_size, 3); 130 } 131 132 /* Memory tar callbacks */ 133 static int mem_read(mtar_t *tar, void *data, unsigned size) { 134 membuffer_t *buf = (membuffer_t *)tar->stream; 135 if (buf->pos + size > buf->size) { 136 return MTAR_EREADFAIL; 137 } 138 memcpy(data, buf->data + buf->pos, size); 139 buf->pos += size; 140 return MTAR_ESUCCESS; 141 } 142 143 static int mem_seek(mtar_t *tar, unsigned pos) { 144 membuffer_t *buf = (membuffer_t *)tar->stream; 145 if (pos > buf->size) { 146 return MTAR_ESEEKFAIL; 147 } 148 buf->pos = pos; 149 return MTAR_ESUCCESS; 150 } 151 152 static int mem_close(mtar_t *tar) { 153 (void)tar; 154 return MTAR_ESUCCESS; 155 } 156 157 /* Download and extract a tar.gz URL into a directory */ 158 int download_and_extract(const char *url, const char *dest_dir) { 159 size_t gzip_size; 160 char *gzip_data = download_url(url, &gzip_size); 161 if (!gzip_data) { 162 return -1; 163 } 164 165 if (gzip_size < 10 || (unsigned char)gzip_data[0] != 0x1f || (unsigned char)gzip_data[1] != 0x8b) { 166 free(gzip_data); 167 fprintf(stderr, "Error: downloaded data is not gzip format\n"); 168 return -1; 169 } 170 171 size_t max_tar_size = gzip_size * 256; 172 char *tar_data = malloc(max_tar_size); 173 if (!tar_data) { 174 free(gzip_data); 175 return -1; 176 } 177 178 size_t tar_size = em_inflate(gzip_data, gzip_size, (unsigned char *)tar_data, max_tar_size); 179 free(gzip_data); 180 181 if (tar_size == (size_t)-1) { 182 free(tar_data); 183 fprintf(stderr, "Error: decompression failed (invalid or corrupted gzip data)\n"); 184 return -1; 185 } 186 if (tar_size == 0) { 187 free(tar_data); 188 fprintf(stderr, "Error: decompressed to empty (likely wrong format or truncated data)\n"); 189 return -1; 190 } 191 192 membuffer_t membuf = {.data = tar_data, .size = tar_size, .pos = 0}; 193 194 mtar_t tar; 195 memset(&tar, 0, sizeof(tar)); 196 tar.read = mem_read; 197 tar.seek = mem_seek; 198 tar.close = mem_close; 199 tar.stream = &membuf; 200 201 char first_component[256] = {0}; 202 int first_component_found = 0; 203 204 while (1) { 205 mtar_header_t h; 206 int err = mtar_read_header(&tar, &h); 207 if (err == MTAR_ENULLRECORD) break; 208 if (err != MTAR_ESUCCESS) { 209 fprintf(stderr, "Error reading tar header: %s\n", mtar_strerror(err)); 210 break; 211 } 212 213 if (!first_component_found && (h.type == MTAR_TREG || h.type == MTAR_TDIR)) { 214 char *slash = strchr(h.name, '/'); 215 if (slash) { 216 size_t len = slash - h.name; 217 strncpy(first_component, h.name, len); 218 first_component[len] = '\0'; 219 first_component_found = 1; 220 } 221 } 222 223 char full_path[PATH_MAX]; 224 char *name_ptr = h.name; 225 226 if (first_component_found) { 227 size_t first_len = strlen(first_component); 228 if (strncmp(h.name, first_component, first_len) == 0 && h.name[first_len] == '/') { 229 name_ptr = h.name + first_len + 1; 230 } 231 } 232 233 if (strlen(name_ptr) == 0) { 234 mtar_next(&tar); 235 continue; 236 } 237 238 snprintf(full_path, sizeof(full_path), "%s/%s", dest_dir, name_ptr); 239 240 if (h.type == MTAR_TDIR) { 241 mkdir_recursive(full_path); 242 } else if (h.type == MTAR_TREG) { 243 char *last_slash = strrchr(full_path, '/'); 244 if (last_slash) { 245 *last_slash = '\0'; 246 mkdir_recursive(full_path); 247 *last_slash = '/'; 248 } 249 250 // Skip if file already exists 251 if (access(full_path, F_OK) == 0) { 252 mtar_next(&tar); 253 continue; 254 } 255 256 FILE *f = fopen(full_path, "wb"); 257 if (!f) { 258 fprintf(stderr, "Error: could not create file %s\n", full_path); 259 mtar_next(&tar); 260 continue; 261 } 262 263 char buf[8192]; 264 unsigned remaining = h.size; 265 while (remaining > 0) { 266 unsigned to_read = remaining > sizeof(buf) ? sizeof(buf) : remaining; 267 int read_err = mtar_read_data(&tar, buf, to_read); 268 if (read_err != MTAR_ESUCCESS) { 269 fprintf(stderr, "Error reading tar data\n"); 270 break; 271 } 272 fwrite(buf, 1, to_read, f); 273 remaining -= to_read; 274 } 275 fclose(f); 276 } 277 278 mtar_next(&tar); 279 } 280 281 free(tar_data); 282 return 0; 283 }