`switch_url_encode()` encodes with `double_encode` false, so a `%XX`
already present in `json_text` was emitted unchanged and became
indistinguishable from the escapes the body encoding added itself.
Decoding the body then consumed the values' own escapes as well, and
the document no longer parsed. Values arrive pre-encoded whenever
`encode-values` is on, and can hold percent-hex text regardless of it.
Encode with `switch_url_encode_opt(..., SWITCH_TRUE)` so `%` is encoded
too and the body decodes back to exactly the serialized document.
`mod_xml_cdr` and `mod_format_cdr` already encode their bodies this way.
- Size the escape buffer `* 3 + 1`. `switch_url_encode_opt()` reserves
the terminator from the length it is given, so `* 3` dropped the last
escaped character when every byte needed encoding. The base64 call now
takes that length directly.
- Warn at load when `encode` and `encode-values` are both on, since the
values stay encoded after the body is decoded.
- Correct both encoding config comments; the `encode` one described only
`base64`.
Adds core coverage in `tests/unit/switch_utils.c` for both
`switch_url_encode_opt()` modes, its output bounds, and a JSON body
encoded and decoded once.
switch_b64_encode wrote each base64 character before checking the
output bound, and computed that bound as `bytes >= (int)olen - 1`.
Both are unsafe:
- Writing before the check meant a buffer with no room for a data
byte still received one: `olen == 1` wrote a character at index 0
and the NUL at index 1, and `olen == 0` (where `(int)olen - 1` is
-1) wrote two bytes, both past the end.
- Casting the unsigned `olen` to `int` truncated it above INT_MAX,
producing a wrong bound for very large buffers.
Reject `olen == 0`, make the output index `bytes` a `size_t`, and test
`bytes + 1 >= olen` before each write, so the bound never casts or
underflows and always leaves the last byte for the terminator. Apply
the same `bytes + 1 < olen` guard to the trailing partial-group
character and the `=` padding, so no write can pass the end.
Remove the dead line-wrap counter `y`: it only gated a commented-out
newline emit, so it counted and reset with no effect on the output.
Add unit tests covering the output-bound edges for every write site.
switch_b64_decode bounded its writes with `ol >= olen - 1`, where
`olen` is unsigned. An `olen` of 0 made `olen - 1` wrap to `SIZE_MAX`,
so the bound never fired and the loop wrote the entire decoded input
plus a trailing NUL past the destination. Reject `olen == 0` and test
`ol + 1 >= olen` before each write, so the comparison never subtracts
from an unsigned and always leaves room for the terminator.
The alphabet lookup table `l64` was a `char` indexed by a `char`,
which is unsafe whichever way `char` is signed:
- Where `char` is signed, an input byte >= 0x80 became a negative
index and read before the table.
- Where `char` is unsigned, the `-1` "not in alphabet" sentinel was
stored as 255, so the skip test never matched and non-alphabet
bytes were folded in as data.
Make `l64` a `signed char` and index it with `(unsigned char)`, so the
sentinel survives and the index stays in range on every platform.
Change the bit accumulator `b` from `int` to `unsigned int` to avoid
signed-overflow undefined behavior on long input; decoded output is
unchanged.
Add unit tests: an encode/decode round-trip across the padding cases,
the output-bound edges (`olen` of 0, 1, and a truncating buffer), and
a non-alphabet byte (including one >= 0x80) that must be skipped.