lmtcurl.c /size: 18 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6# include "lmtoptional.h"
7
8typedef void* curl_instance ;
9typedef int   curl_return_code ;
10typedef int   curl_error_code ;
11
12typedef enum curl_option_type {
13    curl_ignore   = 0,
14    curl_integer  = 1,
15    curl_string   = 2,
16    curl_function = 3, /* ignored */
17    curl_offset   = 4, /* ignored */
18} curl_option_type;
19
20/*tex At the \LUA\ end we can have a mapping of useful ones, */
21
22static const int curl_options[] = {
23    curl_ignore,    /*   0 */
24    curl_string,    /*   1 file | writedata */
25    curl_string,    /*   2 url */
26    curl_integer,   /*   3 port */
27    curl_string,    /*   4 proxy */
28    curl_string,    /*   5 userpwd */
29    curl_string,    /*   6 proxyuserpwd */
30    curl_string,    /*   7 range */
31    curl_ignore,    /*   8 */
32    curl_string,    /*   9 infile | readdata */
33    curl_string,    /*  10 errorbuffer */
34    curl_function,  /*  11 writefunction */
35    curl_function,  /*  12 readfunction */
36    curl_integer,   /*  13 timeout */
37    curl_integer,   /*  14 infilesize */
38    curl_string,    /*  15 postfields */
39    curl_string,    /*  16 referer */
40    curl_string,    /*  17 ftpport */
41    curl_string,    /*  18 useragent */
42    curl_integer,   /*  19 low_speed_limit */
43    curl_integer,   /*  20 low_speed_time */
44    curl_integer,   /*  21 resume_from */
45    curl_string,    /*  22 cookie */
46    curl_string,    /*  23 httpheader | rtspheader */
47    curl_string,    /*  24 httppost */
48    curl_string,    /*  25 sslcert */
49    curl_string,    /*  26 keypasswd */
50    curl_integer,   /*  27 crlf */
51    curl_string,    /*  28 quote */
52    curl_string,    /*  29 writeheader | headerdata */
53    curl_ignore,    /*  30 */
54    curl_string,    /*  31 cookiefile */
55    curl_integer,   /*  32 sslversion */
56    curl_integer,   /*  33 timecondition */
57    curl_integer,   /*  34 timevalue */
58    curl_ignore,    /*  35 */
59    curl_string,    /*  36 customrequest */
60    curl_string,    /*  37 stderr */
61    curl_ignore,    /*  38 */
62    curl_string,    /*  39 postquote */
63    curl_string,    /*  40 writeinfo */
64    curl_integer,   /*  41 verbose */
65    curl_integer,   /*  42 header */
66    curl_integer,   /*  43 noprogress */
67    curl_integer,   /*  44 nobody */
68    curl_integer,   /*  45 failonerror */
69    curl_integer,   /*  46 upload */
70    curl_integer,   /*  47 post */
71    curl_integer,   /*  48 dirlistonly */
72    curl_ignore,    /*  49 */
73    curl_integer,   /*  50 append */
74    curl_integer,   /*  51 netrc */
75    curl_integer,   /*  52 followlocation */
76    curl_integer,   /*  53 transfertext */
77    curl_integer,   /*  54 put */
78    curl_ignore,    /*  55 */
79    curl_function,  /*  56 progressfunction */
80    curl_string,    /*  57 xferinfodata | progressdata */
81    curl_integer,   /*  58 autoreferer */
82    curl_integer,   /*  59 proxyport */
83    curl_integer,   /*  60 postfieldsize */
84    curl_integer,   /*  61 httpproxytunnel */
85    curl_string,    /*  62 interface */
86    curl_string,    /*  63 krblevel */
87    curl_integer,   /*  64 ssl_verifypeer */
88    curl_string,    /*  65 cainfo */
89    curl_ignore,    /*  66 */
90    curl_ignore,    /*  67 */
91    curl_integer,   /*  68 maxredirs */
92    curl_integer,   /*  69 filetime */
93    curl_string,    /*  70 telnetoptions */
94    curl_integer,   /*  71 maxconnects */
95    curl_integer,   /*  72 closepolicy */
96    curl_ignore,    /*  73 */
97    curl_integer,   /*  74 fresh_connect */
98    curl_integer,   /*  75 forbid_reuse */
99    curl_string,    /*  76 random_file */
100    curl_string,    /*  77 egdsocket */
101    curl_integer,   /*  78 connecttimeout */
102    curl_function,  /*  79 headerfunction */
103    curl_integer,   /*  80 httpget */
104    curl_integer,   /*  81 ssl_verifyhost */
105    curl_string,    /*  82 cookiejar */
106    curl_string,    /*  83 ssl_cipher_list */
107    curl_integer,   /*  84 http_version */
108    curl_integer,   /*  85 ftp_use_epsv */
109    curl_string,    /*  86 sslcerttype */
110    curl_string,    /*  87 sslkey */
111    curl_string,    /*  88 sslkeytype */
112    curl_string,    /*  89 sslengine */
113    curl_integer,   /*  90 sslengine_default */
114    curl_integer,   /*  91 dns_use_global_cache */
115    curl_integer,   /*  92 dns_cache_timeout */
116    curl_string,    /*  93 prequote */
117    curl_function,  /*  94 debugfunction */
118    curl_string,    /*  95 debugdata */
119    curl_integer,   /*  96 cookiesession */
120    curl_string,    /*  97 capath */
121    curl_integer,   /*  98 buffersize */
122    curl_integer,   /*  99 nosignal */
123    curl_string,    /* 100 share */
124    curl_integer,   /* 101 proxytype */
125    curl_string,    /* 102 accept_encoding */
126    curl_string,    /* 103 private */
127    curl_string,    /* 104 http200aliases */
128    curl_integer,   /* 105 unrestricted_auth */
129    curl_integer,   /* 106 ftp_use_eprt */
130    curl_integer,   /* 107 httpauth */
131    curl_function,  /* 108 ssl_ctx_function */
132    curl_string,    /* 109 ssl_ctx_data */
133    curl_integer,   /* 110 ftp_create_missing_dirs */
134    curl_integer,   /* 111 proxyauth */
135    curl_integer,   /* 112 server_response_timeout | ftp_response_timeout */
136    curl_integer,   /* 113 ipresolve */
137    curl_integer,   /* 114 maxfilesize */
138    curl_offset,    /* 115 infilesize_large */
139    curl_offset,    /* 116 resume_from_large */
140    curl_offset,    /* 117 maxfilesize_large */
141    curl_string,    /* 118 netrc_file */
142    curl_integer,   /* 119 use_ssl */
143    curl_offset,    /* 120 postfieldsize_large */
144    curl_integer,   /* 121 tcp_nodelay */
145    curl_ignore,    /* 122 */
146    curl_ignore,    /* 123 */
147    curl_ignore,    /* 124 */
148    curl_ignore,    /* 125 */
149    curl_ignore,    /* 126 */
150    curl_ignore,    /* 127 */
151    curl_ignore,    /* 128 */
152    curl_integer,   /* 129 ftpsslauth */
153    curl_function,  /* 130 ioctlfunction */
154    curl_string,    /* 131 ioctldata */
155    curl_ignore,    /* 132 */
156    curl_ignore,    /* 133 */
157    curl_string,    /* 134 ftp_account */
158    curl_string,    /* 135 cookielist */
159    curl_integer,   /* 136 ignore_content_length */
160    curl_integer,   /* 137 ftp_skip_pasv_ip */
161    curl_integer,   /* 138 ftp_filemethod */
162    curl_integer,   /* 139 localport */
163    curl_integer,   /* 140 localportrange */
164    curl_integer,   /* 141 connect_only */
165    curl_function,  /* 142 conv_from_network_function */
166    curl_function,  /* 143 conv_to_network_function */
167    curl_function,  /* 144 conv_from_utf8_function */
168    curl_offset,    /* 145 max_send_speed_large */
169    curl_offset,    /* 146 max_recv_speed_large */
170    curl_string,    /* 147 ftp_alternative_to_user */
171    curl_function,  /* 148 sockoptfunction */
172    curl_string,    /* 149 sockoptdata */
173    curl_integer,   /* 150 ssl_sessionid_cache */
174    curl_integer,   /* 151 ssh_auth_types */
175    curl_string,    /* 152 ssh_public_keyfile */
176    curl_string,    /* 153 ssh_private_keyfile */
177    curl_integer,   /* 154 ftp_ssl_ccc */
178    curl_integer,   /* 155 timeout_ms */
179    curl_integer,   /* 156 connecttimeout_ms */
180    curl_integer,   /* 157 http_transfer_decoding */
181    curl_integer,   /* 158 http_content_decoding */
182    curl_integer,   /* 159 new_file_perms */
183    curl_integer,   /* 160 new_directory_perms */
184    curl_integer,   /* 161 postredir */
185    curl_string,    /* 162 ssh_host_public_key_md5 */
186    curl_function,  /* 163 opensocketfunction */
187    curl_string,    /* 164 opensocketdata */
188    curl_string,    /* 165 copypostfields */
189    curl_integer,   /* 166 proxy_transfer_mode */
190    curl_function,  /* 167 seekfunction */
191    curl_string,    /* 168 seekdata */
192    curl_string,    /* 169 crlfile */
193    curl_string,    /* 170 issuercert */
194    curl_integer,   /* 171 address_scope */
195    curl_integer,   /* 172 certinfo */
196    curl_string,    /* 173 username */
197    curl_string,    /* 174 password */
198    curl_string,    /* 175 proxyusername */
199    curl_string,    /* 176 proxypassword */
200    curl_string,    /* 177 noproxy */
201    curl_integer,   /* 178 tftp_blksize */
202    curl_string,    /* 179 socks5_gssapi_service */
203    curl_integer,   /* 180 socks5_gssapi_nec */
204    curl_integer,   /* 181 protocols */
205    curl_integer,   /* 182 redir_protocols */
206    curl_string,    /* 183 ssh_knownhosts */
207    curl_function,  /* 184 ssh_keyfunction */
208    curl_string,    /* 185 ssh_keydata */
209    curl_string,    /* 186 mail_from */
210    curl_string,    /* 187 mail_rcpt */
211    curl_integer,   /* 188 ftp_use_pret */
212    curl_integer,   /* 189 rtsp_request */
213    curl_string,    /* 190 rtsp_session_id */
214    curl_string,    /* 191 rtsp_stream_uri */
215    curl_string,    /* 192 rtsp_transport */
216    curl_integer,   /* 193 rtsp_client_cseq */
217    curl_integer,   /* 194 rtsp_server_cseq */
218    curl_string,    /* 195 interleavedata */
219    curl_function,  /* 196 interleavefunction */
220    curl_integer,   /* 197 wildcardmatch */
221    curl_function,  /* 198 chunk_bgn_function */
222    curl_function,  /* 199 chunk_end_function */
223    curl_function,  /* 200 fnmatch_function */
224    curl_string,    /* 201 chunk_data */
225    curl_string,    /* 202 fnmatch_data */
226    curl_string,    /* 203 resolve */
227    curl_string,    /* 204 tlsauth_username */
228    curl_string,    /* 205 tlsauth_password */
229    curl_string,    /* 206 tlsauth_type */
230    curl_integer,   /* 207 transfer_encoding */
231    curl_function,  /* 208 closesocketfunction */
232    curl_string,    /* 209 closesocketdata */
233    curl_integer,   /* 210 gssapi_delegation */
234    curl_string,    /* 211 dns_servers */
235    curl_integer,   /* 212 accepttimeout_ms */
236    curl_integer,   /* 213 tcp_keepalive */
237    curl_integer,   /* 214 tcp_keepidle */
238    curl_integer,   /* 215 tcp_keepintvl */
239    curl_integer,   /* 216 ssl_options */
240    curl_string,    /* 217 mail_auth */
241    curl_integer,   /* 218 sasl_ir */
242    curl_function,  /* 219 xferinfofunction */
243    curl_string,    /* 220 xoauth2_bearer */
244    curl_string,    /* 221 dns_interface */
245    curl_string,    /* 222 dns_local_ip4 */
246    curl_string,    /* 223 dns_local_ip6 */
247    curl_string,    /* 224 login_options */
248    curl_integer,   /* 225 ssl_enable_npn */
249    curl_integer,   /* 226 ssl_enable_alpn */
250    curl_integer    /* 227 expect_100_timeout_ms */
251};
252
253# define curl_option_min             1
254# define curl_option_max           227
255# define curl_option_writedata       1
256# define curl_option_url             2
257# define curl_option_writefunction  11
258
259# define curl_integer_base       0 /* long */
260# define curl_string_base    10000
261# define curl_object_base    10000
262# define curl_function_base  20000
263# define curl_offset_base    30000
264# define curl_offset_blob    40000
265
266typedef size_t (*curl_write_callback) (
267    char   *buffer,
268    size_t  size,
269    size_t  nitems,
270    void   *userdata
271);
272
273typedef struct curllib_state_info {
274
275    int initialized;
276    int padding;
277
278    char * (*curl_version) (
279        void
280    );
281
282    void (*curl_free) (
283        void* p
284    );
285
286    curl_instance (*curl_easy_init) (
287        void
288    );
289
290    void (*curl_easy_cleanup) (
291        curl_instance handle
292    );
293
294    curl_return_code (*curl_easy_perform) (
295        curl_instance handle
296    );
297
298    curl_return_code (*curl_easy_setopt) (
299        curl_instance handle,
300        int           option,
301        ...
302    );
303
304    char* (*curl_easy_escape) (
305        curl_instance  handle,
306        const char    *url,
307        int            length
308    );
309
310    char* (*curl_easy_unescape) (
311        curl_instance  handle,
312        const char    *url,
313        int            length,
314        int           *outlength
315    );
316
317    const char* (*curl_easy_strerror) (
318        curl_error_code errcode
319    );
320
321} curllib_state_info;
322
323static curllib_state_info curllib_state = {
324
325    .initialized        = 0,
326    .padding            = 0,
327
328    .curl_version       = NULL,
329    .curl_free          = NULL,
330    .curl_easy_init     = NULL,
331    .curl_easy_cleanup  = NULL,
332    .curl_easy_perform  = NULL,
333    .curl_easy_setopt   = NULL,
334    .curl_easy_escape   = NULL,
335    .curl_easy_unescape = NULL,
336    .curl_easy_strerror = NULL,
337
338};
339
340static int curllib_initialize(lua_State * L)
341{
342    if (! curllib_state.initialized) {
343        const char *filename = lua_tostring(L, 1);
344        if (filename) {
345
346            lmt_library lib = lmt_library_load(filename);
347
348            curllib_state.curl_version       = lmt_library_find(lib, "curl_version");
349            curllib_state.curl_free          = lmt_library_find(lib, "curl_free");
350            curllib_state.curl_easy_init     = lmt_library_find(lib, "curl_easy_init");
351            curllib_state.curl_easy_cleanup  = lmt_library_find(lib, "curl_easy_cleanup");
352            curllib_state.curl_easy_perform  = lmt_library_find(lib, "curl_easy_perform");
353            curllib_state.curl_easy_setopt   = lmt_library_find(lib, "curl_easy_setopt");
354            curllib_state.curl_easy_escape   = lmt_library_find(lib, "curl_easy_escape");
355            curllib_state.curl_easy_unescape = lmt_library_find(lib, "curl_easy_unescape");
356            curllib_state.curl_easy_strerror = lmt_library_find(lib, "curl_easy_strerror");
357
358            curllib_state.initialized = lmt_library_okay(lib);
359        }
360    }
361    lua_pushboolean(L, curllib_state.initialized);
362    return 1;
363}
364
365/* fetch(url, { options }) | fetch({ options }) */
366
367/* we don't need threads so we can just use the local init */
368
369static size_t curllib_write_cb(char *data, size_t n, size_t l, void *b)
370{
371    luaL_addlstring((luaL_Buffer *) b, data, n * l);
372    return n * l;
373}
374
375/*tex
376    Always assume a table as we need to sanitize keys anyway. A former variant also accepted strings
377    but why have more code than needed.
378*/
379
380static int curllib_fetch(lua_State * L)
381{
382    if (curllib_state.initialized) {
383        if (lua_type(L,1) == LUA_TTABLE) {
384            curl_instance *curl = curllib_state.curl_easy_init();
385            if (curl)  {
386                int result; 
387                luaL_Buffer buffer;
388                luaL_buffinit(L, &buffer);
389                curllib_state.curl_easy_setopt(curl, curl_object_base + curl_option_writedata, &buffer);
390                curllib_state.curl_easy_setopt(curl, curl_function_base + curl_option_writefunction, &curllib_write_cb);
391                lua_pushnil(L);  /* first key */
392                while (lua_next(L, 1) != 0) {
393                    if (lua_type(L, -2) == LUA_TNUMBER) {
394                        int o = lmt_tointeger(L, -2);
395                        if (o >= curl_option_min && o <= curl_option_max) {
396                            switch (curl_options[o]) {
397                                case curl_string:
398                                    if (lua_type(L, -1) == LUA_TSTRING) {
399                                        curllib_state.curl_easy_setopt(curl, curl_string_base + o, lua_tostring(L, -1));
400                                    } else {
401                                     // return luaL_error(L, "curl option %d must be a string", o);
402                                    }
403                                    break;
404                                case curl_integer:
405                                    switch (lua_type(L, -1)) {
406                                        case LUA_TNUMBER:
407                                            curllib_state.curl_easy_setopt(curl, curl_integer_base + o, lua_tointeger(L, -1));
408                                            break;
409                                        case LUA_TBOOLEAN:
410                                            curllib_state.curl_easy_setopt(curl, curl_integer_base + o, lua_toboolean(L, -1));
411                                            break;
412                                        default:
413                                         // return luaL_error(L, "curl option %d must be a number of boolean", o);
414                                            break;
415                                    }
416                                    break;
417                            }
418                        } else {
419                         // return luaL_error(L, "curl option %d is invalid", o);
420                        }
421                    } else {
422                     // return luaL_error(L, "curl option id should en a number");
423                    }
424                    lua_pop(L, 1); /* removes 'value' and keeps 'key' for next iteration */
425                }
426                result = curllib_state.curl_easy_perform(curl);
427                if (result) {
428                    lua_pushboolean(L, 0);
429                    lua_pushstring(L, curllib_state.curl_easy_strerror(result));
430                    result = 2;
431                } else {
432                    luaL_pushresult(&buffer);
433                    result = 1;
434                }
435                curllib_state.curl_easy_cleanup(curl);
436                return result;
437            }
438        }
439    }
440    return 0;
441}
442
443static int curllib_escape(lua_State * L)
444{
445    if (curllib_state.initialized) {
446        curl_instance *curl = curllib_state.curl_easy_init();
447        if (curl) {
448            size_t length = 0;
449            const char * url = lua_tolstring(L, 1, &length);
450            char *s = curllib_state.curl_easy_escape(curl, url, (int) length);
451            if (s) {
452                lua_pushstring(L,(const char *) s);
453                curllib_state.curl_free(s);
454                curllib_state.curl_easy_cleanup(curl);
455                return 1;
456            }
457        }
458    }
459    return 0;
460}
461
462static int curllib_unescape(lua_State * L)
463{
464    if (curllib_state.initialized) {
465        curl_instance *curl = curllib_state.curl_easy_init();
466        if (curl) {
467            size_t length = 0;
468            const char *url = lua_tolstring(L, 1, &length);
469            int l = 0;
470            char *s = curllib_state.curl_easy_unescape(curl, url, (int) length, &l);
471            if (s) {
472                lua_pushlstring(L, s, l);
473                curllib_state.curl_free(s);
474                curllib_state.curl_easy_cleanup(curl);
475                return 1;
476            }
477        }
478    }
479    return 0;
480}
481
482static int curllib_getversion(lua_State * L)
483{
484    if (curllib_state.initialized) {
485        char *version = curllib_state.curl_version();
486        if (version) {
487            lua_pushstring(L, version);
488            return 1;
489        }
490    }
491    return 0;
492}
493
494static struct luaL_Reg curllib_function_list[] = {
495    { "initialize", curllib_initialize },
496    { "fetch",      curllib_fetch      },
497    { "escape",     curllib_escape     },
498    { "unescape",   curllib_unescape   },
499    { "getversion", curllib_getversion },
500    { NULL,         NULL               },
501};
502
503int luaopen_curl(lua_State * L)
504{
505    lmt_library_register(L, "curl", curllib_function_list);
506    return 0;
507}
508