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);