1#pragma once
2
3#include <curl/curl.h>
4#include <pw.h>
5
6/*
7 * Note: Curl should be built with IDN support for "international" URLs to work.
8 * But this is not tested yet, and this seems to be locale-dependent.
9 * PetWay works with UTF-8 only.
10 *
11 * International URL checklist:
12 * 1. Content-Disposition
13 * 2. Location
14 */
15
16/****************************************************************
17 * Wrapper type for CURLcode errors CURLE_*
18 */
19
20extern uint16_t PwTypeId_CurlStatus;
21extern uint16_t PwInterfaceId_CurlStatus;
22
23extern uint16_t PW_ERROR_CURL; // PetWay status code for Curl errors
24extern uint16_t PW_ERROR_REQUEST_ALREADY_ADDED;
25
26typedef enum {
27 PwCurlError_Easy, // CURLE_*
28 PwCurlError_Multi, // CURLM_*
29 PwCurlError_Share, // CURLSHE_*
30 PwCurlError_Url, // CURLUE_*
31 PwCurlError_Header // CURLHE_*
32} PwCurlErrorType;
33
34typedef struct {
35 PwCurlErrorType error_type;
36 union {
37 CURLcode curl_error;
38 CURLMcode curlm_error;
39 CURLSHcode curlsh_error;
40 CURLUcode curlu_error;
41 CURLHcode curlh_error;
42 };
43} PwCurlStatusData;
44
45PW_METHOD_BEGIN(CurlStatus, curl_error)
46 bool (*PwFunc_CurlStatus_curl_error)(PwMethod_CurlStatus_curl_error* mthis, PwValuePtr self, PwCurlStatusData* curl_error)
47PW_METHOD_END(CurlStatus, curl_error)
48/*
49 * Return Curl-specific error type and code from Status value.
50 */
51
52#define PW_CURL_STATUS_INTERFACE_METHODS \
53 X(curl_error)
54
55PW_INTERFACE_BEGIN(CurlStatus)
56#define X(name, ...) PwMethod_CurlStatus_##name name;
57 PW_CURL_STATUS_INTERFACE_METHODS
58#undef X
59PW_INTERFACE_END(CurlStatus)
60
61/****************************************************************
62 * Wrapper type for Curl multi handle
63 */
64
65extern uint16_t PwTypeId_CurlSession;
66extern uint16_t PwInterfaceId_CurlSession;
67
68PW_METHOD_BEGIN(CurlSession, add_request)
69 bool (*PwFunc_CurlSession_add_request)(PwMethod_CurlSession_add_request* mthis, PwValuePtr self, PwValuePtr request)
70PW_METHOD_END(CurlSession, add_request)
71/*
72 * Add request to multi handle.
73 * Requests are cloned and stored in the internal list so the caller does not need to keep request
74 * after calling this method.
75 * However, it's worth to keep it for reuse in subsequent transfers.
76 * See https://everything.curl.dev/transfers/easyhandle.html#reuse
77 */
78
79PW_METHOD_BEGIN(CurlSession, remove_request)
80 bool (*PwFunc_CurlSession_remove_request)(PwMethod_CurlSession_remove_request* mthis, PwValuePtr self, PwValuePtr request)
81PW_METHOD_END(CurlSession, remove_request)
82/*
83 * Remove request from multi handle.
84 * Requests are automatically removed when transfer completes so the caller normally does not have to call this method.
85 */
86
87PW_METHOD_BEGIN(CurlSession, perform)
88 bool (*PwFunc_CurlSession_perform)(PwMethod_CurlSession_perform* mthis, PwValuePtr self, PwValuePtr timeout, int* running_transfers)
89PW_METHOD_END(CurlSession, perform)
90/*
91 * Runner.
92 */
93
94#define PW_CURL_SESSION_INTERFACE_METHODS \
95 X(add_request, 1) \
96 X(remove_request, 1) \
97 X(perform)
98
99PW_INTERFACE_BEGIN(CurlSession)
100#define X(name, ...) PwMethod_CurlSession_##name name;
101 PW_CURL_SESSION_INTERFACE_METHODS
102#undef X
103PW_INTERFACE_END(CurlSession)
104
105
106/****************************************************************
107 * Curl Request: wrapper type for Curl easy handle
108 */
109
110extern uint16_t PwTypeId_CurlRequest;
111extern uint16_t PwInterfaceId_CurlRequest;
112
113PW_METHOD_BEGIN(CurlRequest, setopt)
114 bool (*PwFunc_CurlRequest_setopt)(PwMethod_CurlRequest_setopt* mthis, PwValuePtr self, CURLoption option, PwValuePtr value)
115PW_METHOD_END(CurlRequest, setopt)
116/*
117 * Call curl_easy_setopt.
118 */
119
120PW_METHOD_BEGIN(CurlRequest, set_headers)
121 bool (*PwFunc_CurlRequest_set_headers)(PwMethod_CurlRequest_set_headers* mthis, PwValuePtr self, PwValuePtr headers)
122PW_METHOD_END(CurlRequest, set_headers)
123/*
124 * Wrapper for curl_easy_setopt to set one (String) or many (Array or Map) request headers.
125 *
126 * Unlike curl_easy_setopt that overrides previous settings, this method updates internal list of headers
127 * so it's okay to call it multiple times.
128 *
129 * If headers is a Map, null values are used to suppress header.
130 *
131 * To reset the internal list use PwNull or nullptr for headers.
132 *
133 * Note that Curl recommends using CURLOPT_COOKIE, CURLOPT_USERAGENT, and CURLOPT_REFERER
134 * for those commonly replaced headers.
135 */
136
137PW_METHOD_BEGIN(CurlRequest, reset)
138 bool (*PwFunc_CurlRequest_reset)(PwMethod_CurlRequest_reset* mthis, PwValuePtr self)
139PW_METHOD_END(CurlRequest, reset)
140/*
141 * Call curl_easy_reset.
142 */
143
144PW_METHOD_BEGIN(CurlRequest, get_url)
145 bool (*PwFunc_CurlRequest_get_url)(PwMethod_CurlRequest_get_url* mthis, PwValuePtr self, PwValuePtr result)
146PW_METHOD_END(CurlRequest, get_url)
147/*
148 * Get initial URL.
149 */
150
151PW_METHOD_BEGIN(CurlRequest, set_url)
152 bool (*PwFunc_CurlRequest_set_url)(PwMethod_CurlRequest_set_url* mthis, PwValuePtr self, PwValuePtr url)
153PW_METHOD_END(CurlRequest, set_url)
154/*
155 * Store URL in the private data structure for `get_url` and call `curl_easy_setopt` with `CURLOPT_URL`.
156 */
157
158PW_METHOD_BEGIN(CurlRequest, getinfo)
159 bool (*PwFunc_CurlRequest_getinfo)(PwMethod_CurlRequest_getinfo* mthis, PwValuePtr self,
160 CURLINFO info, PwValuePtr result)
161PW_METHOD_END(CurlRequest, getinfo)
162/*
163 * Call curl_easy_getinfo.
164 */
165
166PW_METHOD_BEGIN(CurlRequest, attachment_filename)
167 bool (*PwFunc_CurlRequest_attachment_filename)(PwMethod_CurlRequest_attachment_filename* mthis, PwValuePtr self, PwValuePtr result)
168PW_METHOD_END(CurlRequest, attachment_filename)
169/*
170 * Get attachment filename from the following sources:
171 * - Content-Disposition
172 * - Location
173 * - effective URL
174 *
175 * If no filename found and URL ends with slash, return empty string.
176 */
177
178PW_METHOD_BEGIN(CurlRequest, content_disposition)
179 bool (*PwFunc_CurlRequest_content_disposition)(PwMethod_CurlRequest_content_disposition* mthis, PwValuePtr self,
180 PwValuePtr disposition_type, PwValuePtr disposition_params)
181PW_METHOD_END(CurlRequest, content_disposition)
182/*
183 * Get content disposition type and params from the response.
184 * Result pointers to unnecessary parts can be nullptr.
185 */
186
187PW_METHOD_BEGIN(CurlRequest, content_type)
188 bool (*PwFunc_CurlRequest_content_type)(PwMethod_CurlRequest_content_type* mthis, PwValuePtr self,
189 PwValuePtr media_type, PwValuePtr media_subtype, PwValuePtr media_type_params)
190PW_METHOD_END(CurlRequest, content_type)
191/*
192 * Get media type, subtype, and params from the response.
193 * Result pointers to unnecessary parts can be nullptr.
194 */
195
196PW_METHOD_BEGIN(CurlRequest, response_header)
197 bool (*PwFunc_CurlRequest_response_header)(PwMethod_CurlRequest_response_header* mthis, PwValuePtr self,
198 PwValuePtr name, PwValuePtr result)
199PW_METHOD_END(CurlRequest, response_header)
200/*
201 * Get response header.
202 * Normally the result is a string.
203 * The result can be a list if the response contains multiple instance of header with the same name.
204 * If header is missing from the response the result is Null.
205 */
206
207PW_METHOD_BEGIN(CurlRequest, on_content_start)
208 bool (*PwFunc_CurlRequest_on_content_start)(PwMethod_CurlRequest_on_content_start* mthis, PwValuePtr self,
209 long status, curl_off_t length, PwValuePtr final_url)
210PW_METHOD_END(CurlRequest, on_content_start)
211/*
212 * Called before first invocation of on_content.
213 * `length` can be -1 if unknown.
214 */
215
216PW_METHOD_BEGIN(CurlRequest, on_content)
217 bool (*PwFunc_CurlRequest_on_content)(PwMethod_CurlRequest_on_content* mthis, PwValuePtr self, void* data, size_t size)
218PW_METHOD_END(CurlRequest, on_content)
219/*
220 * Called when data received.
221 * Size is always greater than 0.
222 * If content size is zero, this method is not called.
223 */
224
225PW_METHOD_BEGIN(CurlRequest, on_complete)
226 bool (*PwFunc_CurlRequest_on_complete)(PwMethod_CurlRequest_on_complete* mthis, PwValuePtr self, PwValuePtr error)
227PW_METHOD_END(CurlRequest, on_complete)
228/*
229 * Called when request complete.
230 * If `error` is Null, the request completed normally.
231 * The request is removed from Curl multi handle before calling this method.
232 */
233
234#define PW_CURL_REQUEST_INTERFACE_METHODS \
235 X(setopt, 1) \
236 X(set_headers, 1) \
237 X(reset, 1) \
238 X(get_url, 1) \
239 X(set_url, 1) \
240 X(getinfo, 1) \
241 X(attachment_filename, 1) \
242 X(content_disposition, 1) \
243 X(content_type, 1) \
244 X(response_header, 1) \
245 X(on_content_start, 1) \
246 X(on_content, 1) \
247 X(on_complete)
248 // XXX on_read_data is not implemented yet
249
250PW_INTERFACE_BEGIN(CurlRequest)
251#define X(name, ...) PwMethod_CurlRequest_##name name;
252 PW_CURL_REQUEST_INTERFACE_METHODS
253#undef X
254PW_INTERFACE_END(CurlRequest)
255
256
257/****************************************************************
258 * Shorthand functions
259 */
260
261[[nodiscard]] static inline bool pw_curl_add_request(PwValuePtr session, PwValuePtr request)
262{
263 return pw_call(CurlSession, add_request, session, request);
264}
265
266[[nodiscard]] static inline bool pw_curl_remove_request(PwValuePtr session, PwValuePtr request)
267{
268 return pw_call(CurlSession, remove_request, session, request);
269}
270
271[[nodiscard]] static inline bool pw_curl_perform(PwValuePtr session, PwValuePtr timeout, int* running_transfers)
272{
273 return pw_call(CurlSession, perform, session, timeout, running_transfers);
274}
275
276[[nodiscard]] static inline bool pw_curl_request_setopt(PwValuePtr request, CURLoption tag, PwValuePtr value)
277{
278 return pw_call(CurlRequest, setopt, request, tag, value);
279}
280
281[[nodiscard]] static inline bool pw_curl_request_set_headers(PwValuePtr request, PwValuePtr headers)
282{
283 return pw_call(CurlRequest, set_headers, request, headers);
284}
285
286[[nodiscard]] static inline bool pw_curl_request_getinfo(PwValuePtr request, CURLINFO info, PwValuePtr result)
287{
288 return pw_call(CurlRequest, getinfo, request, info, result);
289}
290
291[[nodiscard]] static inline bool pw_curl_request_attachment_filename(PwValuePtr request, PwValuePtr result)
292{
293 return pw_call(CurlRequest, attachment_filename, request, result);
294}
295
296[[nodiscard]] static inline bool pw_curl_request_content_disposition(
297 PwValuePtr request, PwValuePtr disposition_type, PwValuePtr disposition_params)
298{
299 return pw_call(CurlRequest, content_disposition, request, disposition_type, disposition_params);
300}
301
302[[nodiscard]] static inline bool pw_curl_request_content_type(
303 PwValuePtr request, PwValuePtr media_type, PwValuePtr media_subtype, PwValuePtr media_type_params)
304{
305 return pw_call(CurlRequest, content_type, request, media_type, media_subtype, media_type_params);
306}
307
308[[nodiscard]] static inline bool pw_curl_request_response_header(PwValuePtr request, PwValuePtr name, PwValuePtr result)
309{
310 return pw_call(CurlRequest, response_header, request, name, result);
311}
312
313[[nodiscard]] static inline bool pw_curl_status_curl_error(PwValuePtr status, PwCurlStatusData* error)
314{
315 return pw_call(CurlStatus, curl_error, status, error);
316}
317
318
319/****************************************************************
320 * Utils
321 */
322
323[[nodiscard]] bool pw_parse_content_type(char* content_type,
324 PwValuePtr media_type, PwValuePtr media_subtype, PwValuePtr media_type_params);
325/*
326 * Parse value of Content-Type header
327 * https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.1
328 */
329
330[[nodiscard]] bool pw_parse_content_disposition(char* content_disposition,
331 PwValuePtr disposition_type, PwValuePtr disposition_params);
332/*
333 * Parse value of Content-Disposition header
334 */
335
336[[nodiscard]] bool pw_urljoin_cstr(char* base_url, char* other_url, PwValuePtr result);
337[[nodiscard]] bool pw_urljoin(PwValuePtr base_url, PwValuePtr other_url, PwValuePtr result);