xs_curl.h (6663B)
1 /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ 2 3 #ifndef _XS_CURL_H 4 5 #define _XS_CURL_H 6 7 xs_dict *xs_http_request(const char *method, const char *url, 8 const xs_dict *headers, 9 const xs_str *body, int b_size, int *status, 10 xs_str **payload, int *p_size, int timeout); 11 12 int xs_smtp_request(const char *url, const char *user, const char *pass, 13 const char *from, const char *to, const xs_str *body, 14 int use_ssl); 15 16 const char *xs_curl_strerr(int errnum); 17 18 #ifdef XS_IMPLEMENTATION 19 20 #include <curl/curl.h> 21 22 static size_t _header_callback(char *buffer, size_t size, 23 size_t nitems, xs_dict **userdata) 24 { 25 xs_dict *headers = *userdata; 26 xs *l; 27 28 /* get the line */ 29 l = xs_str_new(NULL); 30 l = xs_append_m(l, buffer, size * nitems); 31 l = xs_strip_i(l); 32 33 /* only the HTTP/x 200 line and the last one doesn't have ': ' */ 34 if (xs_str_in(l, ": ") != -1) { 35 xs *knv = xs_split_n(l, ": ", 1); 36 37 xs_tolower_i((xs_str *)xs_list_get(knv, 0)); 38 39 headers = xs_dict_set(headers, xs_list_get(knv, 0), xs_list_get(knv, 1)); 40 } 41 else 42 if (xs_startswith(l, "HTTP/")) 43 headers = xs_dict_set(headers, "_proto", l); 44 45 *userdata = headers; 46 47 return nitems * size; 48 } 49 50 51 struct _payload_data { 52 char *data; 53 int size; 54 int offset; 55 }; 56 57 static int _data_callback(void *buffer, size_t size, 58 size_t nitems, struct _payload_data *pd) 59 { 60 int sz = size * nitems; 61 62 /* open space */ 63 pd->size += sz; 64 pd->data = xs_realloc(pd->data, _xs_blk_size(pd->size + 1)); 65 66 /* copy data */ 67 memcpy(pd->data + pd->offset, buffer, sz); 68 pd->offset += sz; 69 70 return sz; 71 } 72 73 74 static int _post_callback(char *buffer, size_t size, 75 size_t nitems, struct _payload_data *pd) 76 { 77 /* size of data left */ 78 int sz = pd->size - pd->offset; 79 80 /* if it's still bigger than the provided space, trim */ 81 if (sz > (int) (size * nitems)) 82 sz = size * nitems; 83 84 memcpy(buffer, pd->data + pd->offset, sz); 85 86 /* skip sent data */ 87 pd->offset += sz; 88 89 return sz; 90 } 91 92 93 xs_dict *xs_http_request(const char *method, const char *url, 94 const xs_dict *headers, 95 const xs_str *body, int b_size, int *status, 96 xs_str **payload, int *p_size, int timeout) 97 /* does an HTTP request */ 98 { 99 xs_dict *response; 100 CURL *curl; 101 struct curl_slist *list = NULL; 102 const xs_str *k; 103 const xs_val *v; 104 long lstatus = 0; 105 struct _payload_data pd; 106 107 response = xs_dict_new(); 108 109 curl = curl_easy_init(); 110 111 curl_easy_setopt(curl, CURLOPT_URL, url); 112 113 if (timeout <= 0) 114 timeout = 8; 115 116 curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long) timeout); 117 118 #ifdef FORCE_HTTP_1_1 119 /* force HTTP/1.1 */ 120 curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 121 #endif 122 123 /* obey redirections */ 124 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); 125 126 /* store response headers here */ 127 curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response); 128 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _header_callback); 129 130 struct _payload_data ipd = { NULL, 0, 0 }; 131 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ipd); 132 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _data_callback); 133 134 if (strcmp(method, "POST") == 0 || strcmp(method, "PUT") == 0) { 135 CURLoption curl_method = method[1] == 'O' ? CURLOPT_POST : CURLOPT_UPLOAD; 136 curl_easy_setopt(curl, curl_method, 1L); 137 138 if (body != NULL) { 139 if (b_size <= 0) 140 b_size = xs_size(body); 141 142 /* add the content-length header */ 143 curl_easy_setopt(curl, curl_method == CURLOPT_POST ? CURLOPT_POSTFIELDSIZE : CURLOPT_INFILESIZE, b_size); 144 145 pd.data = (char *)body; 146 pd.size = b_size; 147 pd.offset = 0; 148 149 curl_easy_setopt(curl, CURLOPT_READDATA, &pd); 150 curl_easy_setopt(curl, CURLOPT_READFUNCTION, _post_callback); 151 } 152 } 153 154 /* fill the request headers */ 155 xs_dict_foreach(headers, k, v) { 156 xs *h = xs_fmt("%s: %s", k, v); 157 158 list = curl_slist_append(list, h); 159 } 160 161 /* disable server support for 100-continue */ 162 list = curl_slist_append(list, "Expect:"); 163 164 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); 165 166 /* do it */ 167 CURLcode cc = curl_easy_perform(curl); 168 169 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &lstatus); 170 171 curl_easy_cleanup(curl); 172 173 curl_slist_free_all(list); 174 175 if (status != NULL) { 176 if (lstatus == 0) { 177 /* set the timeout error to a fake HTTP status, or propagate as is */ 178 if (cc == CURLE_OPERATION_TIMEDOUT) 179 lstatus = 599; 180 else 181 lstatus = -cc; 182 } 183 184 *status = (int) lstatus; 185 } 186 187 if (p_size != NULL) 188 *p_size = ipd.size; 189 190 if (payload != NULL) { 191 *payload = ipd.data; 192 193 /* add an asciiz just in case (but not touching p_size) */ 194 if (ipd.data != NULL) 195 ipd.data[ipd.size] = '\0'; 196 } 197 else 198 xs_free(ipd.data); 199 200 return response; 201 } 202 203 204 int xs_smtp_request(const char *url, const char *user, const char *pass, 205 const char *from, const char *to, const xs_str *body, 206 int use_ssl) 207 { 208 CURL *curl; 209 CURLcode res = CURLE_OK; 210 struct curl_slist *rcpt = NULL; 211 struct _payload_data pd = { 212 .data = (char *)body, 213 .size = strlen(body), 214 .offset = 0 215 }; 216 217 curl = curl_easy_init(); 218 219 curl_easy_setopt(curl, CURLOPT_URL, url); 220 if (user && pass) { 221 /* allow authless connections, to, e.g. localhost */ 222 curl_easy_setopt(curl, CURLOPT_USERNAME, user); 223 curl_easy_setopt(curl, CURLOPT_PASSWORD, pass); 224 } 225 226 if (use_ssl) 227 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL); 228 229 curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from); 230 231 rcpt = curl_slist_append(rcpt, to); 232 curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, rcpt); 233 234 curl_easy_setopt(curl, CURLOPT_READDATA, &pd); 235 curl_easy_setopt(curl, CURLOPT_READFUNCTION, _post_callback); 236 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); 237 238 res = curl_easy_perform(curl); 239 240 curl_easy_cleanup(curl); 241 curl_slist_free_all(rcpt); 242 243 return (int)res; 244 } 245 246 247 const char *xs_curl_strerr(int errnum) 248 { 249 CURLcode cc = errnum < 0 ? -errnum : errnum; 250 251 return curl_easy_strerror(cc); 252 } 253 254 255 #endif /* XS_IMPLEMENTATION */ 256 257 #endif /* _XS_CURL_H */