Files

104 lines
2.8 KiB
V
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module s3
// uri_encode performs RFC 3986 percent-encoding as required by Signature V4.
// Only the unreserved set A–Z / a–z / 0–9 / '-' / '_' / '.' / '~' is preserved.
// When `encode_slash` is false (used for object keys), '/' is left intact and
// backslashes are normalized to '/' so Windows-style paths produce the same
// canonical key. All other bytes are emitted as %XX with uppercase hex digits.
pub fn uri_encode(input string, encode_slash bool) string {
mut out := []u8{cap: input.len + (input.len >> 2)} // 25% headroom
for b in input.bytes() {
match b {
`A`...`Z`, `a`...`z`, `0`...`9`, `-`, `_`, `.`, `~` {
out << b
}
`/`, `\\` {
if encode_slash {
append_percent(mut out, b)
} else {
out << if b == `\\` { u8(`/`) } else { b }
}
}
else {
append_percent(mut out, b)
}
}
}
return out.bytestr()
}
// uri_encode_path encodes an S3 object key segment-aware: '/' is preserved
// because S3 paths use it as the segment separator.
@[inline]
pub fn uri_encode_path(path string) string {
return uri_encode(path, false)
}
// uri_encode_query encodes a value that will appear inside a query string.
// Slashes must be percent-encoded.
@[inline]
pub fn uri_encode_query(value string) string {
return uri_encode(value, true)
}
// strip_slashes removes leading and trailing '/' or '\\' separators. S3
// canonical paths must contain a single leading slash, no trailing one.
pub fn strip_slashes(s string) string {
if s == '' {
return s
}
mut start := 0
mut end := s.len
for start < end && (s[start] == `/` || s[start] == `\\`) {
start++
}
for end > start && (s[end - 1] == `/` || s[end - 1] == `\\`) {
end--
}
return s[start..end]
}
// contains_crlf returns true if `value` contains a CR or LF byte. Header
// values that pass user-provided strings (ACL, content-type, …) MUST be
// checked to prevent HTTP header injection (CRLF smuggling).
@[inline]
pub fn contains_crlf(value string) bool {
for b in value.bytes() {
if b == `\r` || b == `\n` {
return true
}
}
return false
}
// to_hex_lower formats raw bytes as their lowercase hex string. Used for
// SHA-256 digests inside SigV4 (the spec requires lowercase).
pub fn to_hex_lower(data []u8) string {
mut out := []u8{len: data.len * 2}
for i, b in data {
out[i * 2] = hex_lower_nibble(b >> 4)
out[i * 2 + 1] = hex_lower_nibble(b & 0x0F)
}
return out.bytestr()
}
@[inline]
fn append_percent(mut out []u8, b u8) {
out << `%`
out << hex_upper_nibble(b >> 4)
out << hex_upper_nibble(b & 0x0F)
}
@[inline]
fn hex_upper_nibble(n u8) u8 {
return if n < 10 { n + `0` } else { n - 10 + `A` }
}
@[inline]
fn hex_lower_nibble(n u8) u8 {
return if n < 10 { n + `0` } else { n - 10 + `a` }
}