#include <cstddef>
#include <cstdint>
#include "cow_string.hpp"
#include "transformer/url_decode.hpp"
#include "utf8.hpp"
#include "utils.hpp"
namespace ddwaf::transformer {
namespace {
bool url_decode_common(cow_string &str, bool read_iis)
{
std::size_t read = 0;
for (; read < str.length() && str.at(read) != '+'; ++read) {
if (read + 2 < str.length() && str.at(read) == '%' && ddwaf::isxdigit(str.at(read + 1)) &&
ddwaf::isxdigit(str.at(read + 2))) {
break;
}
if (read_iis && read + 5 < str.length() && str.at(read) == '%' &&
(str.at(read + 1) | 0x20) == 'u' && ddwaf::isxdigit(str.at(read + 2)) &&
ddwaf::isxdigit(str.at(read + 3)) && ddwaf::isxdigit(str.at(read + 4)) &&
ddwaf::isxdigit(str.at(read + 5))) {
break;
}
}
if (read >= str.length()) {
return false;
}
std::size_t write = read;
while (read < str.length()) {
if (str.at(read) == '+') {
str[write++] = ' ';
read += 1;
} else if (str.at(read) == '%') {
if (read + 2 < str.length() && ddwaf::isxdigit(str.at(read + 1)) &&
ddwaf::isxdigit(str.at(read + 2))) {
const uint8_t highBits = from_hex(str.at(read + 1));
const uint8_t lowBits = from_hex(str.at(read + 2));
str[write++] = static_cast<char>(highBits << 4U | lowBits);
read += 3;
}
else if (read_iis && read + 5 < str.length() && (str.at(read + 1) | 0x20) == 'u' &&
ddwaf::isxdigit(str.at(read + 2)) && ddwaf::isxdigit(str.at(read + 3)) &&
ddwaf::isxdigit(str.at(read + 4)) && ddwaf::isxdigit(str.at(read + 5))) {
const auto codepoint =
(uint16_t)(from_hex(str.at(read + 2)) << 12U |
from_hex(str.at(read + 3)) << 8U | from_hex(str.at(read + 4)) << 4U |
from_hex(str.at(read + 5)));
read += 6;
if (codepoint <= 0x7f) {
str[write++] = static_cast<char>(codepoint);
} else {
write += ddwaf::utf8::write_codepoint(codepoint, &str[write], read - write);
}
}
else {
str[write++] = str.at(read++);
}
} else {
str[write++] = str.at(read++);
}
}
str.truncate(write);
return true;
}
}
bool url_decode::transform_impl(cow_string &str) { return url_decode_common(str, false); }
bool url_decode_iis::transform_impl(cow_string &str)
{
bool output = false;
while (url_decode_common(str, true)) { output = true; }
return output;
}
}