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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  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/robin-hood/robin_hood.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. /***
  113. * @method html_tag:get_type()
  114. * Returns string representation of HTML type for a tag
  115. * @return {string} type of tag
  116. */
  117. LUA_FUNCTION_DEF (html_tag, get_type);
  118. /***
  119. * @method html_tag:get_extra()
  120. * Returns extra data associated with the tag
  121. * @return {url|image|nil} extra data associated with the tag
  122. */
  123. LUA_FUNCTION_DEF (html_tag, get_extra);
  124. /***
  125. * @method html_tag:get_parent()
  126. * Returns parent node for a specified tag
  127. * @return {html_tag} parent object for a specified tag
  128. */
  129. LUA_FUNCTION_DEF (html_tag, get_parent);
  130. /***
  131. * @method html_tag:get_flags()
  132. * Returns flags a specified tag:
  133. *
  134. * - `closed`: tag is properly closed
  135. * - `closing`: tag is a closing tag
  136. * - `broken`: tag is somehow broken
  137. * - `unbalanced`: tag is unbalanced
  138. * - `xml`: tag is xml tag
  139. * @return {table} table of flags
  140. */
  141. LUA_FUNCTION_DEF (html_tag, get_flags);
  142. /***
  143. * @method html_tag:get_content()
  144. * Returns content of tag (approximate for some cases)
  145. * @return {rspamd_text} rspamd text with tag's content
  146. */
  147. LUA_FUNCTION_DEF (html_tag, get_content);
  148. /***
  149. * @method html_tag:get_content_length()
  150. * Returns length of a tag's content
  151. * @return {number} size of content enclosed within a tag
  152. */
  153. LUA_FUNCTION_DEF (html_tag, get_content_length);
  154. /***
  155. * @method html_tag:get_style()
  156. * Returns style calculated for the element
  157. * @return {table} table associated with the style
  158. */
  159. LUA_FUNCTION_DEF (html_tag, get_style);
  160. /***
  161. * @method html_tag:get_style()
  162. * Returns style calculated for the element
  163. * @return {table} table associated with the style
  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. };
  178. static struct rspamd::html::html_content *
  179. lua_check_html (lua_State * L, gint pos)
  180. {
  181. void *ud = rspamd_lua_check_udata (L, pos, "rspamd{html}");
  182. luaL_argcheck (L, ud != NULL, pos, "'html' expected");
  183. return ud ? *((struct rspamd::html::html_content **)ud) : NULL;
  184. }
  185. struct lua_html_tag {
  186. rspamd::html::html_content *html;
  187. const rspamd::html::html_tag *tag;
  188. };
  189. static struct lua_html_tag *
  190. lua_check_html_tag (lua_State * L, gint pos)
  191. {
  192. void *ud = rspamd_lua_check_udata (L, pos, "rspamd{html_tag}");
  193. luaL_argcheck (L, ud != NULL, pos, "'html_tag' expected");
  194. return ud ? ((struct lua_html_tag *)ud) : NULL;
  195. }
  196. static gint
  197. lua_html_has_tag (lua_State *L)
  198. {
  199. LUA_TRACE_POINT;
  200. auto *hc = lua_check_html (L, 1);
  201. const gchar *tagname = luaL_checkstring (L, 2);
  202. gboolean ret = FALSE;
  203. if (hc && tagname) {
  204. if (rspamd_html_tag_seen (hc, tagname)) {
  205. ret = TRUE;
  206. }
  207. }
  208. lua_pushboolean (L, ret);
  209. return 1;
  210. }
  211. constexpr const auto prop_map = frozen::make_unordered_map<frozen::string, int>({
  212. {"no_html", RSPAMD_HTML_FLAG_BAD_START},
  213. {"bad_start", RSPAMD_HTML_FLAG_BAD_START},
  214. {"bad_element", RSPAMD_HTML_FLAG_BAD_ELEMENTS},
  215. {"bad_elements", RSPAMD_HTML_FLAG_BAD_ELEMENTS},
  216. {"xml", RSPAMD_HTML_FLAG_XML},
  217. {"unknown_element", RSPAMD_HTML_FLAG_UNKNOWN_ELEMENTS},
  218. {"unknown_elements", RSPAMD_HTML_FLAG_UNKNOWN_ELEMENTS},
  219. {"duplicate_element", RSPAMD_HTML_FLAG_DUPLICATE_ELEMENTS},
  220. {"duplicate_elements", RSPAMD_HTML_FLAG_DUPLICATE_ELEMENTS},
  221. {"unbalanced", RSPAMD_HTML_FLAG_UNBALANCED},
  222. {"data_urls", RSPAMD_HTML_FLAG_HAS_DATA_URLS},
  223. });
  224. static gint
  225. lua_html_has_property (lua_State *L)
  226. {
  227. LUA_TRACE_POINT;
  228. auto *hc = lua_check_html (L, 1);
  229. const gchar *propname = luaL_checkstring (L, 2);
  230. gboolean ret = FALSE;
  231. if (hc && propname) {
  232. auto found_prop = prop_map.find(frozen::string(propname));
  233. if (found_prop != prop_map.end()) {
  234. ret = hc->flags & found_prop->second;
  235. }
  236. }
  237. lua_pushboolean (L, ret);
  238. return 1;
  239. }
  240. static void
  241. lua_html_push_image (lua_State *L, const struct html_image *img)
  242. {
  243. LUA_TRACE_POINT;
  244. struct lua_html_tag *ltag;
  245. struct rspamd_url **purl;
  246. lua_createtable (L, 0, 7);
  247. if (img->src) {
  248. lua_pushstring (L, "src");
  249. if (img->flags & RSPAMD_HTML_FLAG_IMAGE_DATA) {
  250. struct rspamd_lua_text *t;
  251. t = static_cast<rspamd_lua_text *>(lua_newuserdata(L, sizeof(*t)));
  252. t->start = img->src;
  253. t->len = strlen (img->src);
  254. t->flags = 0;
  255. rspamd_lua_setclass (L, "rspamd{text}", -1);
  256. }
  257. else {
  258. lua_pushstring (L, img->src);
  259. }
  260. lua_settable (L, -3);
  261. }
  262. if (img->url) {
  263. lua_pushstring (L, "url");
  264. purl = static_cast<rspamd_url **>(lua_newuserdata(L, sizeof(gpointer)));
  265. *purl = img->url;
  266. rspamd_lua_setclass (L, "rspamd{url}", -1);
  267. lua_settable (L, -3);
  268. }
  269. if (img->tag) {
  270. lua_pushstring (L, "tag");
  271. ltag = static_cast<lua_html_tag *>(lua_newuserdata(L, sizeof(struct lua_html_tag)));
  272. ltag->tag = static_cast<rspamd::html::html_tag *>(img->tag);
  273. ltag->html = NULL;
  274. rspamd_lua_setclass (L, "rspamd{html_tag}", -1);
  275. lua_settable (L, -3);
  276. }
  277. lua_pushstring (L, "height");
  278. lua_pushinteger (L, img->height);
  279. lua_settable (L, -3);
  280. lua_pushstring (L, "width");
  281. lua_pushinteger (L, img->width);
  282. lua_settable (L, -3);
  283. lua_pushstring (L, "embedded");
  284. lua_pushboolean (L, img->flags & RSPAMD_HTML_FLAG_IMAGE_EMBEDDED);
  285. lua_settable (L, -3);
  286. lua_pushstring (L, "data");
  287. lua_pushboolean (L, img->flags & RSPAMD_HTML_FLAG_IMAGE_DATA);
  288. lua_settable (L, -3);
  289. }
  290. static gint
  291. lua_html_get_images (lua_State *L)
  292. {
  293. LUA_TRACE_POINT;
  294. auto *hc = lua_check_html (L, 1);
  295. guint i = 1;
  296. if (hc != NULL) {
  297. lua_createtable (L, hc->images.size(), 0);
  298. for (const auto *img : hc->images) {
  299. lua_html_push_image (L, img);
  300. lua_rawseti (L, -2, i++);
  301. }
  302. }
  303. else {
  304. lua_newtable (L);
  305. }
  306. return 1;
  307. }
  308. static void
  309. lua_html_push_block (lua_State *L, const struct rspamd::html::html_block *bl)
  310. {
  311. LUA_TRACE_POINT;
  312. lua_createtable (L, 0, 6);
  313. if (bl->fg_color_mask) {
  314. lua_pushstring (L, "color");
  315. lua_createtable (L, 4, 0);
  316. lua_pushinteger (L, bl->fg_color.r);
  317. lua_rawseti (L, -2, 1);
  318. lua_pushinteger (L, bl->fg_color.g);
  319. lua_rawseti (L, -2, 2);
  320. lua_pushinteger (L, bl->fg_color.b);
  321. lua_rawseti (L, -2, 3);
  322. lua_pushinteger (L, bl->fg_color.alpha);
  323. lua_rawseti (L, -2, 4);
  324. lua_settable (L, -3);
  325. }
  326. if (bl->bg_color_mask) {
  327. lua_pushstring (L, "bgcolor");
  328. lua_createtable (L, 4, 0);
  329. lua_pushinteger (L, bl->bg_color.r);
  330. lua_rawseti (L, -2, 1);
  331. lua_pushinteger (L, bl->bg_color.g);
  332. lua_rawseti (L, -2, 2);
  333. lua_pushinteger (L, bl->bg_color.b);
  334. lua_rawseti (L, -2, 3);
  335. lua_pushinteger (L, bl->bg_color.alpha);
  336. lua_rawseti (L, -2, 4);
  337. lua_settable (L, -3);
  338. }
  339. if (bl->font_mask) {
  340. lua_pushstring(L, "font_size");
  341. lua_pushinteger(L, bl->font_size);
  342. lua_settable(L, -3);
  343. }
  344. lua_pushstring(L, "visible");
  345. lua_pushboolean(L, bl->is_visible());
  346. lua_settable(L, -3);
  347. lua_pushstring(L, "transparent");
  348. lua_pushboolean(L, bl->is_transparent());
  349. lua_settable(L, -3);
  350. }
  351. static gint
  352. lua_html_foreach_tag (lua_State *L)
  353. {
  354. LUA_TRACE_POINT;
  355. auto *hc = lua_check_html (L, 1);
  356. const gchar *tagname;
  357. gint id;
  358. auto any = false;
  359. robin_hood::unordered_flat_set<int> tags;
  360. if (lua_type (L, 2) == LUA_TSTRING) {
  361. tagname = luaL_checkstring (L, 2);
  362. if (strcmp (tagname, "any") == 0) {
  363. any = true;
  364. }
  365. else {
  366. id = rspamd_html_tag_by_name(tagname);
  367. if (id == -1) {
  368. return luaL_error (L, "invalid tagname: %s", tagname);
  369. }
  370. tags.insert(id);
  371. }
  372. }
  373. else if (lua_type (L, 2) == LUA_TTABLE) {
  374. lua_pushvalue (L, 2);
  375. for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 1)) {
  376. tagname = luaL_checkstring (L, -1);
  377. if (strcmp (tagname, "any") == 0) {
  378. any = TRUE;
  379. }
  380. else {
  381. id = rspamd_html_tag_by_name (tagname);
  382. if (id == -1) {
  383. return luaL_error (L, "invalid tagname: %s", tagname);
  384. }
  385. tags.insert(id);
  386. }
  387. }
  388. lua_pop (L, 1);
  389. }
  390. if (hc && (any || !tags.empty()) && lua_isfunction (L, 3)) {
  391. hc->traverse_all_tags([&](const rspamd::html::html_tag *tag) -> bool {
  392. if (tag && (any || tags.contains(tag->id))) {
  393. lua_pushcfunction (L, &rspamd_lua_traceback);
  394. auto err_idx = lua_gettop(L);
  395. lua_pushvalue(L, 3);
  396. auto *ltag = static_cast<lua_html_tag *>(lua_newuserdata(L, sizeof(lua_html_tag)));
  397. ltag->tag = tag;
  398. ltag->html = hc;
  399. auto ct = ltag->tag->get_content(hc);
  400. rspamd_lua_setclass (L, "rspamd{html_tag}", -1);
  401. lua_pushinteger (L, ct.size());
  402. /* Leaf flag */
  403. if (tag->children.empty()) {
  404. lua_pushboolean (L, true);
  405. }
  406. else {
  407. lua_pushboolean (L, false);
  408. }
  409. if (lua_pcall (L, 3, 1, err_idx) != 0) {
  410. msg_err ("error in foreach_tag callback: %s", lua_tostring (L, -1));
  411. lua_settop(L, err_idx - 1);
  412. return false;
  413. }
  414. if (lua_toboolean (L, -1)) {
  415. lua_settop(L, err_idx - 1);
  416. return false;
  417. }
  418. lua_settop(L, err_idx - 1);
  419. }
  420. return true;
  421. });
  422. }
  423. else {
  424. return luaL_error (L, "invalid arguments");
  425. }
  426. return 0;
  427. }
  428. static gint
  429. lua_html_get_invisible (lua_State *L)
  430. {
  431. LUA_TRACE_POINT;
  432. auto *hc = lua_check_html (L, 1);
  433. if (hc != NULL) {
  434. lua_new_text (L, hc->invisible.c_str(), hc->invisible.size(), false);
  435. }
  436. else {
  437. lua_newtable (L);
  438. }
  439. return 1;
  440. }
  441. static gint
  442. lua_html_tag_get_type (lua_State *L)
  443. {
  444. LUA_TRACE_POINT;
  445. struct lua_html_tag *ltag = lua_check_html_tag (L, 1);
  446. const gchar *tagname;
  447. if (ltag != NULL) {
  448. tagname = rspamd_html_tag_by_id (ltag->tag->id);
  449. if (tagname) {
  450. lua_pushstring (L, tagname);
  451. }
  452. else {
  453. lua_pushnil (L);
  454. }
  455. }
  456. else {
  457. return luaL_error (L, "invalid arguments");
  458. }
  459. return 1;
  460. }
  461. static gint
  462. lua_html_tag_get_parent (lua_State *L)
  463. {
  464. LUA_TRACE_POINT;
  465. struct lua_html_tag *ltag = lua_check_html_tag (L, 1), *ptag;
  466. if (ltag != NULL) {
  467. auto *parent = ltag->tag->parent;
  468. if (parent) {
  469. ptag = static_cast<lua_html_tag *>(lua_newuserdata(L, sizeof(*ptag)));
  470. ptag->tag = static_cast<rspamd::html::html_tag *>(parent);
  471. ptag->html = ltag->html;
  472. rspamd_lua_setclass (L, "rspamd{html_tag}", -1);
  473. }
  474. else {
  475. lua_pushnil (L);
  476. }
  477. }
  478. else {
  479. return luaL_error (L, "invalid arguments");
  480. }
  481. return 1;
  482. }
  483. static gint
  484. lua_html_tag_get_flags (lua_State *L)
  485. {
  486. LUA_TRACE_POINT;
  487. struct lua_html_tag *ltag = lua_check_html_tag (L, 1);
  488. gint i = 1;
  489. if (ltag->tag) {
  490. /* Push flags */
  491. lua_createtable (L, 4, 0);
  492. if (ltag->tag->flags & FL_HREF) {
  493. lua_pushstring (L, "href");
  494. lua_rawseti (L, -2, i++);
  495. }
  496. if (ltag->tag->flags & FL_CLOSED) {
  497. lua_pushstring (L, "closed");
  498. lua_rawseti (L, -2, i++);
  499. }
  500. if (ltag->tag->flags & FL_BROKEN) {
  501. lua_pushstring (L, "broken");
  502. lua_rawseti (L, -2, i++);
  503. }
  504. if (ltag->tag->flags & FL_XML) {
  505. lua_pushstring (L, "xml");
  506. lua_rawseti (L, -2, i++);
  507. }
  508. if (ltag->tag->flags & RSPAMD_HTML_FLAG_UNBALANCED) {
  509. lua_pushstring (L, "unbalanced");
  510. lua_rawseti (L, -2, i++);
  511. }
  512. }
  513. else {
  514. return luaL_error (L, "invalid arguments");
  515. }
  516. return 1;
  517. }
  518. static gint
  519. lua_html_tag_get_content (lua_State *L)
  520. {
  521. LUA_TRACE_POINT;
  522. struct lua_html_tag *ltag = lua_check_html_tag (L, 1);
  523. struct rspamd_lua_text *t;
  524. if (ltag) {
  525. if (ltag->html) {
  526. auto ct = ltag->tag->get_content(ltag->html);
  527. if (ct.size() > 0) {
  528. t = static_cast<rspamd_lua_text *>(lua_newuserdata(L, sizeof(*t)));
  529. rspamd_lua_setclass(L, "rspamd{text}", -1);
  530. t->start = ct.data();
  531. t->len = ct.size();
  532. t->flags = 0;
  533. }
  534. else {
  535. lua_pushnil (L);
  536. }
  537. }
  538. else {
  539. lua_pushnil (L);
  540. }
  541. }
  542. else {
  543. return luaL_error (L, "invalid arguments");
  544. }
  545. return 1;
  546. }
  547. static gint
  548. lua_html_tag_get_content_length (lua_State *L)
  549. {
  550. LUA_TRACE_POINT;
  551. struct lua_html_tag *ltag = lua_check_html_tag (L, 1);
  552. if (ltag) {
  553. if (ltag->html) {
  554. auto ct = ltag->tag->get_content(ltag->html);
  555. lua_pushinteger (L, ct.size());
  556. }
  557. else {
  558. lua_pushinteger (L, ltag->tag->get_content_length());
  559. }
  560. }
  561. else {
  562. return luaL_error (L, "invalid arguments");
  563. }
  564. return 1;
  565. }
  566. static gint
  567. lua_html_tag_get_extra (lua_State *L)
  568. {
  569. LUA_TRACE_POINT;
  570. struct lua_html_tag *ltag = lua_check_html_tag (L, 1);
  571. struct html_image *img;
  572. if (ltag) {
  573. if (!std::holds_alternative<std::monostate>(ltag->tag->extra)) {
  574. if (std::holds_alternative<struct html_image *>(ltag->tag->extra)) {
  575. img = std::get<struct html_image *>(ltag->tag->extra);
  576. lua_html_push_image (L, img);
  577. }
  578. else if (std::holds_alternative<struct rspamd_url *>(ltag->tag->extra)) {
  579. /* For A that's URL */
  580. auto *lua_url = static_cast<rspamd_lua_url *>(lua_newuserdata(L, sizeof(rspamd_lua_url)));
  581. lua_url->url = std::get<struct rspamd_url *>(ltag->tag->extra);
  582. rspamd_lua_setclass (L, "rspamd{url}", -1);
  583. }
  584. else {
  585. /* Unknown extra ? */
  586. lua_pushnil (L);
  587. }
  588. }
  589. else {
  590. lua_pushnil (L);
  591. }
  592. }
  593. else {
  594. return luaL_error (L, "invalid arguments");
  595. }
  596. return 1;
  597. }
  598. static gint
  599. lua_html_tag_get_style (lua_State *L)
  600. {
  601. LUA_TRACE_POINT;
  602. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  603. if (ltag) {
  604. if (ltag->tag->block) {
  605. lua_html_push_block(L, ltag->tag->block);
  606. }
  607. }
  608. else {
  609. return luaL_error(L, "invalid arguments");
  610. }
  611. return 1;
  612. }
  613. static gint
  614. lua_html_tag_get_attribute (lua_State *L)
  615. {
  616. LUA_TRACE_POINT;
  617. struct lua_html_tag *ltag = lua_check_html_tag(L, 1);
  618. gsize slen;
  619. const gchar *attr_name = luaL_checklstring(L, 2, &slen);
  620. if (ltag && attr_name) {
  621. auto maybe_attr = ltag->tag->find_component(
  622. rspamd::html::html_component_from_string({attr_name, slen}));
  623. if (maybe_attr) {
  624. lua_pushlstring(L, maybe_attr->data(), maybe_attr->size());
  625. }
  626. else {
  627. lua_pushnil(L);
  628. }
  629. }
  630. else {
  631. return luaL_error(L, "invalid arguments");
  632. }
  633. return 1;
  634. }
  635. void
  636. luaopen_html (lua_State * L)
  637. {
  638. rspamd_lua_new_class (L, "rspamd{html}", htmllib_m);
  639. lua_pop (L, 1);
  640. rspamd_lua_new_class (L, "rspamd{html_tag}", taglib_m);
  641. lua_pop (L, 1);
  642. }