code wiki / (root) / cors.nx

cors.nx source

↩ module page · 167 lines · 5934 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 26import "syscalls.nx" 27 28const CORS_ERR_SHORT: i64 = -1 29 30func co_put(out: *u8, cap: i64, off: i64, src: *u8, n: i64) -> i64 { 31 if off + n > cap { return CORS_ERR_SHORT } 32 var i: i64 = 0 33 while i < n { 34 out[off + i] = src[i] 35 i = i + 1 36 } 37 return off + n 38} 39 40// Access-Control-Allow-Origin header. Pass "*" for wildcard or 41// a specific origin URL for credentialed endpoints. 42func cors_allow_origin(out: *u8, cap: i64, off: i64, 43 origin: *u8, origin_len: i64) -> i64 { 44 var cur: i64 = off 45 cur = co_put(out, cap, cur, "Access-Control-Allow-Origin: ", 29) 46 if cur < 0 { return cur } 47 cur = co_put(out, cap, cur, origin, origin_len) 48 if cur < 0 { return cur } 49 cur = co_put(out, cap, cur, "\r\n", 2) 50 return cur 51} 52 53// Allow specific methods -- caller supplies comma-separated list. 54// e.g. "GET, POST, PUT, DELETE, OPTIONS". 55func cors_allow_methods(out: *u8, cap: i64, off: i64, 56 methods: *u8, methods_len: i64) -> i64 { 57 var cur: i64 = off 58 cur = co_put(out, cap, cur, "Access-Control-Allow-Methods: ", 30) 59 if cur < 0 { return cur } 60 cur = co_put(out, cap, cur, methods, methods_len) 61 if cur < 0 { return cur } 62 cur = co_put(out, cap, cur, "\r\n", 2) 63 return cur 64} 65 66// Allow-Headers: comma-separated list of request headers the 67// client is permitted to send. 68func cors_allow_headers(out: *u8, cap: i64, off: i64, 69 headers: *u8, headers_len: i64) -> i64 { 70 var cur: i64 = off 71 cur = co_put(out, cap, cur, "Access-Control-Allow-Headers: ", 30) 72 if cur < 0 { return cur } 73 cur = co_put(out, cap, cur, headers, headers_len) 74 if cur < 0 { return cur } 75 cur = co_put(out, cap, cur, "\r\n", 2) 76 return cur 77} 78 79// Expose-Headers: the response headers the client's JS can read 80// (everything outside the CORS-safelisted set needs opting-in). 81func cors_expose_headers(out: *u8, cap: i64, off: i64, 82 headers: *u8, headers_len: i64) -> i64 { 83 var cur: i64 = off 84 cur = co_put(out, cap, cur, "Access-Control-Expose-Headers: ", 31) 85 if cur < 0 { return cur } 86 cur = co_put(out, cap, cur, headers, headers_len) 87 if cur < 0 { return cur } 88 cur = co_put(out, cap, cur, "\r\n", 2) 89 return cur 90} 91 92// Allow cookies / Authorization to cross origins. Requires 93// Allow-Origin to be a specific origin (not '*'). 94func cors_allow_credentials(out: *u8, cap: i64, off: i64) -> i64 { 95 return co_put(out, cap, off, 96 "Access-Control-Allow-Credentials: true\r\n", 40) 97} 98 99// Max-Age: how long (seconds) browsers may cache the preflight 100// response. Browsers cap to their own max (Chrome: 7200s, FF: 101// 86400s) regardless. 102func cors_max_age(out: *u8, cap: i64, off: i64, seconds: i64) -> i64 { 103 var cur: i64 = off 104 cur = co_put(out, cap, cur, "Access-Control-Max-Age: ", 24) 105 if cur < 0 { return cur } 106 107 // Emit decimal seconds. 108 if seconds == 0 { 109 if cur + 1 > cap { return CORS_ERR_SHORT } 110 out[cur] = 0x30 111 cur = cur + 1 112 } else { 113 let digits_raw: *u8 = sys_mmap(32) 114 var tmp: i64 = seconds 115 var dn: i64 = 0 116 while tmp > 0 { 117 digits_raw[dn] = 0x30 + (tmp % 10) 118 tmp = tmp / 10 119 dn = dn + 1 120 } 121 if cur + dn > cap { return CORS_ERR_SHORT } 122 var i: i64 = dn - 1 123 while i >= 0 { 124 out[cur] = digits_raw[i] 125 cur = cur + 1 126 i = i - 1 127 } 128 } 129 cur = co_put(out, cap, cur, "\r\n", 2) 130 return cur 131} 132 133// Convenience: emit a permissive public-API preflight response 134// (Allow: *, common methods + headers, 24-hour cache). 135func cors_permissive(out: *u8, cap: i64, off: i64) -> i64 { 136 var cur: i64 = off 137 cur = cors_allow_origin(out, cap, cur, "*", 1) 138 if cur < 0 { return cur } 139 cur = cors_allow_methods(out, cap, cur, 140 "GET, POST, PUT, DELETE, OPTIONS", 31) 141 if cur < 0 { return cur } 142 cur = cors_allow_headers(out, cap, cur, 143 "Content-Type, Authorization", 27) 144 if cur < 0 { return cur } 145 cur = cors_max_age(out, cap, cur, 86400) 146 return cur 147} 148 149// Compile-only smoke. 150func main() -> i64 { 151 let out: *u8 = sys_mmap(1024) 152 let n: i64 = cors_permissive(out, 1024, 0) 153 if n <= 0 { return 1 } 154 155 // First char is 'A' of \"Access-Control\". 156 if out[0] != 0x41 { return 2 } 157 158 // Credentialed flow: specific origin + credentials. 159 var off: i64 = 0 160 off = cors_allow_origin(out, 1024, off, 161 "https://nishifamily.com", 23) 162 if off < 0 { return 3 } 163 off = cors_allow_credentials(out, 1024, off) 164 if off < 0 { return 4 } 165 166 return 0 167}