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