You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

css_tokeniser.cxx 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. /*-
  2. * Copyright 2021 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 "css_tokeniser.hxx"
  17. #include "css_util.hxx"
  18. #include "css.hxx"
  19. #include "frozen/unordered_map.h"
  20. #include "frozen/string.h"
  21. #include <string>
  22. #include <cmath>
  23. namespace rspamd::css {
  24. /* Helpers to create tokens */
  25. /*
  26. * This helper is intended to create tokens either with a tag and value
  27. * or with just a tag.
  28. */
  29. template<css_parser_token::token_type T, class Arg>
  30. auto make_token(const Arg &arg) -> css_parser_token;
  31. template<>
  32. auto make_token<css_parser_token::token_type::string_token, std::string_view>(const std::string_view &s)
  33. -> css_parser_token
  34. {
  35. return css_parser_token{css_parser_token::token_type::string_token, s};
  36. }
  37. template<>
  38. auto make_token<css_parser_token::token_type::ident_token, std::string_view>(const std::string_view &s)
  39. -> css_parser_token
  40. {
  41. return css_parser_token{css_parser_token::token_type::ident_token, s};
  42. }
  43. template<>
  44. auto make_token<css_parser_token::token_type::function_token, std::string_view>(const std::string_view &s)
  45. -> css_parser_token
  46. {
  47. return css_parser_token{css_parser_token::token_type::function_token, s};
  48. }
  49. template<>
  50. auto make_token<css_parser_token::token_type::url_token, std::string_view>(const std::string_view &s)
  51. -> css_parser_token
  52. {
  53. return css_parser_token{css_parser_token::token_type::url_token, s};
  54. }
  55. template<>
  56. auto make_token<css_parser_token::token_type::whitespace_token, std::string_view>(const std::string_view &s)
  57. -> css_parser_token
  58. {
  59. return css_parser_token{css_parser_token::token_type::whitespace_token, s};
  60. }
  61. template<>
  62. auto make_token<css_parser_token::token_type::delim_token, char>(const char &c)
  63. -> css_parser_token
  64. {
  65. return css_parser_token{css_parser_token::token_type::delim_token, c};
  66. }
  67. template<>
  68. auto make_token<css_parser_token::token_type::number_token, float>(const float &d)
  69. -> css_parser_token
  70. {
  71. return css_parser_token{css_parser_token::token_type::number_token, d};
  72. }
  73. /*
  74. * Generic tokens with no value (non-terminals)
  75. */
  76. template<css_parser_token::token_type T>
  77. auto make_token(void) -> css_parser_token
  78. {
  79. return css_parser_token{T, css_parser_token_placeholder()};
  80. }
  81. static constexpr inline auto is_plain_ident_start(char c) -> bool
  82. {
  83. if ((c & 0x80) || g_ascii_isalpha(c) || c == '_') {
  84. return true;
  85. }
  86. return false;
  87. };
  88. static constexpr inline auto is_plain_ident(char c) -> bool
  89. {
  90. if (is_plain_ident_start(c) || c == '-' || g_ascii_isdigit(c)) {
  91. return true;
  92. }
  93. return false;
  94. };
  95. struct css_dimension_data {
  96. css_parser_token::dim_type dtype;
  97. double mult;
  98. };
  99. /*
  100. * Maps from css dimensions to the multipliers that look reasonable in email
  101. */
  102. constexpr const auto max_dims = static_cast<int>(css_parser_token::dim_type::dim_max);
  103. constexpr frozen::unordered_map<frozen::string, css_dimension_data, max_dims> dimensions_map{
  104. {"px", {css_parser_token::dim_type::dim_px, 1.0}},
  105. /* EM/REM are 16 px, so multiply and round */
  106. {"em", {css_parser_token::dim_type::dim_em, 16.0}},
  107. {"rem", {css_parser_token::dim_type::dim_rem, 16.0}},
  108. /*
  109. * Represents the x-height of the element's font.
  110. * On fonts with the "x" letter, this is generally the height
  111. * of lowercase letters in the font; 1ex = 0.5em in many fonts.
  112. */
  113. {"ex", {css_parser_token::dim_type::dim_ex, 8.0}},
  114. {"wv", {css_parser_token::dim_type::dim_wv, 8.0}},
  115. {"wh", {css_parser_token::dim_type::dim_wh, 6.0}},
  116. {"vmax", {css_parser_token::dim_type::dim_vmax, 8.0}},
  117. {"vmin", {css_parser_token::dim_type::dim_vmin, 6.0}},
  118. /* One point. 1pt = 1/72nd of 1in */
  119. {"pt", {css_parser_token::dim_type::dim_pt, 96.0 / 72.0}},
  120. /* 96px/2.54 */
  121. {"cm", {css_parser_token::dim_type::dim_cm, 96.0 / 2.54}},
  122. {"mm", {css_parser_token::dim_type::dim_mm, 9.60 / 2.54}},
  123. {"in", {css_parser_token::dim_type::dim_in, 96.0}},
  124. /* 1pc = 12pt = 1/6th of 1in. */
  125. {"pc", {css_parser_token::dim_type::dim_pc, 96.0 / 6.0}}};
  126. auto css_parser_token::adjust_dim(const css_parser_token &dim_token) -> bool
  127. {
  128. if (!std::holds_alternative<float>(value) ||
  129. !std::holds_alternative<std::string_view>(dim_token.value)) {
  130. /* Invalid tokens */
  131. return false;
  132. }
  133. auto num = std::get<float>(value);
  134. auto sv = std::get<std::string_view>(dim_token.value);
  135. auto dim_found = find_map(dimensions_map, sv);
  136. if (dim_found) {
  137. auto dim_elt = dim_found.value().get();
  138. dimension_type = dim_elt.dtype;
  139. flags |= css_parser_token::number_dimension;
  140. num *= dim_elt.mult;
  141. }
  142. else {
  143. flags |= css_parser_token::flag_bad_dimension;
  144. return false;
  145. }
  146. value = num;
  147. return true;
  148. }
  149. /*
  150. * Consume functions: return a token and advance lexer offset
  151. */
  152. auto css_tokeniser::consume_ident(bool allow_number) -> struct css_parser_token {
  153. auto i = offset;
  154. auto need_escape = false;
  155. auto allow_middle_minus = false;
  156. auto maybe_escape_sv = [&](auto cur_pos, auto tok_type) -> auto {
  157. if (need_escape) {
  158. auto escaped = rspamd::css::unescape_css(pool, {&input[offset],
  159. cur_pos - offset});
  160. offset = cur_pos;
  161. return css_parser_token{tok_type, escaped};
  162. }
  163. auto result = std::string_view{&input[offset], cur_pos - offset};
  164. offset = cur_pos;
  165. return css_parser_token{tok_type, result};
  166. };
  167. /* Ident token can start from `-` or `--` */
  168. if (input[i] == '-') {
  169. i++;
  170. if (i < input.size() && input[i] == '-') {
  171. i++;
  172. allow_middle_minus = true;
  173. }
  174. }
  175. while (i < input.size()) {
  176. auto c = input[i];
  177. auto is_plain_c = (allow_number || allow_middle_minus) ? is_plain_ident(c) : is_plain_ident_start(c);
  178. if (!is_plain_c) {
  179. if (c == '\\' && i + 1 < input.size()) {
  180. /* Escape token */
  181. need_escape = true;
  182. auto nhex = 0;
  183. /* Need to find an escape end */
  184. do {
  185. c = input[++i];
  186. if (g_ascii_isxdigit(c)) {
  187. nhex++;
  188. if (nhex > 6) {
  189. /* End of the escape */
  190. break;
  191. }
  192. }
  193. else if (nhex > 0 && c == ' ') {
  194. /* \[hex]{1,6} */
  195. i++; /* Skip one space */
  196. break;
  197. }
  198. else {
  199. /* Single \ + char */
  200. break;
  201. }
  202. } while (i < input.size());
  203. }
  204. else if (c == '(') {
  205. /* Function or url token */
  206. auto j = i + 1;
  207. while (j < input.size() && g_ascii_isspace(input[j])) {
  208. j++;
  209. }
  210. if (input.size() - offset > 3 && input.substr(offset, 3) == "url") {
  211. if (j < input.size() && (input[j] == '"' || input[j] == '\'')) {
  212. /* Function token */
  213. auto ret = maybe_escape_sv(i,
  214. css_parser_token::token_type::function_token);
  215. return ret;
  216. }
  217. else {
  218. /* Consume URL token */
  219. while (j < input.size() && input[j] != ')') {
  220. j++;
  221. }
  222. if (j < input.size() && input[j] == ')') {
  223. /* Valid url token */
  224. auto ret = maybe_escape_sv(j + 1,
  225. css_parser_token::token_type::url_token);
  226. return ret;
  227. }
  228. else {
  229. /* Incomplete url token */
  230. auto ret = maybe_escape_sv(j,
  231. css_parser_token::token_type::url_token);
  232. ret.flags |= css_parser_token::flag_bad_string;
  233. return ret;
  234. }
  235. }
  236. }
  237. else {
  238. auto ret = maybe_escape_sv(i,
  239. css_parser_token::token_type::function_token);
  240. return ret;
  241. }
  242. }
  243. else if (c == '-' && allow_middle_minus) {
  244. i++;
  245. continue;
  246. }
  247. else {
  248. break; /* Not an ident token */
  249. }
  250. } /* !plain ident */
  251. else {
  252. allow_middle_minus = true;
  253. }
  254. i++;
  255. }
  256. return maybe_escape_sv(i, css_parser_token::token_type::ident_token);
  257. }
  258. auto
  259. css_tokeniser::consume_number() -> struct css_parser_token {
  260. auto i = offset;
  261. auto seen_dot = false, seen_exp = false;
  262. if (input[i] == '-' || input[i] == '+') {
  263. i++;
  264. }
  265. if (input[i] == '.' && i < input.size()) {
  266. seen_dot = true;
  267. i++;
  268. }
  269. while (i < input.size()) {
  270. auto c = input[i];
  271. if (!g_ascii_isdigit(c)) {
  272. if (c == '.') {
  273. if (!seen_dot) {
  274. seen_dot = true;
  275. }
  276. else {
  277. break;
  278. }
  279. }
  280. else if (c == 'e' || c == 'E') {
  281. if (!seen_exp) {
  282. seen_exp = true;
  283. seen_dot = true; /* dots are not allowed after e */
  284. if (i + 1 < input.size()) {
  285. auto next_c = input[i + 1];
  286. if (next_c == '+' || next_c == '-') {
  287. i++;
  288. }
  289. else if (!g_ascii_isdigit(next_c)) {
  290. /* Not an exponent */
  291. break;
  292. }
  293. }
  294. else {
  295. /* Not an exponent */
  296. break;
  297. }
  298. }
  299. else {
  300. break;
  301. }
  302. }
  303. else {
  304. break;
  305. }
  306. }
  307. i++;
  308. }
  309. if (i > offset) {
  310. /* I wish it was supported properly */
  311. //auto conv_res = std::from_chars(&input[offset], &input[i], num);
  312. char numbuf[128], *endptr = nullptr;
  313. rspamd_strlcpy(numbuf, &input[offset], MIN(i - offset + 1, sizeof(numbuf)));
  314. auto num = g_ascii_strtod(numbuf, &endptr);
  315. offset = i;
  316. if (fabs(num) >= G_MAXFLOAT || std::isnan(num)) {
  317. msg_debug_css("invalid number: %s", numbuf);
  318. return make_token<css_parser_token::token_type::delim_token>(input[i - 1]);
  319. }
  320. else {
  321. auto ret = make_token<css_parser_token::token_type::number_token>(static_cast<float>(num));
  322. if (i < input.size()) {
  323. if (input[i] == '%') {
  324. ret.flags |= css_parser_token::number_percent;
  325. i++;
  326. offset = i;
  327. }
  328. else if (is_plain_ident_start(input[i])) {
  329. auto dim_token = consume_ident();
  330. if (dim_token.type == css_parser_token::token_type::ident_token) {
  331. if (!ret.adjust_dim(dim_token)) {
  332. auto sv = std::get<std::string_view>(dim_token.value);
  333. msg_debug_css("cannot apply dimension from the token %*s; number value = %.1f",
  334. (int) sv.size(), sv.begin(), num);
  335. /* Unconsume ident */
  336. offset = i;
  337. }
  338. }
  339. else {
  340. /* We have no option but to uncosume ident token in this case */
  341. msg_debug_css("got invalid ident like token after number, unconsume it");
  342. }
  343. }
  344. else {
  345. /* Plain number, nothing to do */
  346. }
  347. }
  348. return ret;
  349. }
  350. }
  351. else {
  352. msg_err_css("internal error: invalid number, empty token");
  353. i++;
  354. }
  355. offset = i;
  356. /* Should not happen */
  357. return make_token<css_parser_token::token_type::delim_token>(input[i - 1]);
  358. }
  359. /*
  360. * Main routine to produce lexer tokens
  361. */
  362. auto
  363. css_tokeniser::next_token(void) -> struct css_parser_token {
  364. /* Check pushback queue */
  365. if (!backlog.empty()) {
  366. auto tok = backlog.front();
  367. backlog.pop_front();
  368. return tok;
  369. }
  370. /* Helpers */
  371. /*
  372. * This lambda eats comment handling nested comments;
  373. * offset is set to the next character after a comment (or eof)
  374. * Nothing is returned
  375. */
  376. auto consume_comment = [this]() {
  377. auto i = offset;
  378. auto nested = 0;
  379. if (input.empty()) {
  380. /* Nothing to consume */
  381. return;
  382. }
  383. /* We handle nested comments just because they can exist... */
  384. while (i < input.size() - 1) {
  385. auto c = input[i];
  386. if (c == '*' && input[i + 1] == '/') {
  387. if (nested == 0) {
  388. offset = i + 2;
  389. return;
  390. }
  391. else {
  392. nested--;
  393. i += 2;
  394. continue;
  395. }
  396. }
  397. else if (c == '/' && input[i + 1] == '*') {
  398. nested++;
  399. i += 2;
  400. continue;
  401. }
  402. i++;
  403. }
  404. offset = i;
  405. };
  406. /*
  407. * Consume quoted string, returns a string_view over a string, offset
  408. * is set one character after the string. Css unescaping is done automatically
  409. * Accepts a quote char to find end of string
  410. */
  411. auto consume_string = [this](auto quote_char) -> auto {
  412. auto i = offset;
  413. bool need_unescape = false;
  414. while (i < input.size()) {
  415. auto c = input[i];
  416. if (c == '\\') {
  417. if (i + 1 < input.size()) {
  418. need_unescape = true;
  419. }
  420. else {
  421. /* \ at the end -> ignore */
  422. }
  423. }
  424. else if (c == quote_char) {
  425. /* End of string */
  426. std::string_view res{&input[offset], i - offset};
  427. if (need_unescape) {
  428. res = rspamd::css::unescape_css(pool, res);
  429. }
  430. offset = i + 1;
  431. return res;
  432. }
  433. else if (c == '\n') {
  434. /* Should be a error, but we ignore it for now */
  435. }
  436. i++;
  437. }
  438. /* EOF with no quote character, consider it fine */
  439. std::string_view res{&input[offset], i - offset};
  440. if (need_unescape) {
  441. res = rspamd::css::unescape_css(pool, res);
  442. }
  443. offset = i;
  444. return res;
  445. };
  446. /* Main tokenisation loop */
  447. for (auto i = offset; i < input.size(); ++i) {
  448. auto c = input[i];
  449. switch (c) {
  450. case '/':
  451. if (i + 1 < input.size() && input[i + 1] == '*') {
  452. offset = i + 2;
  453. consume_comment(); /* Consume comment and go forward */
  454. return next_token(); /* Tail call */
  455. }
  456. else {
  457. offset = i + 1;
  458. return make_token<css_parser_token::token_type::delim_token>(c);
  459. }
  460. break;
  461. case ' ':
  462. case '\t':
  463. case '\n':
  464. case '\r':
  465. case '\f': {
  466. /* Consume as much space as we can */
  467. while (i < input.size() && g_ascii_isspace(input[i])) {
  468. i++;
  469. }
  470. auto ret = make_token<css_parser_token::token_type::whitespace_token>(
  471. std::string_view(&input[offset], i - offset));
  472. offset = i;
  473. return ret;
  474. }
  475. case '"':
  476. case '\'':
  477. offset = i + 1;
  478. if (offset < input.size()) {
  479. return make_token<css_parser_token::token_type::string_token>(consume_string(c));
  480. }
  481. else {
  482. /* Unpaired quote at the end of the rule */
  483. return make_token<css_parser_token::token_type::delim_token>(c);
  484. }
  485. case '(':
  486. offset = i + 1;
  487. return make_token<css_parser_token::token_type::obrace_token>();
  488. case ')':
  489. offset = i + 1;
  490. return make_token<css_parser_token::token_type::ebrace_token>();
  491. case '[':
  492. offset = i + 1;
  493. return make_token<css_parser_token::token_type::osqbrace_token>();
  494. case ']':
  495. offset = i + 1;
  496. return make_token<css_parser_token::token_type::esqbrace_token>();
  497. case '{':
  498. offset = i + 1;
  499. return make_token<css_parser_token::token_type::ocurlbrace_token>();
  500. case '}':
  501. offset = i + 1;
  502. return make_token<css_parser_token::token_type::ecurlbrace_token>();
  503. case ',':
  504. offset = i + 1;
  505. return make_token<css_parser_token::token_type::comma_token>();
  506. case ';':
  507. offset = i + 1;
  508. return make_token<css_parser_token::token_type::semicolon_token>();
  509. case ':':
  510. offset = i + 1;
  511. return make_token<css_parser_token::token_type::colon_token>();
  512. case '<':
  513. /* Maybe an xml like comment */
  514. if (i + 3 < input.size() && input[i + 1] == '!' && input[i + 2] == '-' && input[i + 3] == '-') {
  515. offset += 3;
  516. return make_token<css_parser_token::token_type::cdo_token>();
  517. }
  518. else {
  519. offset = i + 1;
  520. return make_token<css_parser_token::token_type::delim_token>(c);
  521. }
  522. break;
  523. case '-':
  524. if (i + 1 < input.size()) {
  525. auto next_c = input[i + 1];
  526. if (g_ascii_isdigit(next_c)) {
  527. /* negative number */
  528. return consume_number();
  529. }
  530. else if (next_c == '-') {
  531. if (i + 2 < input.size() && input[i + 2] == '>') {
  532. /* XML like comment */
  533. offset += 3;
  534. return make_token<css_parser_token::token_type::cdc_token>();
  535. }
  536. }
  537. }
  538. /* No other options, a delimiter - */
  539. offset = i + 1;
  540. return make_token<css_parser_token::token_type::delim_token>(c);
  541. break;
  542. case '+':
  543. case '.':
  544. /* Maybe number */
  545. if (i + 1 < input.size()) {
  546. auto next_c = input[i + 1];
  547. if (g_ascii_isdigit(next_c)) {
  548. /* Numeric token */
  549. return consume_number();
  550. }
  551. else {
  552. offset = i + 1;
  553. return make_token<css_parser_token::token_type::delim_token>(c);
  554. }
  555. }
  556. /* No other options, a delimiter - */
  557. offset = i + 1;
  558. return make_token<css_parser_token::token_type::delim_token>(c);
  559. break;
  560. case '\\':
  561. if (i + 1 < input.size()) {
  562. if (input[i + 1] == '\n' || input[i + 1] == '\r') {
  563. offset = i + 1;
  564. return make_token<css_parser_token::token_type::delim_token>(c);
  565. }
  566. else {
  567. /* Valid escape, assume ident */
  568. return consume_ident();
  569. }
  570. }
  571. else {
  572. offset = i + 1;
  573. return make_token<css_parser_token::token_type::delim_token>(c);
  574. }
  575. break;
  576. case '@':
  577. if (i + 3 < input.size()) {
  578. if (is_plain_ident_start(input[i + 1]) &&
  579. is_plain_ident(input[i + 2]) && is_plain_ident(input[i + 3])) {
  580. offset = i + 1;
  581. auto ident_token = consume_ident();
  582. if (ident_token.type == css_parser_token::token_type::ident_token) {
  583. /* Update type */
  584. ident_token.type = css_parser_token::token_type::at_keyword_token;
  585. }
  586. return ident_token;
  587. }
  588. else {
  589. offset = i + 1;
  590. return make_token<css_parser_token::token_type::delim_token>(c);
  591. }
  592. }
  593. else {
  594. offset = i + 1;
  595. return make_token<css_parser_token::token_type::delim_token>(c);
  596. }
  597. break;
  598. case '#':
  599. /* TODO: make it more conformant */
  600. if (i + 2 < input.size()) {
  601. auto next_c = input[i + 1], next_next_c = input[i + 2];
  602. if ((is_plain_ident(next_c) || next_c == '-') &&
  603. (is_plain_ident(next_next_c) || next_next_c == '-')) {
  604. offset = i + 1;
  605. /* We consume indent, but we allow numbers there */
  606. auto ident_token = consume_ident(true);
  607. if (ident_token.type == css_parser_token::token_type::ident_token) {
  608. /* Update type */
  609. ident_token.type = css_parser_token::token_type::hash_token;
  610. }
  611. return ident_token;
  612. }
  613. else {
  614. offset = i + 1;
  615. return make_token<css_parser_token::token_type::delim_token>(c);
  616. }
  617. }
  618. else {
  619. offset = i + 1;
  620. return make_token<css_parser_token::token_type::delim_token>(c);
  621. }
  622. break;
  623. default:
  624. /* Generic parsing code */
  625. if (g_ascii_isdigit(c)) {
  626. return consume_number();
  627. }
  628. else if (is_plain_ident_start(c)) {
  629. return consume_ident();
  630. }
  631. else {
  632. offset = i + 1;
  633. return make_token<css_parser_token::token_type::delim_token>(c);
  634. }
  635. break;
  636. }
  637. }
  638. return make_token<css_parser_token::token_type::eof_token>();
  639. }
  640. constexpr auto
  641. css_parser_token::get_token_type() -> const char *
  642. {
  643. const char *ret = "unknown";
  644. switch (type) {
  645. case token_type::whitespace_token:
  646. ret = "whitespace";
  647. break;
  648. case token_type::ident_token:
  649. ret = "ident";
  650. break;
  651. case token_type::function_token:
  652. ret = "function";
  653. break;
  654. case token_type::at_keyword_token:
  655. ret = "atkeyword";
  656. break;
  657. case token_type::hash_token:
  658. ret = "hash";
  659. break;
  660. case token_type::string_token:
  661. ret = "string";
  662. break;
  663. case token_type::number_token:
  664. ret = "number";
  665. break;
  666. case token_type::url_token:
  667. ret = "url";
  668. break;
  669. case token_type::cdo_token: /* xml open comment */
  670. ret = "cdo";
  671. break;
  672. case token_type::cdc_token: /* xml close comment */
  673. ret = "cdc";
  674. break;
  675. case token_type::delim_token:
  676. ret = "delim";
  677. break;
  678. case token_type::obrace_token: /* ( */
  679. ret = "obrace";
  680. break;
  681. case token_type::ebrace_token: /* ) */
  682. ret = "ebrace";
  683. break;
  684. case token_type::osqbrace_token: /* [ */
  685. ret = "osqbrace";
  686. break;
  687. case token_type::esqbrace_token: /* ] */
  688. ret = "esqbrace";
  689. break;
  690. case token_type::ocurlbrace_token: /* { */
  691. ret = "ocurlbrace";
  692. break;
  693. case token_type::ecurlbrace_token: /* } */
  694. ret = "ecurlbrace";
  695. break;
  696. case token_type::comma_token:
  697. ret = "comma";
  698. break;
  699. case token_type::colon_token:
  700. ret = "colon";
  701. break;
  702. case token_type::semicolon_token:
  703. ret = "semicolon";
  704. break;
  705. case token_type::eof_token:
  706. ret = "eof";
  707. break;
  708. }
  709. return ret;
  710. }
  711. auto css_parser_token::debug_token_str() -> std::string
  712. {
  713. const auto *token_type_str = get_token_type();
  714. std::string ret = token_type_str;
  715. std::visit([&](auto arg) -> auto {
  716. using T = std::decay_t<decltype(arg)>;
  717. if constexpr (std::is_same_v<T, std::string_view> || std::is_same_v<T, char>) {
  718. ret += "; value=";
  719. ret += arg;
  720. }
  721. else if constexpr (std::is_same_v<T, double>) {
  722. ret += "; value=";
  723. ret += std::to_string(arg);
  724. }
  725. },
  726. value);
  727. if ((flags & (~number_dimension)) != default_flags) {
  728. ret += "; flags=" + std::to_string(flags);
  729. }
  730. if (flags & number_dimension) {
  731. ret += "; dim=" + std::to_string(static_cast<int>(dimension_type));
  732. }
  733. return ret; /* Copy elision */
  734. }
  735. }// namespace rspamd::css