2.81
xs_curl.h
1/* copyright (c) 2022 - 2025 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 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
74static 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
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, _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
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, _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 */