code wiki / _hdl_build / nx_adnet_slot.nx

nx_adnet_slot.nx source

↩ module page · 300 lines · 11829 B

1// nx_adnet_slot.nx -- LIB: the UNIVERSAL site-wide ad slot for the sovereign ad network (nx_adnet core). 2// One labeled, first-party, zero-third-party-JS banner slot rendered per page (injected by hr_serve3_slot), 3// plus the first-party /ad/click/<id> redirect responder. PRIVACY BY CONSTRUCTION: no cookie, no visitor id, 4// per-AD counters only; rotation is TIME-based (no per-user frequency state -- per-user tracking is REFUSED). 5// Inventory conf rows (nx_adnet contract): id<TAB>advertiser<TAB>img<TAB>clickurl<TAB>section<TAB>weight 6// plus directive rows: @host<TAB><allowed-vhost> (FAIL-CLOSED: no @host row for the vhost -> no slot ever). 7// All parsing here is BOUNDED (rule 12 defensive-at-boundaries); a row that fails validation is DROPPED, 8// never served. Section falls back to rows with section 'any'. license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "_hdl_build/nx_adnet.nx" 11 12func aslot_lc(c: u8) -> u8 { if c >= (65 as u8) { if c <= (90 as u8) { return (c + (32 as u8)) } } return c } 13 14func aslot_starts(s: *u8, pre: *u8) -> i64 { 15 var i: i64 = 0 16 while pre[i] != (0 as u8) { if s[i] != pre[i] { return 0 } i = i + 1 } 17 return 1 18} 19 20// BOUNDED field extract (nx_adnet ad_field has no cap -- this one refuses to overflow out). 21func aslot_field_b(row: *u8, rlen: i64, idx: i64, out: *u8, cap: i64) -> i64 { 22 var fs: i64 = 0 23 var f: i64 = 0 24 while f < idx { let t: i64 = ad_tab(row, fs, rlen); fs = t + 1; f = f + 1 } 25 let fe: i64 = ad_tab(row, fs, rlen) 26 var o: i64 = 0 27 var k: i64 = fs 28 while k < fe { if o < cap - 1 { out[o] = row[k]; o = o + 1 } k = k + 1 } 29 out[o] = 0 as u8 30 return o 31} 32 33// derive the section token from the request path: first path segment, lowercased ("/": home). 34func aslot_section_of(path: *u8, pn: i64, out: *u8, cap: i64) -> i64 { 35 var i: i64 = 0 36 if pn > 0 { if path[0] == (47 as u8) { i = 1 } } 37 var j: i64 = 0 38 while i < pn { 39 let c: u8 = path[i] 40 if c == (47 as u8) { break } 41 if c == (46 as u8) { break } 42 if c == (63 as u8) { break } 43 if j < cap - 1 { out[j] = aslot_lc(c); j = j + 1 } 44 i = i + 1 45 } 46 if j == 0 { 47 let h: *u8 = "home" as *u8 48 var k: i64 = 0 49 while h[k] != (0 as u8) { if k < cap - 1 { out[k] = h[k] } k = k + 1 } 50 j = k 51 } 52 out[j] = 0 as u8 53 return j 54} 55 56// directive scan: is this vhost explicitly ad-enabled? FAIL-CLOSED (no @host rows -> 0). 57func aslot_host_ok(inv: *u8, ilen: i64, host: *u8) -> i64 { 58 let f0: *u8 = sys_mmap(128) 59 let f1: *u8 = sys_mmap(256) 60 var ls: i64 = 0 61 while ls < ilen { 62 let le: i64 = ad_eol(inv, ls, ilen) 63 if le > ls { 64 let row: *u8 = ((inv as i64) + ls) as *u8 65 aslot_field_b(row, le - ls, 0, f0, 128) 66 if ad_streq(f0, "@host" as *u8) == 1 { 67 aslot_field_b(row, le - ls, 1, f1, 256) 68 if ad_streq(f1, host) == 1 { return 1 } 69 } 70 } 71 ls = le + 1 72 } 73 return 0 74} 75 76// id charset: [A-Za-z0-9_-] only, 1..64 chars. 77func aslot_id_ok(id: *u8) -> i64 { 78 var i: i64 = 0 79 while id[i] != (0 as u8) { 80 let c: u8 = id[i] 81 var ok: i64 = 0 82 if c >= (97 as u8) { if c <= (122 as u8) { ok = 1 } } 83 if c >= (65 as u8) { if c <= (90 as u8) { ok = 1 } } 84 if c >= (48 as u8) { if c <= (57 as u8) { ok = 1 } } 85 if c == (95 as u8) { ok = 1 } 86 if c == (45 as u8) { ok = 1 } 87 if ok == 0 { return 0 } 88 i = i + 1 89 if i > 64 { return 0 } 90 } 91 if i == 0 { return 0 } 92 return 1 93} 94 95// forbidden-byte scan shared by url validators: quote/angle/space/apostrophe kill the row. 96func aslot_url_chars_ok(u: *u8) -> i64 { 97 var i: i64 = 0 98 while u[i] != (0 as u8) { 99 let c: u8 = u[i] 100 if c == (34 as u8) { return 0 } 101 if c == (39 as u8) { return 0 } 102 if c == (60 as u8) { return 0 } 103 if c == (62 as u8) { return 0 } 104 if c == (32 as u8) { return 0 } 105 i = i + 1 106 if i > 900 { return 0 } 107 } 108 if i == 0 { return 0 } 109 return 1 110} 111 112// creative img: FIRST-PARTY ONLY -- absolute path, not scheme-relative, clean charset. 113func aslot_img_ok(u: *u8) -> i64 { 114 if u[0] != (47 as u8) { return 0 } 115 if u[1] == (47 as u8) { return 0 } 116 return aslot_url_chars_ok(u) 117} 118 119// click target: first-party path OR explicit http(s). javascript:/data: excluded by construction. 120func aslot_click_ok(u: *u8) -> i64 { 121 var sch: i64 = 0 122 if u[0] == (47 as u8) { if u[1] != (47 as u8) { sch = 1 } } 123 if aslot_starts(u, "https://" as *u8) == 1 { sch = 1 } 124 if aslot_starts(u, "http://" as *u8) == 1 { sch = 1 } 125 if sch == 0 { return 0 } 126 return aslot_url_chars_ok(u) 127} 128 129// HTML-escape append: < > & " escaped; apostrophe replaced with space (attribute-safe, no entity). 130func aslot_cat_esc(dst: *u8, off: i64, s: *u8, cap: i64) -> i64 { 131 var o: i64 = off 132 var i: i64 = 0 133 while s[i] != (0 as u8) { 134 if o + 8 >= cap { return o } 135 let c: u8 = s[i] 136 var lit: i64 = 1 137 if c == (60 as u8) { o = ad_cat(dst, o, "&lt;" as *u8); lit = 0 } 138 if c == (62 as u8) { if lit == 1 { o = ad_cat(dst, o, "&gt;" as *u8); lit = 0 } } 139 if c == (38 as u8) { if lit == 1 { o = ad_cat(dst, o, "&amp;" as *u8); lit = 0 } } 140 if c == (34 as u8) { if lit == 1 { o = ad_cat(dst, o, "&quot;" as *u8); lit = 0 } } 141 if c == (39 as u8) { if lit == 1 { dst[o] = 32 as u8; o = o + 1; lit = 0 } } 142 if lit == 1 { dst[o] = c; o = o + 1 } 143 i = i + 1 144 } 145 return o 146} 147 148// count inventory rows whose SECTION (field 4) == section, skipping directive/oversize rows. 149func aslot_count(inv: *u8, ilen: i64, section: *u8) -> i64 { 150 let tmp: *u8 = sys_mmap(128) 151 var cnt: i64 = 0 152 var ls: i64 = 0 153 while ls < ilen { 154 let le: i64 = ad_eol(inv, ls, ilen) 155 if le > ls { 156 if le - ls < 1536 { 157 let row: *u8 = ((inv as i64) + ls) as *u8 158 if row[0] != (64 as u8) { 159 aslot_field_b(row, le - ls, 4, tmp, 128) 160 if ad_streq(tmp, section) == 1 { cnt = cnt + 1 } 161 } 162 } 163 } 164 ls = le + 1 165 } 166 return cnt 167} 168 169// BOUNDED rotate-pick: copy the (rot mod matches)-th section row into out (cap'd). 1 = found. 170func aslot_pick_b(inv: *u8, ilen: i64, section: *u8, rot: i64, out: *u8, cap: i64) -> i64 { 171 let m: i64 = aslot_count(inv, ilen, section) 172 if m == 0 { out[0] = 0 as u8; return 0 } 173 var r: i64 = rot 174 if r < 0 { r = 0 - r } 175 let target: i64 = r % m 176 let tmp: *u8 = sys_mmap(128) 177 var seen: i64 = 0 178 var ls: i64 = 0 179 while ls < ilen { 180 let le: i64 = ad_eol(inv, ls, ilen) 181 if le > ls { 182 if le - ls < 1536 { 183 let row: *u8 = ((inv as i64) + ls) as *u8 184 if row[0] != (64 as u8) { 185 aslot_field_b(row, le - ls, 4, tmp, 128) 186 if ad_streq(tmp, section) == 1 { 187 if seen == target { 188 var o: i64 = 0 189 var k: i64 = ls 190 while k < le { if o < cap - 1 { out[o] = inv[k]; o = o + 1 } k = k + 1 } 191 out[o] = 0 as u8 192 return 1 193 } 194 seen = seen + 1 195 } 196 } 197 } 198 } 199 ls = le + 1 200 } 201 return 0 202} 203 204// MAIN: build the labeled slot html for this request. Returns bytes written (0 = no slot). 205// idout (cap>=128) receives the picked ad id for the served-impression log. cap must be >= 6144. 206func aslot_html(inv: *u8, ilen: i64, path: *u8, pn: i64, rot: i64, out: *u8, cap: i64, idout: *u8) -> i64 { 207 idout[0] = 0 as u8 208 if cap < 6144 { return 0 } 209 if ilen <= 0 { return 0 } 210 let sec: *u8 = sys_mmap(128) 211 aslot_section_of(path, pn, sec, 128) 212 let row: *u8 = sys_mmap(2048) 213 var got: i64 = aslot_pick_b(inv, ilen, sec, rot, row, 2048) 214 if got == 0 { got = aslot_pick_b(inv, ilen, "any" as *u8, rot, row, 2048) } 215 if got == 0 { return 0 } 216 let rlen: i64 = ad_slen(row) 217 let id: *u8 = sys_mmap(128) 218 let adv: *u8 = sys_mmap(256) 219 let img: *u8 = sys_mmap(1024) 220 aslot_field_b(row, rlen, 0, id, 128) 221 aslot_field_b(row, rlen, 1, adv, 256) 222 aslot_field_b(row, rlen, 2, img, 1024) 223 let clk: *u8 = sys_mmap(1024) 224 aslot_field_b(row, rlen, 3, clk, 1024) 225 if aslot_id_ok(id) == 0 { return 0 } 226 if aslot_img_ok(img) == 0 { return 0 } 227 if aslot_click_ok(clk) == 0 { return 0 } 228 var o: i64 = 0 229 o = ad_cat(out, o, "<aside class=\"nx-ad-slot\" aria-label=\"Advertisement\" style=\"grid-column:1/-1;max-width:768px;margin:18px auto 10px;padding:4px;text-align:center\">" as *u8) 230 o = ad_cat(out, o, "<div style=\"font:600 10px system-ui,sans-serif;color:gray;letter-spacing:.6px;margin-bottom:3px\">AD &middot; BROUGHT TO YOU BY " as *u8) 231 o = aslot_cat_esc(out, o, adv, cap) 232 o = ad_cat(out, o, "</div><a rel=\"noopener nofollow sponsored\" href=\"/ad/click/" as *u8) 233 o = ad_cat(out, o, id) 234 o = ad_cat(out, o, "\"><img src=\"" as *u8) 235 o = ad_cat(out, o, img) 236 o = ad_cat(out, o, "\" alt=\"" as *u8) 237 o = aslot_cat_esc(out, o, adv, cap) 238 o = ad_cat(out, o, "\" style=\"max-width:100%;height:auto;border:0\" width=\"728\" height=\"90\" loading=\"lazy\"></a></aside>" as *u8) 239 if o + 2 >= cap { idout[0] = 0 as u8; return 0 } 240 out[o] = 0 as u8 241 var k2: i64 = 0 242 while id[k2] != (0 as u8) { idout[k2] = id[k2]; k2 = k2 + 1 } 243 idout[k2] = 0 as u8 244 return o 245} 246 247// BOUNDED click-target resolve (nx_adnet contract field 3), validated separately by the caller. 248func aslot_click_target_b(inv: *u8, ilen: i64, ad_id: *u8, out: *u8, cap: i64) -> i64 { 249 let tmp: *u8 = sys_mmap(128) 250 var ls: i64 = 0 251 while ls < ilen { 252 let le: i64 = ad_eol(inv, ls, ilen) 253 if le > ls { 254 if le - ls < 1536 { 255 let row: *u8 = ((inv as i64) + ls) as *u8 256 if row[0] != (64 as u8) { 257 aslot_field_b(row, le - ls, 0, tmp, 128) 258 if ad_streq(tmp, ad_id) == 1 { aslot_field_b(row, le - ls, 3, out, cap); return 1 } 259 } 260 } 261 } 262 ls = le + 1 263 } 264 out[0] = 0 as u8 265 return 0 266} 267 268// If path is /ad/click/<id>: emit a FULL first-party 302 response into out; idout <- id when a real 269// target resolved (so the caller logs the click). Unknown/invalid id -> 302 to / (fail-closed, no 500). 270// Returns response bytes, or 0 when the path is not an ad click (caller continues its cascade). 271func aslot_click_resp(inv: *u8, ilen: i64, path: *u8, pn: i64, out: *u8, cap: i64, idout: *u8) -> i64 { 272 idout[0] = 0 as u8 273 if aslot_starts(path, "/ad/click/" as *u8) == 0 { return 0 } 274 if cap < 2048 { return 0 } 275 let id: *u8 = sys_mmap(128) 276 var i: i64 = 10 277 var j: i64 = 0 278 while i < pn { if j < 100 { id[j] = path[i]; j = j + 1 } i = i + 1 } 279 id[j] = 0 as u8 280 let url: *u8 = sys_mmap(1024) 281 var tgt: *u8 = "/" as *u8 282 if aslot_id_ok(id) == 1 { 283 if ilen > 0 { 284 let found: i64 = aslot_click_target_b(inv, ilen, id, url, 1024) 285 if found == 1 { 286 if aslot_click_ok(url) == 1 { 287 tgt = url 288 var k: i64 = 0 289 while id[k] != (0 as u8) { idout[k] = id[k]; k = k + 1 } 290 idout[k] = 0 as u8 291 } 292 } 293 } 294 } 295 var o: i64 = 0 296 o = ad_cat(out, o, "HTTP/1.1 302 Found\r\nLocation: " as *u8) 297 o = ad_cat(out, o, tgt) 298 o = ad_cat(out, o, "\r\nContent-Length: 0\r\nConnection: keep-alive\r\nCache-Control: no-store\r\nX-Robots-Tag: noindex\r\n\r\n" as *u8) 299 return o 300}