encoding

Base64, hex, and URL percent-encoding. All six functions operate on UTF-8 strings.

Oxigen
introduce encoding
FunctionSignatureReturns
base64_encodebase64_encode(s)str
base64_decodebase64_decode(s)str or Error
hex_encodehex_encode(s)str
hex_decodehex_decode(s)str or Error
url_encodeurl_encode(s)str
url_decodeurl_decode(s)str or Error

The three *_decode functions return a catchable <Error> on malformed input rather than guessing. Encoding never fails. (url_decode is the lenient one — see below.)

Encoding is not encryption and not hashing: anyone can reverse it. It exists to move bytes through a channel that only accepts certain characters.


#Base64

#base64_encode(s)

Standard base64, with = padding.

Oxigen
println(encoding.base64_encode("hi"))   // aGk=

#base64_decode(s)

Oxigen
println(encoding.base64_decode("aGk="))   // hi

Handling bad input:

Oxigen
introduce encoding

r := <type<Error> || <Value>>(encoding.base64_decode("not base64!!"))
option {
    is_error(r) -> { println("bad payload: {r.msg}") }
    { println(r.value) }
}
// bad payload: invalid base64: Invalid symbol 32, offset 3.

Typical use — an HTTP Basic auth header:

Oxigen
introduce encoding
introduce net

token := encoding.base64_encode("user:password")
resp := net.request(net.HttpMethod.get, "https://example.com/private",
                    {"Authorization": "Basic " + token}, None)

#Hex

#hex_encode(s)

Lowercase hex, two characters per byte.

Oxigen
println(encoding.hex_encode("hi"))   // 6869

#hex_decode(s)

Oxigen
println(encoding.hex_decode("6869"))   // hi

An odd-length string or a non-hex character is an Error.


#URL percent-encoding

#url_encode(s)

Percent-encode a string so it is safe inside a URL query value.

Oxigen
println(encoding.url_encode("a b&c"))   // a%20b%26c

Building a query string by hand:

Oxigen
introduce encoding
introduce strings

fun query(params <map>) {
    parts <array> := []
    each k, v in params {
        parts = push(parts, encoding.url_encode(k) + "=" + encoding.url_encode(str(v)))
    }
    strings.join(parts, "&")
}

#url_decode(s)

Oxigen
println(encoding.url_decode("a%20b%26c"))   // a b&c

An escape that is not valid hex is left alone rather than rejected — url_decode("%ZZ") is "%ZZ", and so is a trailing %. What does fail is an escape sequence that decodes to bytes which are not valid UTF-8:

Oxigen
r := <type<Error> || <Value>>(encoding.url_decode("%FF"))
println(r.msg)
// invalid url encoding: invalid utf-8 sequence of 1 bytes from index 0

The api module decodes request paths and query strings for you and falls back to the raw text on such input, so a handler never receives an error where it expects a string.


#Worked example

Oxigen
introduce encoding
introduce json
introduce strings

// Decode a JWT payload (the middle segment). Base64url uses - and _ instead
// of + and /, so translate before decoding.
fun jwt_payload(token <str>) {
    segments := strings.split(token, ".")
    b64 := strings.replace(strings.replace(segments[1], "-", "+"), "_", "/")
    padded := b64 + strings.repeated("=", (4 - len(b64) % 4) % 4)
    json.parse(encoding.base64_decode(padded))
}

See also: hash for one-way digests, json for structured payloads, net and api for HTTP.