You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lua_html.cxx 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. /*-
  2. * Copyright 2016 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "lua_common.h"
  17. #include "message.h"
  18. #include "libserver/html/html.h"
  19. #include "libserver/html/html.hxx"
  20. #include "libserver/html/html_tag.hxx"
  21. #include "libserver/html/html_block.hxx"
  22. #include "images.h"
  23. #include "contrib/ankerl/unordered_dense.h"
  24. #include <frozen/string.h>
  25. #include <frozen/unordered_map.h>
  26. /***
  27. * @module rspamd_html
  28. * This module provides different methods to access HTML tags. To get HTML context
  29. * from an HTML part you could use method `part:get_html()`
  30. * @example
  31. rspamd_config.R_EMPTY_IMAGE = function(task)
  32. local tp = task:get_text_parts() -- get text parts in a message
  33. for _,p in ipairs(tp) do -- iterate over text parts array using `ipairs`
  34. if p:is_html() then -- if the current part is html part
  35. local hc = p:get_html() -- we get HTML context
  36. local len = p:get_length() -- and part's length
  37. if len < 50 then -- if we have a part that has less than 50 bytes of text
  38. local images = hc:get_images() -- then we check for HTML images
  39. if images then -- if there are images
  40. for _,i in ipairs(images) do -- then iterate over images in the part
  41. if i['height'] + i['width'] >= 400 then -- if we have a large image
  42. return true -- add symbol
  43. end
  44. end
  45. end
  46. end
  47. end
  48. end
  49. end
  50. */
  51. /***
  52. * @method html:has_tag(name)
  53. * Checks if a specified tag `name` is presented in a part
  54. * @param {string} name name of tag to check
  55. * @return {boolean} `true` if the tag exists in HTML tree
  56. */
  57. LUA_FUNCTION_DEF(html, has_tag);
  58. /***
  59. * @method html:check_property(name)
  60. * Checks if the HTML has a specific property. Here is the list of available properties:
  61. *
  62. * - `no_html` - no html tag presented
  63. * - `bad_element` - part has some broken elements
  64. * - `xml` - part is xhtml
  65. * - `unknown_element` - part has some unknown elements
  66. * - `duplicate_element` - part has some duplicate elements that should be unique (namely, `title` tag)
  67. * - `unbalanced` - part has unbalanced tags
  68. * @param {string} name name of property
  69. * @return {boolean} true if the part has the specified property
  70. */
  71. LUA_FUNCTION_DEF(html, has_property);
  72. /***
  73. * @method html:get_images()
  74. * Returns a table of images found in html. Each image is, in turn, a table with the following fields:
  75. *
  76. * - `src` - link to the source
  77. * - `height` - height in pixels
  78. * - `width` - width in pixels
  79. * - `embedded` - `true` if an image is embedded in a message
  80. * @return {table} table of images in html part
  81. */
  82. LUA_FUNCTION_DEF(html, get_images);
  83. /***
  84. * @method html:foreach_tag(tagname, callback)
  85. * Processes HTML tree calling the specified callback for each tag of the specified
  86. * type.
  87. *
  88. * Callback is called with the following attributes:
  89. *
  90. * - `tag`: html tag structure
  91. * - `content_length`: length of content within a tag
  92. *
  93. * Callback function should return `true` to **stop** processing and `false` to continue
  94. * @return nothing
  95. */
  96. LUA_FUNCTION_DEF(html, foreach_tag);
  97. /***
  98. * @method html:get_invisible()
  99. * Returns invisible content of the HTML data
  100. * @return
  101. */
  102. LUA_FUNCTION_DEF(html, get_invisible);
  103. static const struct luaL_reg htmllib_m[] = {
  104. LUA_INTERFACE_DEF(html, has_tag),
  105. LUA_INTERFACE_DEF(html, has_property),
  106. LUA_INTERFACE_DEF(html, get_images),
  107. LUA_INTERFACE_DEF(html, foreach_tag),
  108. LUA_INTERFACE_DEF(html, get_invisible),
  109. {"__tostring", rspamd_lua_class_tostring},
  110. {NULL, NULL}};
  111. /***
  112. * @method html_tag:get_type()
  113. * Returns string representation of HTML type for a tag
  114. * @return {string} type of tag
  115. */
  116. LUA_FUNCTION_DEF(html_tag, get_type);
  117. /***
  118. * @method html_tag:get_extra()
  119. * Returns extra data associated with the tag
  120. * @return {url|image|nil} extra data associated with the tag
  121. */
  122. LUA_FUNCTION_DEF(html_tag, get_extra);
  123. /***
  124. * @method html_tag:get_parent()
  125. * Returns parent node for a specified tag
  126. * @return {html_tag} parent object for a specified tag
  127. */
  128. LUA_FUNCTION_DEF(html_tag, get_parent);
  129. /***
  130. * @method html_tag:get_flags()
  131. * Returns flags a specified tag:
  132. *
  133. * - `closed`: tag is properly closed
  134. * - `closing`: tag is a closing tag
  135. * - `broken`: tag is somehow broken
  136. * - `unbalanced`: tag is unbalanced
  137. * - `xml`: tag is xml tag
  138. * @return {table} table of flags
  139. */
  140. LUA_FUNCTION_DEF(html_tag, get_flags);
  141. /***
  142. * @method html_tag:get_content()
  143. * Returns content of tag (approximate for some cases)
  144. * @return {rspamd_text} rspamd text with tag's content
  145. */
  146. LUA_FUNCTION_DEF(html_tag, get_content);
  147. /***
  148. * @method html_tag:get_content_length()
  149. * Returns length of a tag's content
  150. * @return {number} size of content enclosed within a tag
  151. */
  152. LUA_FUNCTION_DEF(html_tag, get_content_length);
  153. /***
  154. * @method html_tag:get_style()
  155. * Returns style calculated for the element
  156. * @return {table} table associated with the style
  157. */
  158. LUA_FUNCTION_DEF(html_tag, get_style);
  159. /***
  160. * @method html_tag:get_attribute(name)
  161. * Returns value of attribute for the element
  162. * Refer to `html_components_map` in `src/libserver/html/html.cxx` for recognised names
  163. * @return {string|nil} value of the attribute
  164. */
  165. LUA_FUNCTION_DEF(html_tag, get_attribute);
  166. static const struct luaL_reg taglib_m[] = {
  167. LUA_INTERFACE_DEF(html_tag, get_type),
  168. LUA_INTERFACE_DEF(html_tag, get_extra),
  169. LUA_INTERFACE_DEF(html_tag, get_parent),
  170. LUA_INTERFACE_DEF(html_tag, get_flags),
  171. LUA_INTERFACE_DEF(html_tag, get_content),
  172. LUA_INTERFACE_DEF(html_tag, get_content_length),
  173. LUA_INTERFACE_DEF(html_tag, get_style),
  174. LUA_INTERFACE_DEF(html_tag, get_attribute),
  175. {"__tostring", rspamd_lua_class_tostring},
  176. {NULL, NULL}};
  177. static struct rspamd::html::html_content *
  178. lua_check_html(lua_State *L, int pos)
  179. {
  180. void *ud = rspamd_lua_check_udata(L, pos, rspamd_html_classname);
  181. luaL_argcheck(L, ud != NULL, pos, "'html' expected");
  182. return ud ? *((struct rspamd::html::html_content **) ud) : NULL;
  183. }
  184. struct lua_html_tag {
  185. rspamd::html::html_content *html;
  186. const rspamd::html::html_tag *tag;
  187. };
  188. static struct lua_html_tag *
  189. lua_check_html_tag(lua_State *L, int pos)
  190. {
  191. void *ud = rspamd_lua_check_udata(L, pos, rspamd_html_tag_classname);
  192. luaL_argcheck(L, ud != NULL, pos, "'html_tag' expected");
  193. return ud ? ((struct lua_html_tag *) ud) : NULL;
  194. }
  195. static int
  196. lua_html_has_tag(lua_State *L)
  197. {
  198. LUA_TRACE_POINT;
  199. auto *hc = lua_check_html(L, 1);
  200. const char *tagname = luaL_checkstring(L, 2);
  201. gboolean ret = FALSE;
  202. if (hc && tagname) {
  203. if (rspamd_html_tag_seen(hc, tagname)) {
  204. ret = TRUE;
  205. }
  206. }
  207. lua_pushboolean(L, ret);
  208. return 1;
  209. }
  210. constexpr const auto prop_map = frozen::make_unordered_map<frozen::string, int>({
  211. {"no_html", RSPAMD_HTML_FLAG_BAD_START},
  212. {"bad_start", RSPAMD_HTML_FLAG_BAD_START},
  213. {"bad_element", RSPAMD_HTML_FLAG_BAD_ELEMENTS},
  214. {"bad_elements", RSPAMD_HTML_FLAG_BAD_ELEMENTS},
  215. {"xml", RSPAMD_HTML_FLAG_XML},
  216. {"unknown_element", RSPAMD_HTML_FLAG_UNKNOWN_ELEMENTS},
  217. {"unknown_elements", RSPAMD_HTML_FLAG_UNKNOWN_ELEMENTS},
  218. {"duplicate_element", RSPAMD_HTML_FLAG_DUPLICATE_ELEMENTS},
  219. {"duplicate_elements", RSPAMD_HTML_FLAG_DUPLICATE_ELEMENTS},
  220. {"unbalanced", RSPAMD_HTML_FLAG_UNBALANCED},
  221. {"data_urls", RSPAMD_HTML_FLAG_HAS_DATA_URLS},
  222. });
  223. static int
  224. lua_html_has_property(lua_State *L)
  225. {
  226. LUA_TRACE_POINT;
  227. auto *hc = lua_check_html(L, 1);
  228. const char *propname = luaL_checkstring(L, 2);
  229. gboolean ret = FALSE;
  230. if (hc && propname) {
  231. auto found_prop = prop_map.find(frozen::string(propname));
  232. if (found_prop != prop_map.end()) {
  233. ret = hc->flags & found_prop->second;
  234. }
  235. }
  236. lua_pushboolean(L, ret);
  237. return 1;
  238. }
  239. static void
  240. lua_html_push_image(lua_State *L, const struct html_image *img)
  241. {
  242. LUA_TRACE_POINT;
  243. struct lua_html_tag *ltag;
  244. struct rspamd_url **purl;
  245. lua_createtable(L, 0, 7);
  246. if (img->src) {
  247. lua_pushstring(L, "src");
  248. if (img->flags & RSPAMD_HTML_FLAG_IMAGE_DATA) {
  249. struct rspamd_lua_text *t;
  250. t = static_cast<rspamd_lua_text *>(lua_newuserdata(L, sizeof(*t)));
  251. t->start = img->src;
  252. t->len = strlen(img->src);
  253. t->flags = 0;
  254. rspamd_lua_setclass(L, rspamd_text_classname, -1);
  255. }
  256. else {
  257. lua_pushstring(L, img->src);
  258. }
  259. lua_settable(L, -3);
  260. }
  261. if (img->url) {
  262. lua_pushstring(L, "url");
  263. purl = static_cast<rspamd_url **>(lua_newuserdata(L, sizeof(gpointer)));
  264. *purl = img->url;
  265. rspamd_lua_setclass(L, rspamd_url_classname, -1);
  266. lua_settable(L, -3);
  267. }
  268. if (img->tag) {
  269. lua_pushstring(L, "tag");
  270. ltag = static_cast<lua_html_tag *>(lua_newuserdata(L, sizeof(struct lua_html_tag)));
  271. ltag->tag = static_cast<rspamd::html::html_tag *>(img->tag);
  272. ltag->html = NULL;
  273. rspamd_lua_setclass(L, rspamd_html_tag_classname, -1);
  274. lua_settable(L, -3);
  275. }
  276. lua_pushstring(L, "height");
  277. lua_pushinteger(L, img->height);
  278. lua_settable(L, -3);
  279. lua_pushstring(L, "width");
  280. lua_pushinteger(L, img->width);
  281. lua_settable(L, -3);
  282. lua_pushstring(L, "embedded");
  283. lua_pushboolean(L, img->flags & RSPAMD_HTML_FLAG_IMAGE_EMBEDDED);
  284. lua_settable(L, -3);
  285. lua_pushstring(L, "data");
  286. lua_pushboolean(L, img->flags & RSPAMD_HTML_FLAG_IMAGE_DATA);
  287. lua_settable(L, -3);
  288. }
  289. static int
  290. lua_html_get_images(lua_State *L)
  291. {
  292. LUA_TRACE_POINT;
  293. auto *hc = lua_check_html(L, 1);
  294. unsigned int i = 1;
  295. if (hc != NULL) {
  296. lua_createtable(L, hc->images.size(), 0);
  297. for (const auto *img: hc->images) {
  298. lua_html_push_image(L, img);
  299. lua_rawseti(L, -2, i++);
  300. }
  301. }
  302. else {
  303. lua_newtable(L);
  304. }
  305. return 1;
  306. }
  307. static void
  308. lua_html_push_block(lua_State *L, const struct rspamd::html::html_block *bl)
  309. {
  310. LUA_TRACE_POINT;
  311. lua_createtable(L, 0, 6);
  312. if (bl->fg_color_mask) {
  313. lua_pushstring(L, "color");
  314. lua_createtable(L, 4, 0);
  315. lua_pushinteger(L, bl->fg_color.r);
  316. lua_rawseti(L, -2, 1);
  317. lua_pushinteger(L, bl->fg_color.g);
  318. lua_rawseti(L, -2, 2);
  319. lua_pushinteger(L, bl->fg_color.b);
  320. lua_rawseti(L, -2, 3);
  321. lua_pushinteger(L, bl->fg_color.alpha);
  322. lua_rawseti(L, -2, 4);
  323. lua_settable(L, -3);
  324. }
  325. if (bl->bg_color_mask) {
  326. lua_pushstring(L, "bgcolor");
  327. lua_createtable(L, 4, 0);
  328. lua_pushinteger(L, bl->bg_color.r);
  329. lua_rawseti(L, -2, 1);
  330. lua_pushinteger(L, bl->bg_color.g);
  331. lua_rawseti(L, -2, 2);
  332. lua_pushinteger(L, bl->bg_color.b);
  333. lua_rawseti(L, -2, 3);
  334. lua_pushinteger(L, bl->bg_color.alpha);
  335. lua_rawseti(L, -2, 4);
  336. lua_settable(L, -3);
  337. }
  338. if (bl->font_mask) {
  339. lua_pushstring(L, "font_size");
  340. lua_pushinteger(L, bl->font_size);
  341. lua_settable(L, -3);
  342. }
  343. lua_pushstring(L, "visible");
  344. lua_pushboolean(L, bl->is_visible());
  345. lua_settable(L, -3);
  346. lua_pushstring(L, "transparent");
  347. lua_pushboolean(L, bl->is_transparent());
  348. lua_settable(L, -3);
  349. }
  350. static int
  351. lua_html_foreach_tag(lua_State *L)
  352. {
  353. LUA_TRACE_POINT;
  354. auto *hc = lua_check_html(L, 1);
  355. const char *tagname;
  356. int id;
  357. auto any = false;
  358. ankerl::unordered_dense::set<int> tags;
  359. if (lua_type(L, 2) == LUA_TSTRING) {
  360. tagname = luaL_checkstring(L, 2);
  361. if (strcmp(tagname, "any") == 0) {
  362. any = true;
  363. }
  364. else {
  365. id = rspamd_html_tag_by_name(tagname);
  366. if (id == -1) {
  367. return luaL_error(L, "invalid tagname: %s", tagname);
  368. }
  369. tags.insert(id);
  370. }
  371. }
  372. else if (lua_type(L, 2) == LUA_TTABLE) {
  373. lua_pushvalue(L, 2);
  374. for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
  375. tagname = luaL_checkstring(L, -1);
  376. if (strcmp(tagname, "any") == 0) {
  377. any = TRUE;
  378. }
  379. else {
  380. id = rspamd_html_tag_by_name(tagname);
  381. if (id == -1) {
  382. return luaL_error(L, "invalid tagname: %s", tagname);
  383. }
  384. tags.insert(id);
  385. }
  386. }
  387. lua_pop(L, 1);
  388. }
  389. if (hc && (any || !tags.empty()) && lua_isfunction(L, 3)) {
  390. hc->traverse_all_tags([&](const rspamd::html::html_tag *tag) -> bool {
  391. if (tag && (any || tags.contains(tag->id))) {
  392. lua_pushcfunction(L, &rspamd_lua_traceback);
  393. auto err_idx = lua_gettop(L);
  394. lua_pushvalue(L, 3);
  395. auto *ltag = static_cast<lua_html_tag *>(lua_newuserdata(L, sizeof(lua_html_tag)));
  396. ltag->tag = tag;
  397. ltag->html = hc;
  398. auto ct = ltag->tag->get_content(hc);
  399. rspamd_lua_setclass(L, rspamd_html_tag_classname, -1);
  400. lua_pushinteger(L, ct.size());
  401. /* Leaf flag */
  402. if (tag->children.empty()) {
  403. lua_pushboolean(L, true);
  404. }
  405. else {
  406. lua_pushboolean(L, false);
  407. }
  408. if (lua_pcall(L, 3, 1, err_idx) != 0) {
  409. msg_err("error in foreach_tag callback: %s", lua_tostring(L, -1));
  410. lua_settop(L, err_idx - 1);
  411. return false;
  412. }
  413. if (lua_toboolean(L, -1)) {
  414. lua_settop(L, err_idx - 1);
  415. return false;
  416. }
  417. lua_settop(L, err_idx - 1);
  418. }
  419. return true;
  420. });
  421. }
  422. else {
  423. return luaL_error(L, "invalid arguments");
  424. }
  425. return 0;
  426. }
  427. static int
  428. lua_html_get_invisible(lua_State *L)
  429. {
  430. LUA_TRACE_POINT;
  431. auto *hc = lua_check_html(L, 1);
  432. if (hc != NULL) {
  433. lua_new_text(L, hc->invisible.c_str(), hc->invisible.size(), false);
  434. }
  435. else {
  436. lua_newtable(L);
  437. }
  438. return 1;
  439. }
  440. static int
  441. lua_html_tag_get_type(lua_State *L)
  442. {
  443. LUA_TRACE_POINT;
  444. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  445. const char *tagname;
  446. if (ltag != NULL) {
  447. tagname = rspamd_html_tag_by_id(ltag->tag->id);
  448. if (tagname) {
  449. lua_pushstring(L, tagname);
  450. }
  451. else {
  452. lua_pushnil(L);
  453. }
  454. }
  455. else {
  456. return luaL_error(L, "invalid arguments");
  457. }
  458. return 1;
  459. }
  460. static int
  461. lua_html_tag_get_parent(lua_State *L)
  462. {
  463. LUA_TRACE_POINT;
  464. struct lua_html_tag *ltag = lua_check_html_tag(L, 1), *ptag;
  465. if (ltag != NULL) {
  466. auto *parent = ltag->tag->parent;
  467. if (parent) {
  468. ptag = static_cast<lua_html_tag *>(lua_newuserdata(L, sizeof(*ptag)));
  469. ptag->tag = static_cast<rspamd::html::html_tag *>(parent);
  470. ptag->html = ltag->html;
  471. rspamd_lua_setclass(L, rspamd_html_tag_classname, -1);
  472. }
  473. else {
  474. lua_pushnil(L);
  475. }
  476. }
  477. else {
  478. return luaL_error(L, "invalid arguments");
  479. }
  480. return 1;
  481. }
  482. static int
  483. lua_html_tag_get_flags(lua_State *L)
  484. {
  485. LUA_TRACE_POINT;
  486. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  487. int i = 1;
  488. if (ltag && ltag->tag) {
  489. /* Push flags */
  490. lua_createtable(L, 4, 0);
  491. if (ltag->tag->flags & FL_HREF) {
  492. lua_pushstring(L, "href");
  493. lua_rawseti(L, -2, i++);
  494. }
  495. if (ltag->tag->flags & FL_CLOSED) {
  496. lua_pushstring(L, "closed");
  497. lua_rawseti(L, -2, i++);
  498. }
  499. if (ltag->tag->flags & FL_BROKEN) {
  500. lua_pushstring(L, "broken");
  501. lua_rawseti(L, -2, i++);
  502. }
  503. if (ltag->tag->flags & FL_XML) {
  504. lua_pushstring(L, "xml");
  505. lua_rawseti(L, -2, i++);
  506. }
  507. if (ltag->tag->flags & RSPAMD_HTML_FLAG_UNBALANCED) {
  508. lua_pushstring(L, "unbalanced");
  509. lua_rawseti(L, -2, i++);
  510. }
  511. }
  512. else {
  513. return luaL_error(L, "invalid arguments");
  514. }
  515. return 1;
  516. }
  517. static int
  518. lua_html_tag_get_content(lua_State *L)
  519. {
  520. LUA_TRACE_POINT;
  521. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  522. struct rspamd_lua_text *t;
  523. if (ltag) {
  524. if (ltag->html) {
  525. auto ct = ltag->tag->get_content(ltag->html);
  526. if (ct.size() > 0) {
  527. t = static_cast<rspamd_lua_text *>(lua_newuserdata(L, sizeof(*t)));
  528. rspamd_lua_setclass(L, rspamd_text_classname, -1);
  529. t->start = ct.data();
  530. t->len = ct.size();
  531. t->flags = 0;
  532. }
  533. else {
  534. lua_pushnil(L);
  535. }
  536. }
  537. else {
  538. lua_pushnil(L);
  539. }
  540. }
  541. else {
  542. return luaL_error(L, "invalid arguments");
  543. }
  544. return 1;
  545. }
  546. static int
  547. lua_html_tag_get_content_length(lua_State *L)
  548. {
  549. LUA_TRACE_POINT;
  550. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  551. if (ltag) {
  552. if (ltag->html) {
  553. auto ct = ltag->tag->get_content(ltag->html);
  554. lua_pushinteger(L, ct.size());
  555. }
  556. else {
  557. lua_pushinteger(L, ltag->tag->get_content_length());
  558. }
  559. }
  560. else {
  561. return luaL_error(L, "invalid arguments");
  562. }
  563. return 1;
  564. }
  565. static int
  566. lua_html_tag_get_extra(lua_State *L)
  567. {
  568. LUA_TRACE_POINT;
  569. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  570. struct html_image *img;
  571. if (ltag) {
  572. if (!std::holds_alternative<std::monostate>(ltag->tag->extra)) {
  573. if (std::holds_alternative<struct html_image *>(ltag->tag->extra)) {
  574. img = std::get<struct html_image *>(ltag->tag->extra);
  575. lua_html_push_image(L, img);
  576. }
  577. else if (std::holds_alternative<struct rspamd_url *>(ltag->tag->extra)) {
  578. /* For A that's URL */
  579. auto *lua_url = static_cast<rspamd_lua_url *>(lua_newuserdata(L, sizeof(rspamd_lua_url)));
  580. lua_url->url = std::get<struct rspamd_url *>(ltag->tag->extra);
  581. rspamd_lua_setclass(L, rspamd_url_classname, -1);
  582. }
  583. else {
  584. /* Unknown extra ? */
  585. lua_pushnil(L);
  586. }
  587. }
  588. else {
  589. lua_pushnil(L);
  590. }
  591. }
  592. else {
  593. return luaL_error(L, "invalid arguments");
  594. }
  595. return 1;
  596. }
  597. static int
  598. lua_html_tag_get_style(lua_State *L)
  599. {
  600. LUA_TRACE_POINT;
  601. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  602. if (ltag) {
  603. if (ltag->tag->block) {
  604. lua_html_push_block(L, ltag->tag->block);
  605. }
  606. }
  607. else {
  608. return luaL_error(L, "invalid arguments");
  609. }
  610. return 1;
  611. }
  612. static int
  613. lua_html_tag_get_attribute(lua_State *L)
  614. {
  615. LUA_TRACE_POINT;
  616. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  617. gsize slen;
  618. const char *attr_name = luaL_checklstring(L, 2, &slen);
  619. if (ltag && attr_name) {
  620. auto maybe_attr = ltag->tag->find_component(
  621. rspamd::html::html_component_from_string({attr_name, slen}));
  622. if (maybe_attr) {
  623. lua_pushlstring(L, maybe_attr->data(), maybe_attr->size());
  624. }
  625. else {
  626. lua_pushnil(L);
  627. }
  628. }
  629. else {
  630. return luaL_error(L, "invalid arguments");
  631. }
  632. return 1;
  633. }
  634. void luaopen_html(lua_State *L)
  635. {
  636. rspamd_lua_new_class(L, rspamd_html_classname, htmllib_m);
  637. lua_pop(L, 1);
  638. rspamd_lua_new_class(L, rspamd_html_tag_classname, taglib_m);
  639. lua_pop(L, 1);
  640. }