code wiki / (root) / nx_cors.nx

nx_cors.nx source

↩ module page · 175 lines · 6146 B

1// cors.nx -- build CORS response headers. 2// 3// Fetch / XHR from a browser to a cross-origin server triggers 4// CORS (Cross-Origin Resource Sharing). Browser sends a preflight 5// OPTIONS request with Origin + Access-Control-Request-Method + 6// Access-Control-Request-Headers. Server answers with Access- 7// Control-Allow-* headers indicating what's permitted. 8// 9// Spec: Fetch Living Standard §Cors (supersedes the W3C CORS rec). 10// 11// Typical minimal response for a permissive public API: 12// Access-Control-Allow-Origin: * 13// Access-Control-Allow-Methods: GET, POST, OPTIONS 14// Access-Control-Allow-Headers: Content-Type, Authorization 15// Access-Control-Max-Age: 86400 16// 17// For credentialed requests (cookies / auth), Allow-Origin MUST 18// echo the specific origin (not '*') and Allow-Credentials MUST 19// be true. We expose both modes. 20// 21// Invariants: 22// CO1 Stream builder; caller supplies an output buffer + offset. 23// CO2 Headers include trailing CRLF; caller concatenates them 24// after the HTTP status line. 25 26// nx_safety_envelope: 27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 28// sil_target: SIL1 29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 30// verdict: NOT_YET_EVALUATED 31 32import "nx_syscalls.nx" 33const CORS_MAGIC_86400: i64 = 86400 34const CORS_MAGIC_1024: i64 = 1024 35 36const CORS_ERR_SHORT: i64 = -1 37 38func co_put(out: *u8, cap: i64, off: i64, src: *u8, n: i64) -> i64 { 39 if off + n > cap { return CORS_ERR_SHORT } 40 var i: i64 = 0 41 while i < n { 42 out[off + i] = src[i] 43 i = i + 1 44 } 45 return off + n 46} 47 48// Access-Control-Allow-Origin header. Pass "*" for wildcard or 49// a specific origin URL for credentialed endpoints. 50func cors_allow_origin(out: *u8, cap: i64, off: i64, 51 origin: *u8, origin_len: i64) -> i64 { 52 var cur: i64 = off 53 cur = co_put(out, cap, cur, "Access-Control-Allow-Origin: ", 29) 54 if cur < 0 { return cur } 55 cur = co_put(out, cap, cur, origin, origin_len) 56 if cur < 0 { return cur } 57 cur = co_put(out, cap, cur, "\r\n", 2) 58 return cur 59} 60 61// Allow specific methods -- caller supplies comma-separated list. 62// e.g. "GET, POST, PUT, DELETE, OPTIONS". 63func cors_allow_methods(out: *u8, cap: i64, off: i64, 64 methods: *u8, methods_len: i64) -> i64 { 65 var cur: i64 = off 66 cur = co_put(out, cap, cur, "Access-Control-Allow-Methods: ", 30) 67 if cur < 0 { return cur } 68 cur = co_put(out, cap, cur, methods, methods_len) 69 if cur < 0 { return cur } 70 cur = co_put(out, cap, cur, "\r\n", 2) 71 return cur 72} 73 74// Allow-Headers: comma-separated list of request headers the 75// client is permitted to send. 76func cors_allow_headers(out: *u8, cap: i64, off: i64, 77 headers: *u8, headers_len: i64) -> i64 { 78 var cur: i64 = off 79 cur = co_put(out, cap, cur, "Access-Control-Allow-Headers: ", 30) 80 if cur < 0 { return cur } 81 cur = co_put(out, cap, cur, headers, headers_len) 82 if cur < 0 { return cur } 83 cur = co_put(out, cap, cur, "\r\n", 2) 84 return cur 85} 86 87// Expose-Headers: the response headers the client's JS can read 88// (everything outside the CORS-safelisted set needs opting-in). 89func cors_expose_headers(out: *u8, cap: i64, off: i64, 90 headers: *u8, headers_len: i64) -> i64 { 91 var cur: i64 = off 92 cur = co_put(out, cap, cur, "Access-Control-Expose-Headers: ", 31) 93 if cur < 0 { return cur } 94 cur = co_put(out, cap, cur, headers, headers_len) 95 if cur < 0 { return cur } 96 cur = co_put(out, cap, cur, "\r\n", 2) 97 return cur 98} 99 100// Allow cookies / Authorization to cross origins. Requires 101// Allow-Origin to be a specific origin (not '*'). 102func cors_allow_credentials(out: *u8, cap: i64, off: i64) -> i64 { 103 return co_put(out, cap, off, 104 "Access-Control-Allow-Credentials: true\r\n", 40) 105} 106 107// Max-Age: how long (seconds) browsers may cache the preflight 108// response. Browsers cap to their own max (Chrome: 7200s, FF: 109// 86400s) regardless. 110func cors_max_age(out: *u8, cap: i64, off: i64, seconds: i64) -> i64 { 111 var cur: i64 = off 112 cur = co_put(out, cap, cur, "Access-Control-Max-Age: ", 24) 113 if cur < 0 { return cur } 114 115 // Emit decimal seconds. 116 if seconds == 0 { 117 if cur + 1 > cap { return CORS_ERR_SHORT } 118 out[cur] = 0x30 119 cur = cur + 1 120 } else { 121 let digits_raw: *u8 = sys_mmap(32) 122 var tmp: i64 = seconds 123 var dn: i64 = 0 124 while tmp > 0 { 125 digits_raw[dn] = 0x30 + (tmp % 10) 126 tmp = tmp / 10 127 dn = dn + 1 128 } 129 if cur + dn > cap { return CORS_ERR_SHORT } 130 var i: i64 = dn - 1 131 while i >= 0 { 132 out[cur] = digits_raw[i] 133 cur = cur + 1 134 i = i - 1 135 } 136 } 137 cur = co_put(out, cap, cur, "\r\n", 2) 138 return cur 139} 140 141// Convenience: emit a permissive public-API preflight response 142// (Allow: *, common methods + headers, 24-hour cache). 143func cors_permissive(out: *u8, cap: i64, off: i64) -> i64 { 144 var cur: i64 = off 145 cur = cors_allow_origin(out, cap, cur, "*", 1) 146 if cur < 0 { return cur } 147 cur = cors_allow_methods(out, cap, cur, 148 "GET, POST, PUT, DELETE, OPTIONS", 31) 149 if cur < 0 { return cur } 150 cur = cors_allow_headers(out, cap, cur, 151 "Content-Type, Authorization", 27) 152 if cur < 0 { return cur } 153 cur = cors_max_age(out, cap, cur, CORS_MAGIC_86400) 154 return cur 155} 156 157// Compile-only smoke. 158func main() -> i64 { 159 let out: *u8 = sys_mmap(CORS_MAGIC_1024) 160 let n: i64 = cors_permissive(out, CORS_MAGIC_1024, 0) 161 if n <= 0 { return 1 } 162 163 // First char is 'A' of \"Access-Control\". 164 if out[0] != 0x41 { return 2 } 165 166 // Credentialed flow: specific origin + credentials. 167 var off: i64 = 0 168 off = cors_allow_origin(out, CORS_MAGIC_1024, off, 169 "https://nishifamily.com", 23) 170 if off < 0 { return 3 } 171 off = cors_allow_credentials(out, CORS_MAGIC_1024, off) 172 if off < 0 { return 4 } 173 174 return 0 175}