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 */