diff options
Diffstat (limited to 'src/libserver/css/css_value.cxx')
-rw-r--r-- | src/libserver/css/css_value.cxx | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/src/libserver/css/css_value.cxx b/src/libserver/css/css_value.cxx new file mode 100644 index 0000000..2546e01 --- /dev/null +++ b/src/libserver/css/css_value.cxx @@ -0,0 +1,449 @@ +/*- + * Copyright 2021 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "css_value.hxx" +#include "css_colors_list.hxx" +#include "frozen/unordered_map.h" +#include "frozen/string.h" +#include "libutil/util.h" +#include "contrib/ankerl/unordered_dense.h" +#include "fmt/core.h" + +#define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#include "doctest/doctest.h" + +/* Helper for unit test stringification */ +namespace doctest { +template<> +struct StringMaker<rspamd::css::css_color> { + static String convert(const rspamd::css::css_color &value) + { + return fmt::format("r={};g={};b={};alpha={}", + value.r, value.g, value.b, value.alpha) + .c_str(); + } +}; + +}// namespace doctest + +namespace rspamd::css { + +auto css_value::maybe_color_from_string(const std::string_view &input) + -> std::optional<css_value> +{ + + if (input.size() > 1 && input.front() == '#') { + return css_value::maybe_color_from_hex(input.substr(1)); + } + else { + auto found_it = css_colors_map.find(input); + + if (found_it != css_colors_map.end()) { + return css_value{found_it->second}; + } + } + + return std::nullopt; +} + +constexpr static inline auto hexpair_decode(char c1, char c2) -> std::uint8_t +{ + std::uint8_t ret = 0; + + if (c1 >= '0' && c1 <= '9') ret = c1 - '0'; + else if (c1 >= 'A' && c1 <= 'F') + ret = c1 - 'A' + 10; + else if (c1 >= 'a' && c1 <= 'f') + ret = c1 - 'a' + 10; + + ret *= 16; + + if (c2 >= '0' && c2 <= '9') ret += c2 - '0'; + else if (c2 >= 'A' && c2 <= 'F') + ret += c2 - 'A' + 10; + else if (c2 >= 'a' && c2 <= 'f') + ret += c2 - 'a' + 10; + + return ret; +} + +auto css_value::maybe_color_from_hex(const std::string_view &input) + -> std::optional<css_value> +{ + if (input.length() == 6) { + /* Plain RGB */ + css_color col(hexpair_decode(input[0], input[1]), + hexpair_decode(input[2], input[3]), + hexpair_decode(input[4], input[5])); + return css_value(col); + } + else if (input.length() == 3) { + /* Rgb as 3 hex digests */ + css_color col(hexpair_decode(input[0], input[0]), + hexpair_decode(input[1], input[1]), + hexpair_decode(input[2], input[2])); + return css_value(col); + } + else if (input.length() == 8) { + /* RGBA */ + css_color col(hexpair_decode(input[0], input[1]), + hexpair_decode(input[2], input[3]), + hexpair_decode(input[4], input[5]), + hexpair_decode(input[6], input[7])); + return css_value(col); + } + + return std::nullopt; +} + +constexpr static inline auto rgb_color_component_convert(const css_parser_token &tok) + -> std::uint8_t +{ + std::uint8_t ret = 0; + + if (tok.type == css_parser_token::token_type::number_token) { + auto dbl = std::get<float>(tok.value); + + if (tok.flags & css_parser_token::number_percent) { + if (dbl > 100) { + dbl = 100; + } + else if (dbl < 0) { + dbl = 0; + } + ret = (std::uint8_t)(dbl / 100.0 * 255.0); + } + else { + if (dbl > 255) { + dbl = 255; + } + else if (dbl < 0) { + dbl = 0; + } + + ret = (std::uint8_t)(dbl); + } + } + + return ret; +} + +constexpr static inline auto alpha_component_convert(const css_parser_token &tok) + -> std::uint8_t +{ + double ret = 1.0; + + if (tok.type == css_parser_token::token_type::number_token) { + auto dbl = std::get<float>(tok.value); + + if (tok.flags & css_parser_token::number_percent) { + if (dbl > 100) { + dbl = 100; + } + else if (dbl < 0) { + dbl = 0; + } + ret = (dbl / 100.0); + } + else { + if (dbl > 1.0) { + dbl = 1.0; + } + else if (dbl < 0) { + dbl = 0; + } + + ret = dbl; + } + } + + return (std::uint8_t)(ret * 255.0); +} + +constexpr static inline auto h_component_convert(const css_parser_token &tok) + -> double +{ + double ret = 0.0; + + if (tok.type == css_parser_token::token_type::number_token) { + auto dbl = std::get<float>(tok.value); + + if (tok.flags & css_parser_token::number_percent) { + if (dbl > 100) { + dbl = 100; + } + else if (dbl < 0) { + dbl = 0; + } + ret = (dbl / 100.0); + } + else { + dbl = ((((int) dbl % 360) + 360) % 360); /* Deal with rotations */ + ret = dbl / 360.0; /* Normalize to 0..1 */ + } + } + + return ret; +} + +constexpr static inline auto sl_component_convert(const css_parser_token &tok) + -> double +{ + double ret = 0.0; + + if (tok.type == css_parser_token::token_type::number_token) { + ret = tok.get_normal_number_or_default(ret); + } + + return ret; +} + +static inline auto hsl_to_rgb(double h, double s, double l) + -> css_color +{ + css_color ret; + + constexpr auto hue2rgb = [](auto p, auto q, auto t) -> auto { + if (t < 0.0) { + t += 1.0; + } + if (t > 1.0) { + t -= 1.0; + } + if (t * 6. < 1.0) { + return p + (q - p) * 6.0 * t; + } + if (t * 2. < 1) { + return q; + } + if (t * 3. < 2.) { + return p + (q - p) * (2.0 / 3.0 - t) * 6.0; + } + return p; + }; + + if (s == 0) { + /* Achromatic */ + ret.r = l; + ret.g = l; + ret.b = l; + } + else { + auto q = l <= 0.5 ? l * (1.0 + s) : l + s - l * s; + auto p = 2.0 * l - q; + ret.r = (std::uint8_t)(hue2rgb(p, q, h + 1.0 / 3.0) * 255); + ret.g = (std::uint8_t)(hue2rgb(p, q, h) * 255); + ret.b = (std::uint8_t)(hue2rgb(p, q, h - 1.0 / 3.0) * 255); + } + + ret.alpha = 255; + + return ret; +} + +auto css_value::maybe_color_from_function(const css_consumed_block::css_function_block &func) + -> std::optional<css_value> +{ + + if (func.as_string() == "rgb" && func.args.size() == 3) { + css_color col{rgb_color_component_convert(func.args[0]->get_token_or_empty()), + rgb_color_component_convert(func.args[1]->get_token_or_empty()), + rgb_color_component_convert(func.args[2]->get_token_or_empty())}; + + return css_value(col); + } + else if (func.as_string() == "rgba" && func.args.size() == 4) { + css_color col{rgb_color_component_convert(func.args[0]->get_token_or_empty()), + rgb_color_component_convert(func.args[1]->get_token_or_empty()), + rgb_color_component_convert(func.args[2]->get_token_or_empty()), + alpha_component_convert(func.args[3]->get_token_or_empty())}; + + return css_value(col); + } + else if (func.as_string() == "hsl" && func.args.size() == 3) { + auto h = h_component_convert(func.args[0]->get_token_or_empty()); + auto s = sl_component_convert(func.args[1]->get_token_or_empty()); + auto l = sl_component_convert(func.args[2]->get_token_or_empty()); + + auto col = hsl_to_rgb(h, s, l); + + return css_value(col); + } + else if (func.as_string() == "hsla" && func.args.size() == 4) { + auto h = h_component_convert(func.args[0]->get_token_or_empty()); + auto s = sl_component_convert(func.args[1]->get_token_or_empty()); + auto l = sl_component_convert(func.args[2]->get_token_or_empty()); + + auto col = hsl_to_rgb(h, s, l); + col.alpha = alpha_component_convert(func.args[3]->get_token_or_empty()); + + return css_value(col); + } + + return std::nullopt; +} + +auto css_value::maybe_dimension_from_number(const css_parser_token &tok) + -> std::optional<css_value> +{ + if (std::holds_alternative<float>(tok.value)) { + auto dbl = std::get<float>(tok.value); + css_dimension dim; + + dim.dim = dbl; + + if (tok.flags & css_parser_token::number_percent) { + dim.is_percent = true; + } + else { + dim.is_percent = false; + } + + return css_value{dim}; + } + + return std::nullopt; +} + +constexpr const auto display_names_map = frozen::make_unordered_map<frozen::string, css_display_value>({ + {"hidden", css_display_value::DISPLAY_HIDDEN}, + {"none", css_display_value::DISPLAY_HIDDEN}, + {"inline", css_display_value::DISPLAY_INLINE}, + {"block", css_display_value::DISPLAY_BLOCK}, + {"content", css_display_value::DISPLAY_INLINE}, + {"flex", css_display_value::DISPLAY_BLOCK}, + {"grid", css_display_value::DISPLAY_BLOCK}, + {"inline-block", css_display_value::DISPLAY_INLINE}, + {"inline-flex", css_display_value::DISPLAY_INLINE}, + {"inline-grid", css_display_value::DISPLAY_INLINE}, + {"inline-table", css_display_value::DISPLAY_INLINE}, + {"list-item", css_display_value::DISPLAY_BLOCK}, + {"run-in", css_display_value::DISPLAY_INLINE}, + {"table", css_display_value::DISPLAY_BLOCK}, + {"table-caption", css_display_value::DISPLAY_TABLE_ROW}, + {"table-column-group", css_display_value::DISPLAY_TABLE_ROW}, + {"table-header-group", css_display_value::DISPLAY_TABLE_ROW}, + {"table-footer-group", css_display_value::DISPLAY_TABLE_ROW}, + {"table-row-group", css_display_value::DISPLAY_TABLE_ROW}, + {"table-cell", css_display_value::DISPLAY_TABLE_ROW}, + {"table-column", css_display_value::DISPLAY_TABLE_ROW}, + {"table-row", css_display_value::DISPLAY_TABLE_ROW}, + {"initial", css_display_value::DISPLAY_INLINE}, +}); + +auto css_value::maybe_display_from_string(const std::string_view &input) + -> std::optional<css_value> +{ + auto f = display_names_map.find(input); + + if (f != display_names_map.end()) { + return css_value{f->second}; + } + + return std::nullopt; +} + + +auto css_value::debug_str() const -> std::string +{ + std::string ret; + + std::visit([&](const auto &arg) { + using T = std::decay_t<decltype(arg)>; + + if constexpr (std::is_same_v<T, css_color>) { + ret += fmt::format("color: r={};g={};b={};alpha={}", + arg.r, arg.g, arg.b, arg.alpha); + } + else if constexpr (std::is_same_v<T, double>) { + ret += "size: " + std::to_string(arg); + } + else if constexpr (std::is_same_v<T, css_dimension>) { + ret += "dimension: " + std::to_string(arg.dim); + if (arg.is_percent) { + ret += "%"; + } + } + else if constexpr (std::is_same_v<T, css_display_value>) { + ret += "display: "; + switch (arg) { + case css_display_value::DISPLAY_HIDDEN: + ret += "hidden"; + break; + case css_display_value::DISPLAY_BLOCK: + ret += "block"; + break; + case css_display_value::DISPLAY_INLINE: + ret += "inline"; + break; + case css_display_value::DISPLAY_TABLE_ROW: + ret += "table_row"; + break; + } + } + else if constexpr (std::is_integral_v<T>) { + ret += "integral: " + std::to_string(static_cast<int>(arg)); + } + else { + ret += "nyi"; + } + }, + value); + + return ret; +} + +TEST_SUITE("css"){ + TEST_CASE("css hex colors"){ + const std::pair<const char *, css_color> hex_tests[] = { + {"000", css_color(0, 0, 0)}, + {"000000", css_color(0, 0, 0)}, + {"f00", css_color(255, 0, 0)}, + {"FEDCBA", css_color(254, 220, 186)}, + {"234", css_color(34, 51, 68)}, + }; + +for (const auto &p: hex_tests) { + SUBCASE((std::string("parse hex color: ") + p.first).c_str()) + { + auto col_parsed = css_value::maybe_color_from_hex(p.first); + //CHECK_UNARY(col_parsed); + //CHECK_UNARY(col_parsed.value().to_color()); + auto final_col = col_parsed.value().to_color().value(); + CHECK(final_col == p.second); + } +} +}// namespace rspamd::css +TEST_CASE("css colors strings") +{ + auto passed = 0; + for (const auto &p: css_colors_map) { + /* Match some of the colors selected randomly */ + if (rspamd_random_double_fast() > 0.9) { + auto col_parsed = css_value::maybe_color_from_string(p.first); + auto final_col = col_parsed.value().to_color().value(); + CHECK_MESSAGE(final_col == p.second, p.first.data()); + passed++; + + if (passed > 20) { + break; + } + } + } +} +} +; +} |