Files
c3c/lib/std/net/url_encoding.c3
Christoffer Lerno 5c77c9a754 - Change distinct -> typedef.
- Order of attribute declaration is changed for `alias`.
- Added `LANGUAGE_DEV_VERSION` env constant.
- Rename `anyfault` -> `fault`.
- Changed `fault` -> `faultdef`.
- Added `attrdef` instead of `alias` for attribute aliases.
2025-03-15 20:10:47 +01:00

171 lines
3.8 KiB
Plaintext

<*
This module section provides encoding and decoding functions for URL
components according to RFC 3986.
*>
module std::net::url;
import std::encoding::hex;
enum UrlEncodingMode : char (String allowed)
{
UNRESERVED = "-_.~", // section 2.3
PATH = "$&+,/:;=@", // section 3.3
HOST = "!$&'()*+,;=:[]", // section 3.2.2 (also include ':', '[', ']' for ipv6 hosts)
USERPASS = ";:&=+$,", // section 3.2.1
QUERY = "", // section 3.4
FRAGMENT = "$&+,/:;=?@!()*", // section 4.1
}
faultdef INVALID_HEX;
<*
Returns true if char c should be encoded according to RFC 3986.
@param c : "Character to check if it should be encoded."
@param mode : "Url encoding mode."
*>
fn bool should_encode(char c, UrlEncodingMode mode) @private
{
// alphanumeric characters are allowed
if (c.is_alnum()) return false;
// unreserved characters are allowed
if (try UrlEncodingMode.UNRESERVED.allowed.index_of_char(c)) return false;
// some mode-specific characters are allowed
if (try mode.allowed.index_of_char(c)) return false;
// everything else must be encoded
return true;
}
<*
Calculate the length of the percent-encoded string.
*>
fn usz encode_len(String s, UrlEncodingMode mode) @inline
{
usz n;
foreach (c: s)
{
if (!should_encode(c, mode)) continue;
if (c != ' ' || mode != QUERY)
{
n++;
}
}
return s.len + 2 * n;
}
<*
Encode the string s for a given encoding mode.
Returned string must be freed.
@param s : "String to encode"
@param mode : "Url encoding mode"
@param [inout] allocator
@return "Percent-encoded String"
*>
fn String encode(Allocator allocator, String s, UrlEncodingMode mode) => @pool(allocator)
{
usz n = encode_len(s, mode);
DString builder = dstring::temp_with_capacity(n);
foreach(i, c: s)
{
switch
{
// encode spaces in queries
case c == ' ' && mode == QUERY:
builder.append_char('+');
// add encoded char
case should_encode(c, mode):
builder.append_char('%');
String hex = hex::tencode(s[i:1]);
builder.append(hex.to_upper_tcopy());
// use char, no encoding needed
default:
builder.append_char(c);
}
}
return builder.copy_str(allocator);
}
<*
Encode string s for a given encoding mode, stored on the temp allocator.
@param s : "String to encode"
@param mode : "Url encoding mode"
@return "Percent-encoded String"
*>
fn String tencode(String s, UrlEncodingMode mode) => encode(tmem(), s, mode);
<*
Calculate the length of the percent-decoded string.
@return? INVALID_HEX
*>
fn usz? decode_len(String s, UrlEncodingMode mode) @inline
{
usz n;
foreach (i, c: s)
{
if (c != '%') continue;
if (i + 2 >= s.len || !s[i+1].is_xdigit() || !s[i+2].is_xdigit())
{
return INVALID_HEX?;
}
n++;
}
return s.len - 2 * n;
}
<*
Decode string s for a given encoding mode.
Returned string must be freed.
@param s : "String to decode"
@param mode : "Url encoding mode"
@param [inout] allocator
@return "Percent-decoded String"
*>
fn String? decode(Allocator allocator, String s, UrlEncodingMode mode) => @pool(allocator)
{
usz n = decode_len(s, mode)!;
DString builder = dstring::temp_with_capacity(n);
for (usz i = 0; i < s.len; i++)
{
switch (s[i])
{
// decode encoded char
case '%':
char[] hex = hex::tdecode(s[i+1:2])!;
builder.append(hex);
i += 2;
// decode space when in queries
case '+':
builder.append_char((mode == QUERY) ? ' ' : '+');
// use char, no decoding needed
default:
builder.append_char(s[i]);
}
}
return builder.copy_str(allocator);
}
<*
Decode string s for a given encoding mode, stored on the temp allocator.
@param s : "String to decode"
@param mode : "Url encoding mode"
@return "Percent-decoded String"
*>
fn String? tdecode(String s, UrlEncodingMode mode) => decode(tmem(), s, mode);