UrlEscape
URL percent-encoding / decoding, pure Kotlin so it runs in commonMain on every target.
Direct port of libtorrent's escape_string / escape_path / unescape_string / need_encoding (src/escape_string.cpp). These drive tracker GET requests (where the 20-byte binary info-hash and peer-id are percent-encoded into the query string) and magnet/URL parsing.
libtorrent keeps a single unreserved_chars table and indexes into it at different offsets so the three operations share one set:
index: 0..1 "%+"
2..10 ";?:@=&,$/" (reserved)
11..18 "-_!.~*()" (unreserved specials; note ' is excluded
because some buggy trackers reject it)
19.. A-Z a-z 0-9 (alphanumerics)escape (libtorrent
escape_string) starts at offset 11: it keeps only-_!.~*()and the alphanumerics, percent-encoding everything else, including/,:,?,&,%and so on. This is what you use to encode an individual query-string value.escapePath (libtorrent
escape_path) starts at offset 10: identical to escape except/is also left intact, so a whole path can be escaped without mangling its separators.needsEncoding (libtorrent
need_encoding) starts at offset 0: returns true if the string contains anything outside the full table (so%,+, and the reserved set all count as "fine"). Used to decide whether a URL even needs touching.
A subtle detail faithfully reproduced from the C++: the table test also requires *str != 0, i.e. a NUL byte is always escaped (to %00) even though C's strchr would otherwise treat the terminating NUL as a match.
Output hex digits are lowercase, matching libtorrent's aux::hex_chars.
Functions
Percent-encode raw bytes as a path (libtorrent escape_path); / preserved.
Percent-encode arbitrary raw bytes (libtorrent escape_string over a char const*). This is the form used for binary values such as the 20-byte info-hash or peer-id in a tracker announce query.
Byte-array form of needsEncoding (libtorrent need_encoding).
True if s (interpreted as its UTF-8 bytes) contains any character that would have to be percent-encoded in a URL: libtorrent need_encoding. %, + and the reserved set do not count as needing encoding.
Like unescape but returns the raw decoded bytes. libtorrent's unescape_string operates byte-wise on a std::string; this is the faithful byte-level form, which matters when the decoded payload is binary (an info-hash percent-encoded into a magnet/tracker URL is not valid UTF-8).