code wiki / (root) / etag.nx

etag.nx source

↩ module page · 155 lines · 5666 B

1// etag.nx -- generate + compare HTTP ETag headers (RFC 7232). 2// 3// The ETag header identifies a specific version of a resource so 4// conditional requests (If-None-Match / If-Match) can avoid 5// re-sending bytes that haven't changed. Every static-file server 6// needs this, every CDN lives or dies on it. 7// 8// Format (RFC 7232 §2.3): 9// ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" <- strong 10// ETag: W/"3ad4a9c3" <- weak 11// 12// Strong ETags mean byte-exact match; weak (W/-prefixed) means 13// semantically equivalent but possibly byte-different (e.g. same 14// content reflowed whitespace). 15// 16// We generate ETags by: 17// 1. sha256 (existing) of the file contents -> 32 bytes 18// 2. hex-encode (existing) -> 64 ASCII chars 19// 3. truncate to etag_size bytes (caller choice; 8-32 typical) 20// 4. wrap in double quotes 21// 22// Comparison follows RFC 7232 §2.3.2: strong match requires both 23// to be strong and byte-identical; weak match matches regardless 24// of W/- prefix. 25// 26// Invariants: 27// ET1 Output always ends with a trailing DQUOTE. 28// ET2 Weak prefix is exactly "W/" (2 ASCII bytes, case-sensitive). 29// ET3 Caller chooses truncation length; 8 hex chars (4 bytes 30// of entropy) is the minimum practical, 32 (128 bits) is 31// plenty. 32 33import "syscalls.nx" 34import "sha256.nx" 35import "hex.nx" 36 37const ETAG_ERR_SHORT: i64 = -1 38 39// Generate a strong ETag from data[0..n] into out buffer. 40// `trunc` is how many hex characters to include (without quotes). 41// Writes: "<trunc hex chars>" (trunc + 2 bytes total, DQUOTE-wrapped). 42// Returns bytes written or ETAG_ERR_SHORT. 43func etag_gen_strong(data: *u8, n: i64, 44 out: *u8, cap: i64, trunc: i64) -> i64 { 45 if trunc < 2 { return ETAG_ERR_SHORT } 46 if trunc > 64 { return ETAG_ERR_SHORT } 47 let total: i64 = trunc + 2 48 if cap < total { return ETAG_ERR_SHORT } 49 50 let digest: *u8 = sys_mmap(64) 51 sha256_digest(data, n, digest) 52 let hex_out: *u8 = sys_mmap(96) 53 hex_encode(digest, 32, hex_out) 54 55 out[0] = 0x22 // '\"' 56 var i: i64 = 0 57 while i < trunc { 58 out[1 + i] = hex_out[i] 59 i = i + 1 60 } 61 out[1 + trunc] = 0x22 62 return total 63} 64 65// Generate a weak ETag: W/"<trunc hex>". 66func etag_gen_weak(data: *u8, n: i64, 67 out: *u8, cap: i64, trunc: i64) -> i64 { 68 if cap < trunc + 4 { return ETAG_ERR_SHORT } 69 out[0] = 0x57 // 'W' 70 out[1] = 0x2F // '/' 71 let inner: i64 = etag_gen_strong(data, n, out + 2, cap - 2, trunc) 72 if inner < 0 { return inner } 73 return inner + 2 74} 75 76// Test whether an ETag value begins with the weak "W/" prefix. 77func etag_is_weak(tag: *u8, n: i64) -> i64 { 78 if n < 3 { return 0 } 79 if tag[0] != 0x57 { return 0 } 80 if tag[1] != 0x2F { return 0 } 81 return 1 82} 83 84// Strip outer quotes. Writes (start_off, end_off) into the 85// caller's slot pair. Returns 1 if the tag was well-formed, 86// 0 otherwise. 87func etag_strip_quotes(tag: *u8, n: i64, 88 start_out: *i64, end_out: *i64) -> i64 { 89 var start: i64 = 0 90 if etag_is_weak(tag, n) == 1 { start = 2 } 91 if n < start + 2 { return 0 } 92 if tag[start] != 0x22 { return 0 } // opening quote 93 if tag[n - 1] != 0x22 { return 0 } // closing quote 94 *start_out = start + 1 95 *end_out = n - 1 96 return 1 97} 98 99// Weak match: ignore W/- prefix, compare inner opaque bytes. 100// Returns 1 if they match, 0 otherwise. 101func etag_weak_match(a: *u8, a_len: i64, 102 b: *u8, b_len: i64) -> i64 { 103 let a_start: *i64 = (sys_mmap(32)) as *i64 104 let a_end: *i64 = (sys_mmap(32)) as *i64 105 let b_start: *i64 = (sys_mmap(32)) as *i64 106 let b_end: *i64 = (sys_mmap(32)) as *i64 107 if etag_strip_quotes(a, a_len, a_start, a_end) != 1 { return 0 } 108 if etag_strip_quotes(b, b_len, b_start, b_end) != 1 { return 0 } 109 let la: i64 = *a_end - *a_start 110 let lb: i64 = *b_end - *b_start 111 if la != lb { return 0 } 112 var i: i64 = 0 113 while i < la { 114 if a[*a_start + i] != b[*b_start + i] { return 0 } 115 i = i + 1 116 } 117 return 1 118} 119 120// Strong match: both must be strong AND byte-identical inner. 121func etag_strong_match(a: *u8, a_len: i64, 122 b: *u8, b_len: i64) -> i64 { 123 if etag_is_weak(a, a_len) == 1 { return 0 } 124 if etag_is_weak(b, b_len) == 1 { return 0 } 125 return etag_weak_match(a, a_len, b, b_len) 126} 127 128// Compile-only smoke. 129func main() -> i64 { 130 let tag1: *u8 = sys_mmap(64) 131 let tag2: *u8 = sys_mmap(64) 132 let tag3: *u8 = sys_mmap(64) 133 134 let data: *u8 = "hello world" 135 let l1: i64 = etag_gen_strong(data, 11, tag1, 64, 16) 136 if l1 != 18 { return 1 } // 16 hex + 2 quotes 137 if tag1[0] != 0x22 { return 2 } 138 if tag1[17] != 0x22 { return 3 } 139 140 let l2: i64 = etag_gen_weak(data, 11, tag2, 64, 16) 141 if l2 != 20 { return 4 } // W/ + 18 = 20 142 if tag2[0] != 0x57 { return 5 } 143 if tag2[1] != 0x2F { return 6 } 144 145 // Same underlying data -> weak-match equal. 146 if etag_weak_match(tag1, l1, tag2, l2) != 1 { return 7 } 147 // Strong match fails because tag2 is weak. 148 if etag_strong_match(tag1, l1, tag2, l2) != 0 { return 8 } 149 150 // Different data -> no match. 151 let l3: i64 = etag_gen_strong("hello worlds", 12, tag3, 64, 16) 152 if etag_weak_match(tag1, l1, tag3, l3) != 0 { return 9 } 153 if etag_strong_match(tag1, l1, tag3, l3) != 0 { return 10 } 154 return 0 155}