1 /**
2  * @file
3  * Integrated Lua scripting
4  *
5  * @authors
6  * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
7  * Copyright (C) 2016 Bernard Pratz <z+mutt+pub@m0g.net>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page neo_mutt_lua Integrated Lua scripting
26  *
27  * Integrated Lua scripting
28  */
29 
30 #ifndef LUA_COMPAT_ALL
31 #define LUA_COMPAT_ALL
32 #endif
33 #ifndef LUA_COMPAT_5_1
34 #define LUA_COMPAT_5_1
35 #endif
36 
37 #include "config.h"
38 #include <lauxlib.h>
39 #include <limits.h>
40 #include <lua.h>
41 #include <lualib.h>
42 #include <stdbool.h>
43 #include <stdint.h>
44 #include <stdio.h>
45 #include "mutt/lib.h"
46 #include "config/lib.h"
47 #include "core/lib.h"
48 #include "mutt.h"
49 #include "mutt_lua.h"
50 #include "init.h"
51 #include "mutt_commands.h"
52 #include "muttlib.h"
53 #include "myvar.h"
54 
55 /// Global Lua State
56 lua_State *LuaState = NULL;
57 
58 /**
59  * LuaCommands - List of NeoMutt commands to register
60  */
61 static const struct Command LuaCommands[] = {
62   // clang-format off
63   { "lua",        mutt_lua_parse,       0 },
64   { "lua-source", mutt_lua_source_file, 0 },
65   // clang-format on
66 };
67 
68 /**
69  * handle_panic - Handle a panic in the Lua interpreter
70  * @param l Lua State
71  * @retval -1 Always
72  */
handle_panic(lua_State * l)73 static int handle_panic(lua_State *l)
74 {
75   mutt_debug(LL_DEBUG1, "lua runtime panic: %s\n", lua_tostring(l, -1));
76   mutt_error("Lua runtime panic: %s", lua_tostring(l, -1));
77   lua_pop(l, 1);
78   return -1;
79 }
80 
81 /**
82  * handle_error - Handle an error in the Lua interpreter
83  * @param l Lua State
84  * @retval -1 Always
85  */
handle_error(lua_State * l)86 static int handle_error(lua_State *l)
87 {
88   mutt_debug(LL_DEBUG1, "lua runtime error: %s\n", lua_tostring(l, -1));
89   mutt_error("Lua runtime error: %s", lua_tostring(l, -1));
90   lua_pop(l, 1);
91   return -1;
92 }
93 
94 /**
95  * lua_mutt_call - Call a NeoMutt command by name
96  * @param l Lua State
97  * @retval >=0 Success
98  * @retval -1 Error
99  */
lua_mutt_call(lua_State * l)100 static int lua_mutt_call(lua_State *l)
101 {
102   mutt_debug(LL_DEBUG2, " * lua_mutt_call()\n");
103   struct Buffer *err = mutt_buffer_pool_get();
104   struct Buffer *token = mutt_buffer_pool_get();
105   char buf[1024] = { 0 };
106   const struct Command *cmd = NULL;
107   int rc = 0;
108 
109   if (lua_gettop(l) == 0)
110   {
111     luaL_error(l, "Error command argument required.");
112     return -1;
113   }
114 
115   cmd = mutt_command_get(lua_tostring(l, 1));
116   if (!cmd)
117   {
118     luaL_error(l, "Error command %s not found.", lua_tostring(l, 1));
119     return -1;
120   }
121 
122   for (int i = 2; i <= lua_gettop(l); i++)
123   {
124     const char *s = lua_tostring(l, i);
125     mutt_strn_cat(buf, sizeof(buf), s, mutt_str_len(s));
126     mutt_strn_cat(buf, sizeof(buf), " ", 1);
127   }
128 
129   struct Buffer expn = mutt_buffer_make(0);
130   expn.data = buf;
131   expn.dptr = buf;
132   expn.dsize = mutt_str_len(buf);
133 
134   if (cmd->parse(token, &expn, cmd->data, err))
135   {
136     luaL_error(l, "NeoMutt error: %s", mutt_buffer_string(err));
137     rc = -1;
138   }
139   else
140   {
141     if (!lua_pushstring(l, mutt_buffer_string(err)))
142       handle_error(l);
143     else
144       rc++;
145   }
146 
147   mutt_buffer_pool_release(&token);
148   mutt_buffer_pool_release(&err);
149   return rc;
150 }
151 
152 /**
153  * lua_mutt_set - Set a NeoMutt variable
154  * @param l Lua State
155  * @retval  0 Success
156  * @retval -1 Error
157  */
lua_mutt_set(lua_State * l)158 static int lua_mutt_set(lua_State *l)
159 {
160   const char *param = lua_tostring(l, -2);
161   mutt_debug(LL_DEBUG2, " * lua_mutt_set(%s)\n", param);
162 
163   if (mutt_str_startswith(param, "my_"))
164   {
165     const char *val = lua_tostring(l, -1);
166     myvar_set(param, val);
167     return 0;
168   }
169 
170   struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
171   if (!he)
172   {
173     luaL_error(l, "NeoMutt parameter not found %s", param);
174     return -1;
175   }
176 
177   struct ConfigDef *cdef = he->data;
178 
179   int rc = 0;
180   struct Buffer err = mutt_buffer_make(256);
181 
182   switch (DTYPE(cdef->type))
183   {
184     case DT_ADDRESS:
185     case DT_ENUM:
186     case DT_MBTABLE:
187     case DT_PATH:
188     case DT_REGEX:
189     case DT_SLIST:
190     case DT_SORT:
191     case DT_STRING:
192     {
193       const char *value = lua_tostring(l, -1);
194       size_t val_size = lua_rawlen(l, -1);
195       struct Buffer value_buf = mutt_buffer_make(val_size);
196       mutt_buffer_strcpy_n(&value_buf, value, val_size);
197       if (DTYPE(he->type) == DT_PATH)
198         mutt_buffer_expand_path(&value_buf);
199 
200       int rv = cs_subset_he_string_set(NeoMutt->sub, he, value_buf.data, &err);
201       mutt_buffer_dealloc(&value_buf);
202       if (CSR_RESULT(rv) != CSR_SUCCESS)
203         rc = -1;
204       break;
205     }
206     case DT_NUMBER:
207     case DT_QUAD:
208     {
209       const intptr_t value = lua_tointeger(l, -1);
210       int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, &err);
211       if (CSR_RESULT(rv) != CSR_SUCCESS)
212         rc = -1;
213       break;
214     }
215     case DT_BOOL:
216     {
217       const intptr_t value = lua_toboolean(l, -1);
218       int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, &err);
219       if (CSR_RESULT(rv) != CSR_SUCCESS)
220         rc = -1;
221       break;
222     }
223     default:
224       luaL_error(l, "Unsupported NeoMutt parameter type %d for %s", DTYPE(cdef->type), param);
225       rc = -1;
226       break;
227   }
228 
229   mutt_buffer_dealloc(&err);
230   return rc;
231 }
232 
233 /**
234  * lua_mutt_get - Get a NeoMutt variable
235  * @param l Lua State
236  * @retval  1 Success
237  * @retval -1 Error
238  */
lua_mutt_get(lua_State * l)239 static int lua_mutt_get(lua_State *l)
240 {
241   const char *param = lua_tostring(l, -1);
242   mutt_debug(LL_DEBUG2, " * lua_mutt_get(%s)\n", param);
243 
244   if (mutt_str_startswith(param, "my_"))
245   {
246     const char *mv = myvar_get(param);
247     if (!mv)
248     {
249       luaL_error(l, "NeoMutt parameter not found %s", param);
250       return -1;
251     }
252 
253     lua_pushstring(l, mv);
254     return 1;
255   }
256 
257   struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
258   if (!he)
259   {
260     mutt_debug(LL_DEBUG2, " * error\n");
261     luaL_error(l, "NeoMutt parameter not found %s", param);
262     return -1;
263   }
264 
265   struct ConfigDef *cdef = he->data;
266 
267   switch (DTYPE(cdef->type))
268   {
269     case DT_ADDRESS:
270     case DT_ENUM:
271     case DT_MBTABLE:
272     case DT_REGEX:
273     case DT_SLIST:
274     case DT_SORT:
275     case DT_STRING:
276     {
277       struct Buffer value = mutt_buffer_make(256);
278       int rc = cs_subset_he_string_get(NeoMutt->sub, he, &value);
279       if (CSR_RESULT(rc) != CSR_SUCCESS)
280       {
281         mutt_buffer_dealloc(&value);
282         return -1;
283       }
284 
285       struct Buffer escaped = mutt_buffer_make(256);
286       escape_string(&escaped, value.data);
287       lua_pushstring(l, escaped.data);
288       mutt_buffer_dealloc(&value);
289       mutt_buffer_dealloc(&escaped);
290       return 1;
291     }
292     case DT_QUAD:
293       lua_pushinteger(l, (unsigned char) cdef->var);
294       return 1;
295     case DT_NUMBER:
296       lua_pushinteger(l, (signed short) cdef->var);
297       return 1;
298     case DT_BOOL:
299       lua_pushboolean(l, (bool) cdef->var);
300       return 1;
301     default:
302       luaL_error(l, "NeoMutt parameter type %d unknown for %s", cdef->type, param);
303       return -1;
304   }
305 }
306 
307 /**
308  * lua_mutt_enter - Execute NeoMutt config from Lua
309  * @param l Lua State
310  * @retval >=0 Success
311  * @retval -1  Error
312  */
lua_mutt_enter(lua_State * l)313 static int lua_mutt_enter(lua_State *l)
314 {
315   mutt_debug(LL_DEBUG2, " * lua_mutt_enter()\n");
316   struct Buffer *err = mutt_buffer_pool_get();
317   char *buf = mutt_str_dup(lua_tostring(l, -1));
318   int rc = 0;
319 
320   if (mutt_parse_rc_line(buf, err))
321   {
322     luaL_error(l, "NeoMutt error: %s", mutt_buffer_string(err));
323     rc = -1;
324   }
325   else
326   {
327     if (!lua_pushstring(l, mutt_buffer_string(err)))
328       handle_error(l);
329     else
330       rc++;
331   }
332 
333   FREE(&buf);
334   mutt_buffer_pool_release(&err);
335 
336   return rc;
337 }
338 
339 /**
340  * lua_mutt_message - Display a message in Neomutt
341  * @param l Lua State
342  * @retval 0 Always
343  */
lua_mutt_message(lua_State * l)344 static int lua_mutt_message(lua_State *l)
345 {
346   mutt_debug(LL_DEBUG2, " * lua_mutt_message()\n");
347   const char *msg = lua_tostring(l, -1);
348   if (msg)
349     mutt_message(msg);
350   return 0;
351 }
352 
353 /**
354  * lua_mutt_error - Display an error in Neomutt
355  * @param l Lua State
356  * @retval 0 Always
357  */
lua_mutt_error(lua_State * l)358 static int lua_mutt_error(lua_State *l)
359 {
360   mutt_debug(LL_DEBUG2, " * lua_mutt_error()\n");
361   const char *msg = lua_tostring(l, -1);
362   if (msg)
363     mutt_error(msg);
364   return 0;
365 }
366 
367 /**
368  * lua_expose_command - Expose a NeoMutt command to the Lua interpreter
369  * @param p   Lua state
370  * @param cmd NeoMutt Command
371  */
lua_expose_command(void * p,const struct Command * cmd)372 static void lua_expose_command(void *p, const struct Command *cmd)
373 {
374   lua_State *l = (lua_State *) p;
375   char buf[1024];
376   snprintf(buf, sizeof(buf), "mutt.command.%s = function (...); mutt.call('%s', ...); end",
377            cmd->name, cmd->name);
378   (void) luaL_dostring(l, buf);
379 }
380 
381 /**
382  * LuaMuttCommands - List of Lua commands to register
383  *
384  * In NeoMutt, run:
385  *
386  * `:lua mutt.message('hello')`
387  *
388  * and it will call lua_mutt_message()
389  */
390 static const luaL_Reg LuaMuttCommands[] = {
391   // clang-format off
392   { "set",     lua_mutt_set },
393   { "get",     lua_mutt_get },
394   { "call",    lua_mutt_call },
395   { "enter",   lua_mutt_enter },
396   { "print",   lua_mutt_message },
397   { "message", lua_mutt_message },
398   { "error",   lua_mutt_error },
399   { NULL, NULL },
400   // clang-format on
401 };
402 
403 /**
404  * luaopen_mutt_decl - Declare some NeoMutt types to the Lua interpreter
405  * @param l Lua State
406  * @retval 1 Always
407  */
luaopen_mutt_decl(lua_State * l)408 static int luaopen_mutt_decl(lua_State *l)
409 {
410   mutt_debug(LL_DEBUG2, " * luaopen_mutt()\n");
411   luaL_newlib(l, LuaMuttCommands);
412   int lib_idx = lua_gettop(l);
413 
414   // clang-format off
415   lua_pushstring(l, "VERSION");     lua_pushstring(l, mutt_make_version()); lua_settable(l, lib_idx);;
416   lua_pushstring(l, "QUAD_YES");    lua_pushinteger(l, MUTT_YES);           lua_settable(l, lib_idx);;
417   lua_pushstring(l, "QUAD_NO");     lua_pushinteger(l, MUTT_NO);            lua_settable(l, lib_idx);;
418   lua_pushstring(l, "QUAD_ASKYES"); lua_pushinteger(l, MUTT_ASKYES);        lua_settable(l, lib_idx);;
419   lua_pushstring(l, "QUAD_ASKNO");  lua_pushinteger(l, MUTT_ASKNO);         lua_settable(l, lib_idx);;
420   // clang-format on
421 
422   return 1;
423 }
424 
425 /**
426  * luaopen_mutt - Expose a 'Mutt' object to the Lua interpreter
427  * @param l Lua State
428  */
luaopen_mutt(lua_State * l)429 static void luaopen_mutt(lua_State *l)
430 {
431   luaL_requiref(l, "mutt", luaopen_mutt_decl, 1);
432   (void) luaL_dostring(l, "mutt.command = {}");
433   mutt_commands_apply((void *) l, &lua_expose_command);
434 }
435 
436 /**
437  * lua_init - Initialise a Lua State
438  * @param[out] l Lua State
439  * @retval true Successful
440  */
lua_init(lua_State ** l)441 static bool lua_init(lua_State **l)
442 {
443   if (!l)
444     return false;
445   if (*l)
446     return true;
447 
448   mutt_debug(LL_DEBUG2, " * lua_init()\n");
449   *l = luaL_newstate();
450 
451   if (!*l)
452   {
453     mutt_error(_("Error: Couldn't load the lua interpreter"));
454     return false;
455   }
456 
457   lua_atpanic(*l, handle_panic);
458 
459   /* load various Lua libraries */
460   luaL_openlibs(*l);
461   luaopen_mutt(*l);
462 
463   return true;
464 }
465 
466 /**
467  * mutt_lua_init - Setup feature commands
468  */
mutt_lua_init(void)469 void mutt_lua_init(void)
470 {
471   COMMANDS_REGISTER(LuaCommands);
472 }
473 
474 /**
475  * mutt_lua_parse - Parse the 'lua' command - Implements Command::parse() - @ingroup command_parse
476  */
mutt_lua_parse(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)477 enum CommandResult mutt_lua_parse(struct Buffer *buf, struct Buffer *s,
478                                   intptr_t data, struct Buffer *err)
479 {
480   lua_init(&LuaState);
481   mutt_debug(LL_DEBUG2, " * mutt_lua_parse(%s)\n", buf->data);
482 
483   if (luaL_dostring(LuaState, s->dptr))
484   {
485     mutt_debug(LL_DEBUG2, " * %s -> failure\n", s->dptr);
486     mutt_buffer_printf(err, "%s: %s", s->dptr, lua_tostring(LuaState, -1));
487     /* pop error message from the stack */
488     lua_pop(LuaState, 1);
489     return MUTT_CMD_ERROR;
490   }
491   mutt_debug(LL_DEBUG2, " * %s -> success\n", s->dptr);
492   mutt_buffer_reset(s); // Clear the rest of the line
493   return MUTT_CMD_SUCCESS;
494 }
495 
496 /**
497  * mutt_lua_source_file - Parse the 'lua-source' command - Implements Command::parse() - @ingroup command_parse
498  */
mutt_lua_source_file(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)499 enum CommandResult mutt_lua_source_file(struct Buffer *buf, struct Buffer *s,
500                                         intptr_t data, struct Buffer *err)
501 {
502   mutt_debug(LL_DEBUG2, " * mutt_lua_source()\n");
503 
504   lua_init(&LuaState);
505 
506   char path[PATH_MAX];
507 
508   if (mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS) != 0)
509   {
510     mutt_buffer_printf(err, _("source: error at %s"), s->dptr);
511     return MUTT_CMD_ERROR;
512   }
513   if (MoreArgs(s))
514   {
515     mutt_buffer_printf(err, _("%s: too many arguments"), "source");
516     return MUTT_CMD_WARNING;
517   }
518   mutt_str_copy(path, buf->data, sizeof(path));
519   mutt_expand_path(path, sizeof(path));
520 
521   if (luaL_dofile(LuaState, path))
522   {
523     mutt_error(_("Couldn't source lua source: %s"), lua_tostring(LuaState, -1));
524     lua_pop(LuaState, 1);
525     return MUTT_CMD_ERROR;
526   }
527   return MUTT_CMD_SUCCESS;
528 }
529