1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.
#include <cstddef>
#include <cstdint>
#include "cow_string.hpp"
#include "transformer/html_entity_decode.hpp"
#include "utf8.hpp"
#include "utils.hpp"
namespace ddwaf::transformer {
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
namespace {
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
bool replace_if_match(cow_string &str, size_t &read, size_t &write, const char *token,
size_t token_length, char decoded)
{
const size_t remaining = str.length() - read;
if (remaining < token_length) {
return false;
}
// Case incensitive match (assume the token is lowercase)
for (size_t pos = 0; pos < token_length; ++pos) {
if ((str.at(read + pos) | 0x20) != *token++) {
return false;
}
}
str[write++] = decoded;
read += token_length;
return true;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
} // namespace
bool html_entity_decode::transform_impl(cow_string &str)
{
// If the string is too short
if (str.length() < 3) {
return false;
}
// There are three kinds of escape in HTML:
// &#XXXXX; where XX are numerical digits
// &#xYYY; or &#XYYY; where YYY is an hex-encoded codepoint
// &ZZZZ; where ZZZZ is an alphanumerical name for the character
// In practice, the semicolon is optional
// We skip ahead looking for a `&`. That's not enough to know for sure if we need to
// edit but it's a decent shortcut nonetheless
size_t read = 0;
for (; read < str.length() && str.at(read) != '&'; ++read) {}
size_t write = read;
while (read < str.length()) {
if (str.at(read) != '&' || read == str.length() - 1) {
str.copy_char(read++, write++);
continue;
}
read += 1; // Skip the &
// Codepoint
if (str.at(read) == '#') {
read += 1; // Skip the #
uint32_t codePoint = 0;
// Hexadecimal codepoint
if (read < str.length() - 1 && (str.at(read) == 'x' || str.at(read) == 'X') &&
ddwaf::isxdigit(str.at(read + 1))) {
read += 1; // Skip the x
// Compute the codepoint. We need to compute an arbitrary number of hex
// chars because browsers do too :(
while (read < str.length() && ddwaf::isxdigit(str.at(read))) {
codePoint <<= 4;
codePoint |= ddwaf::from_hex(str.at(read++));
// If we go out of range, move the read head to the end and abort
// immediately. We don't want to risk an overflow
if (codePoint > 0x10ffff) {
for (; read < str.length() && ddwaf::isxdigit(str.at(read)); read += 1) {}
}
}
}
// Numeric codepoint
else if (read < str.length() && ddwaf::isdigit(str.at(read))) {
// Compute the codepoint. We need to compute an arbitrary number of digits
// because browsers do too :(
while (read < str.length() && ddwaf::isdigit(str.at(read))) {
codePoint *= 10;
codePoint += (uint32_t)str.at(read++) - '0';
// If we go out of range, move the read head to the end and abort
// immediately. We don't want to risk an overflow
if (codePoint > 0x10ffff) {
for (; read < str.length() && ddwaf::isdigit(str.at(read)); read += 1) {}
}
}
}
// Accidental match
else {
str.copy_char(read - 2, write++);
str.copy_char(read - 1, write++);
continue;
}
// We extracted the codepoint (or bailed out). Now, we can transcribe it
write += ddwaf::utf8::write_codepoint(codePoint, &str[write], read - write);
if (read < str.length() && str.at(read) == ';') {
read += 1;
}
}
// Named character references
else if (ddwaf::isalnum(str.at(read))) {
// Try to decode a few known references
if (!replace_if_match(str, read, write, "lt;", 3, '<') &&
!replace_if_match(str, read, write, "gt;", 3, '>') &&
!replace_if_match(str, read, write, "amp;", 4, '&') &&
!replace_if_match(str, read, write, "quot;", 5, '"') &&
!replace_if_match(str, read, write, "nbsp;", 5, (char)160)) {
// If none work, write the & we skipped
str.copy_char(read - 1, write++);
}
} else {
str.copy_char(read - 1, write++);
}
}
if (write == str.length()) {
return false;
}
str.truncate(write);
return true;
}
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
} // namespace ddwaf::transformer