summaryrefslogtreecommitdiffstats
path: root/src/libserver/css/css_value.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/libserver/css/css_value.cxx')
-rw-r--r--src/libserver/css/css_value.cxx449
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;
+ }
+ }
+ }
+}
+}
+;
+}