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