@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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; | |||
}; | |||
@@ -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") { |
@@ -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; |
@@ -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); |
@@ -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; | |||
} |