dep

Package manager for embedded C libraries
git clone git://git.finwo.net/app/dep
Log | Files | Refs | README | LICENSE

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 }