2.86 xs_curl.h
  1/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */
  2
  3#ifndef _XS_CURL_H
  4
  5#define _XS_CURL_H
  6
  7xs_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
 12int 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
 16const char *xs_curl_strerr(int errnum);
 17
 18#ifdef XS_IMPLEMENTATION
 19
 20#include <curl/curl.h>
 21
 22static 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
 51struct _payload_data {
 52    char *data;
 53    int size;
 54    int offset;
 55};
 56
 57static size_t _data_callback(void *buffer, size_t size,
 58                          size_t nitems, struct _payload_data *pd)
 59{
 60    size_t 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
 74static size_t _post_callback(char *buffer, size_t size,
 75                          size_t nitems, struct _payload_data *pd)
 76{
 77    /* size of data left */
 78    size_t sz = pd->size - pd->offset;
 79
 80    /* if it's still bigger than the provided space, trim */
 81    if (sz > (size_t) (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
 93xs_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, (curl_read_callback) _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, (curl_read_callback) _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, (curl_read_callback) _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
204int 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, (curl_read_callback) _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
247const 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 */