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}