]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Project] Css: Add preliminary stylesheet support
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 23 Mar 2021 15:14:07 +0000 (15:14 +0000)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 23 Mar 2021 15:14:07 +0000 (15:14 +0000)
src/libserver/css/css.cxx
src/libserver/css/css.hxx
src/libserver/css/css_parser.cxx
src/libserver/css/css_parser.hxx
src/libserver/css/css_rule.cxx
src/libserver/css/css_rule.hxx

index 4587085a8659f18dead1df57a7fd8a2a1e8846e0..bd26cee1ebe0fcf104c79de056f263a76eb4b791 100644 (file)
@@ -16,7 +16,7 @@
 
 #include "css.h"
 #include "css.hxx"
-#include "css_style.hxx"
+#include "contrib/robin-hood/robin_hood.h"
 #include "css_parser.hxx"
 #define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
 #define DOCTEST_CONFIG_IMPLEMENT
@@ -28,8 +28,6 @@ rspamd_css_parse_style (rspamd_mempool_t *pool, const guchar *begin, gsize len,
 {
        auto parse_res = rspamd::css::parse_css(pool, {(const char* )begin, len});
 
-#if 0
-       /* Return once semantical parsing is done */
        if (parse_res.has_value()) {
                return reinterpret_cast<rspamd_css>(parse_res.value().release());
        }
@@ -39,9 +37,6 @@ rspamd_css_parse_style (rspamd_mempool_t *pool, const guchar *begin, gsize len,
                                "parse error");
                return nullptr;
        }
-#else
-       return nullptr;
-#endif
 }
 
 namespace rspamd::css {
@@ -49,10 +44,73 @@ namespace rspamd::css {
 INIT_LOG_MODULE_PUBLIC(css);
 
 class css_style_sheet::impl {
-
+public:
+       using selector_ptr = std::unique_ptr<css_selector>;
+       using selectors_hash = robin_hood::unordered_flat_map<selector_ptr, css_declarations_block_ptr>;
+       using universal_selector_t = std::pair<selector_ptr, css_declarations_block_ptr>;
+       selectors_hash tags_selector;
+       selectors_hash class_selectors;
+       selectors_hash id_selectors;
+       std::optional<universal_selector_t> universal_selector;
 };
 
-css_style_sheet::css_style_sheet () : pimpl(new impl) {}
-css_style_sheet::~css_style_sheet () {}
+css_style_sheet::css_style_sheet(rspamd_mempool_t *pool)
+               :  pool(pool), pimpl(new impl) {}
+css_style_sheet::~css_style_sheet() {}
+
+auto
+css_style_sheet::add_selector_rule(std::unique_ptr<css_selector> &&selector,
+                                                                       css_declarations_block_ptr decls) -> void
+{
+       impl::selectors_hash *target_hash = nullptr;
+
+       switch(selector->type) {
+       case css_selector::selector_type::SELECTOR_ALL:
+               if (pimpl->universal_selector) {
+                       /* Another universal selector */
+                       msg_debug_css("redefined universal selector, merging rules");
+                       pimpl->universal_selector->second->merge_block(*decls);
+               }
+               else {
+                       msg_debug_css("added universal selector");
+                       pimpl->universal_selector = std::make_pair(std::move(selector),
+                                       decls);
+               }
+               break;
+       case css_selector::selector_type::SELECTOR_CLASS:
+               target_hash = &pimpl->class_selectors;
+               break;
+       case css_selector::selector_type::SELECTOR_ID:
+               target_hash = &pimpl->id_selectors;
+               break;
+       case css_selector::selector_type::SELECTOR_ELEMENT:
+               target_hash = &pimpl->tags_selector;
+               break;
+       }
+
+       if (target_hash) {
+               auto found_it = target_hash->find(selector);
+
+               if (found_it == target_hash->end()) {
+                       /* Easy case, new element */
+                       target_hash->insert({std::move(selector), decls});
+               }
+               else {
+                       /* The problem with merging is actually in how to handle selectors chains
+                        * For example, we have 2 selectors:
+                        * 1. class id tag -> meaning that we first match class, then we ensure that
+                        * id is also the same and finally we check the tag
+                        * 2. tag class id -> it means that we check first tag, then class and then id
+                        * So we have somehow equal path in the xpath terms.
+                        * I suppose now, that we merely check parent stuff and handle duplicates
+                        * merging when finally resolving paths.
+                        */
+                       auto sel_str = selector->to_string().value_or("unknown");
+                       msg_debug_css("found duplicate selector: %*s", (int)sel_str.size(),
+                                       sel_str.data());
+                       found_it->second->merge_block(*decls);
+               }
+       }
+}
 
 }
\ No newline at end of file
index 9ed323ec34252d08749947ce1691be6324504ca3..739ad3251028e2e5c824ae45c0420ea3166cffdd 100644 (file)
@@ -40,10 +40,13 @@ extern unsigned int rspamd_css_log_id;
 
 class css_style_sheet {
 public:
-       css_style_sheet();
+       css_style_sheet(rspamd_mempool_t *pool);
        ~css_style_sheet(); /* must be declared separately due to pimpl */
+       auto add_selector_rule(std::unique_ptr<css_selector> &&selector,
+                                                  css_declarations_block_ptr decls) -> void;
 private:
        class impl;
+       rspamd_mempool_t *pool;
        std::unique_ptr<impl> pimpl;
 };
 
index 415039e19c7a6ebae28b03e91ab17e9ded76ad61..f80386fc28532a9e8ccdd520df8484f98a4b24c2 100644 (file)
@@ -559,7 +559,7 @@ bool css_parser::consume_input(const std::string_view &sv)
                return false;
        }
 
-       style_object = std::make_unique<css_style_sheet>();
+       style_object = std::make_unique<css_style_sheet>(pool);
 
        for (auto &&rule : rules) {
                /*
@@ -627,6 +627,10 @@ bool css_parser::consume_input(const std::string_view &sv)
                                                msg_debug_css("processed %d rules",
                                                                (int)declarations_vec->get_rules().size());
 
+                                               for (auto &&selector : selectors_vec) {
+                                                       style_object->add_selector_rule(std::move(selector),
+                                                                          declarations_vec);
+                                               }
                                        }
                                }
                        }
@@ -677,15 +681,16 @@ get_selectors_parser_functor(rspamd_mempool_t *pool,
  * Wrapper for the parser
  */
 auto parse_css(rspamd_mempool_t *pool, const std::string_view &st) ->
-       bool
+               tl::expected<std::unique_ptr<css_style_sheet>, css_parse_error>
 {
        css_parser parser(pool);
 
        if (parser.consume_input(st)) {
-               return true;
+               return parser.get_object_maybe();
        }
 
-       return false;
+       return tl::make_unexpected(css_parse_error{css_parse_error_type::PARSE_ERROR_INVALID_SYNTAX,
+                                                                                       "cannot parse input"});
 }
 
 TEST_SUITE("css parser") {
index be788ea81bdaff0a63ee4078a76f77ca1cd18b6a..8450316181ff961559e24ae960dafb2c52a70872 100644 (file)
@@ -183,8 +183,9 @@ extern const css_consumed_block css_parser_eof_block;
 
 using blocks_gen_functor = std::function<const css_consumed_block &(void)>;
 
+class css_style_sheet;
 auto parse_css(rspamd_mempool_t *pool, const std::string_view &st) ->
-               bool;
+       tl::expected<std::unique_ptr<css_style_sheet>, css_parse_error>;
 
 auto get_selectors_parser_functor(rspamd_mempool_t *pool,
                                                                  const std::string_view &st) -> blocks_gen_functor;
index bd589da95bd271f2f662ad3e8aafdc08c06a438c..cf04eb689b805fdc599d439162fdbd32fe9882d8 100644 (file)
@@ -313,6 +313,38 @@ auto process_declaration_tokens(rspamd_mempool_t *pool,
        return ret; /* copy elision */
 }
 
+auto
+css_declarations_block::merge_block(const css_declarations_block &other, merge_type how)
+       -> void
+{
+       const auto &other_rules = other.get_rules();
+
+       for (const auto &rule : other_rules) {
+               auto &&found_it = rules.find(rule);
+
+               if (found_it != rules.end()) {
+                       /* Duplicate, need to merge */
+                       switch(how) {
+                       case merge_type::merge_override:
+                               /* Override */
+                               rules.insert(rule);
+                               break;
+                       case merge_type::merge_duplicate:
+                               /* Merge values */
+                               (*found_it)->merge_values(*rule);
+                               break;
+                       case merge_type::merge_parent:
+                               /* Do not merge parent rule if more specific local one is presented */
+                               break;
+                       }
+               }
+               else {
+                       /* New property, just insert */
+                       rules.insert(rule);
+               }
+       }
+}
+
 void css_rule::add_value(const css_value &value)
 {
        values.push_back(value);
index 05c3fd82daefad2e3831f7e9e13fe752e760d0d2..c7cbae45d378d95450e31ac5dc08e4bbd80109f8 100644 (file)
@@ -81,8 +81,16 @@ public:
        using rule_shared_ptr = std::shared_ptr<css_rule>;
        using rule_shared_hash = shared_ptr_hash<css_rule>;
        using rule_shared_eq = shared_ptr_equal<css_rule>;
+       enum class merge_type {
+               merge_duplicate,
+               merge_parent,
+               merge_override
+       };
+
        css_declarations_block() = default;
        auto add_rule(rule_shared_ptr &&rule) -> bool;
+       auto merge_block(const css_declarations_block &other,
+                                 merge_type how = merge_type::merge_duplicate) -> void;
        auto get_rules(void) const -> const auto & {
                return rules;
        }