snac2

Fork of https://codeberg.org/grunfink/snac2
git clone https://git.inz.fi/snac2
Log | Files | Refs | README | LICENSE

xs_url.h (7840B)


      1 /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */
      2 
      3 #ifndef _XS_URL_H
      4 
      5 #define _XS_URL_H
      6 
      7 xs_str *xs_url_dec(const char *str);
      8 xs_str *xs_url_enc(const char *str);
      9 xs_dict *xs_url_vars(const char *str);
     10 xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header);
     11 
     12 #ifdef XS_IMPLEMENTATION
     13 
     14 char *xs_url_dec_in(char *str, int qs)
     15 {
     16     char *w = str;
     17     char *r;
     18 
     19     for (r = str; *r != '\0'; r++) {
     20         switch (*r) {
     21         case '%': {
     22             unsigned hex;
     23             if (!r[1] || !r[2])
     24                 return NULL;
     25             if (sscanf(r + 1, "%2x", &hex) != 1)
     26                 return NULL;
     27             *w++ = hex;
     28             r += 2;
     29             break;
     30         }
     31 
     32         case '+':
     33             if (qs) {
     34                 *w++ = ' ';
     35                 break;
     36             }
     37             /* fall-through */
     38         default:
     39             *w++ = *r;
     40         }
     41     }
     42 
     43     *w++ = '\0';
     44     return str;
     45 }
     46 
     47 xs_str *xs_url_dec(const char *str)
     48 /* decodes an URL */
     49 {
     50     xs_str *s = xs_str_new(NULL);
     51 
     52     while (*str) {
     53         if (!xs_is_string(str))
     54             break;
     55 
     56         if (*str == '%') {
     57             unsigned int i;
     58 
     59             if (sscanf(str + 1, "%02x", &i) == 1) {
     60                 unsigned char uc = i;
     61 
     62                 if (!xs_is_string((char *)&uc))
     63                     break;
     64 
     65                 s = xs_append_m(s, (char *)&uc, 1);
     66                 str += 2;
     67             }
     68         }
     69         else
     70         if (*str == '+')
     71             s = xs_append_m(s, " ", 1);
     72         else
     73             s = xs_append_m(s, str, 1);
     74 
     75         str++;
     76     }
     77 
     78     return s;
     79 }
     80 
     81 
     82 xs_str *xs_url_enc(const char *str)
     83 /* URL-encodes a string (RFC 3986) */
     84 {
     85     xs_str *s = xs_str_new(NULL);
     86 
     87     while (*str) {
     88         if (isalnum(*str) || strchr("-._~", *str)) {
     89             s = xs_append_m(s, str, 1);
     90         }
     91         else {
     92             char tmp[8];
     93             snprintf(tmp, sizeof(tmp), "%%%02X", (unsigned char)*str);
     94             s = xs_append_m(s, tmp, 3);
     95         }
     96 
     97         str++;
     98     }
     99 
    100     return s;
    101 }
    102 
    103 
    104 xs_dict *xs_url_vars(const char *str)
    105 /* parse url variables */
    106 {
    107     xs_dict *vars;
    108 
    109     vars = xs_dict_new();
    110 
    111     if (xs_is_string(str)) {
    112         xs *dup = xs_dup(str);
    113         char *k;
    114         char *saveptr;
    115         for (k = strtok_r(dup, "&", &saveptr);
    116              k;
    117              k = strtok_r(NULL, "&", &saveptr)) {
    118             char *v = strchr(k, '=');
    119             if (!v)
    120                 continue;
    121             *v++ = '\0';
    122             k = xs_url_dec_in(k, 1);
    123             v = xs_url_dec_in(v, 1);
    124             if (!xs_is_string(k) || !xs_is_string(v))
    125                 continue;
    126 
    127             const char *pv  = xs_dict_get(vars, k);
    128             if (!xs_is_null(pv)) {
    129                 /* there is a previous value: convert to a list and append */
    130                 xs *vlist = NULL;
    131                 if (xs_type(pv) == XSTYPE_LIST)
    132                     vlist = xs_dup(pv);
    133                 else {
    134                     vlist = xs_list_new();
    135                     vlist = xs_list_append(vlist, pv);
    136                 }
    137 
    138                 vlist = xs_list_append(vlist, v);
    139                 vars  = xs_dict_set(vars, k, vlist);
    140             }
    141             else {
    142                 /* ends with []? force to always be a list */
    143                 if (xs_endswith(k, "[]")) {
    144                     xs *vlist = xs_list_new();
    145                     vlist = xs_list_append(vlist, v);
    146                     vars = xs_dict_append(vars, k, vlist);
    147                 }
    148                 else
    149                     vars = xs_dict_append(vars, k, v);
    150             }
    151         }
    152     }
    153 
    154     return vars;
    155 }
    156 
    157 
    158 xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header)
    159 /* parses a multipart/form-data payload */
    160 {
    161     xs *boundary = NULL;
    162     int offset = 0;
    163     int bsz;
    164     char *p;
    165 
    166     /* build the boundary string */
    167     {
    168         xs *l1 = xs_split(header, "=");
    169 
    170         if (xs_list_len(l1) != 2)
    171             return NULL;
    172 
    173         xs *t_boundary = xs_dup(xs_list_get(l1, 1));
    174 
    175         /* Tokodon sends the boundary header with double quotes surrounded */
    176         if (xs_between("\"", t_boundary, "\"") != 0)
    177             t_boundary = xs_strip_chars_i(t_boundary, "\"");
    178 
    179         boundary = xs_fmt("--%s", t_boundary);
    180     }
    181 
    182     bsz = strlen(boundary);
    183 
    184     xs_dict *p_vars = xs_dict_new();
    185 
    186     /* iterate searching the boundaries */
    187     while ((p = xs_memmem(payload + offset, p_size - offset, boundary, bsz)) != NULL) {
    188         xs *vn = NULL;
    189         xs *fn = NULL;
    190         xs *ct = NULL;
    191         char *q;
    192         int po, ps;
    193 
    194         /* final boundary? */
    195         p += bsz;
    196 
    197         if ((p - payload) + 2 > p_size || (p[0] == '-' && p[1] == '-'))
    198             break;
    199 
    200         /* skip the \r\n */
    201         p += 2;
    202 
    203         /* Tokodon sends also a Content-Type headers,
    204            let's use it to determine the file type */
    205         do {
    206             xs *s1 = NULL;
    207             xs *l1 = NULL;
    208             if (p[0] == '\r' && p[1] == '\n')
    209                 break;
    210             q = memchr(p, '\r', p_size - (p - payload));
    211 
    212             /* unexpected formatting, fail immediately */
    213             if (q == NULL)
    214                 return p_vars;
    215 
    216             s1 = xs_realloc(NULL, q - p + 1);
    217             memcpy(s1, p, q - p);
    218             s1[q - p] = '\0';
    219 
    220             if (xs_startswith(s1, "Content-Disposition") || xs_startswith(s1, "content-disposition")) {
    221                 /* split by " like a primitive man */
    222                 l1 = xs_split(s1, "\"");
    223 
    224                 /* get the variable name */
    225                 vn = xs_dup(xs_list_get(l1, 1));
    226 
    227                 /* is it an attached file? */
    228                 if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) {
    229                     /* get the file name */
    230                     fn = xs_dup(xs_list_get(l1, 3));
    231                 }
    232             }
    233             else
    234             if (xs_startswith(s1, "Content-Type") || xs_startswith(s1, "content-type")) {
    235                 l1 = xs_split(s1, ":");
    236 
    237                 if (xs_list_len(l1) >= 2) {
    238                     ct = xs_lstrip_chars_i(xs_dup(xs_list_get(l1, 1)), " ");
    239                 }
    240             }
    241 
    242             p += (q - p);
    243             p += 2; // Skip /r/n
    244         } while (1);
    245 
    246         /* find the start of the part content */
    247         if ((p = xs_memmem(p, p_size - (p - payload), "\r\n", 2)) == NULL)
    248             break;
    249 
    250         p += 2; // Skip empty line
    251 
    252         /* find the next boundary */
    253         if ((q = xs_memmem(p, p_size - (p - payload), boundary, bsz)) == NULL)
    254             break;
    255 
    256         po = p - payload;
    257         ps = q - p - 2;     /* - 2 because the final \r\n */
    258 
    259         /* is it a filename? */
    260         if (fn != NULL) {
    261             /* p_var value is a list */
    262             /* if filename has no extension and content-type is image, attach extension to the filename */
    263             if (strchr(fn, '.') == NULL && ct && xs_startswith(ct, "image/")) {
    264                 char *ext = strchr(ct, '/');
    265                 ext++;
    266                 fn = xs_str_cat(xs_str_new(""), fn, ".", ext);
    267             }
    268 
    269             xs *l1 = xs_list_new();
    270             xs *vpo = xs_number_new(po);
    271             xs *vps = xs_number_new(ps);
    272 
    273             l1 = xs_list_append(l1, fn);
    274             l1 = xs_list_append(l1, vpo);
    275             l1 = xs_list_append(l1, vps);
    276 
    277             if (xs_is_string(vn))
    278                 p_vars = xs_dict_append(p_vars, vn, l1);
    279         }
    280         else {
    281             /* regular variable; just copy */
    282             xs *vc = xs_realloc(NULL, ps + 1);
    283             memcpy(vc, payload + po, ps);
    284             vc[ps] = '\0';
    285 
    286             if (xs_is_string(vn) && xs_is_string(vc))
    287                 p_vars = xs_dict_append(p_vars, vn, vc);
    288         }
    289 
    290         /* move on */
    291         offset = q - payload;
    292     }
    293 
    294     return p_vars;
    295 }
    296 
    297 
    298 #endif /* XS_IMPLEMENTATION */
    299 
    300 #endif /* XS_URL_H */