code wiki / (root) / nx_archive_site_viewer.nx

nx_archive_site_viewer.nx source

↩ module page · 224 lines · 18221 B

1// nx_archive_site_viewer.nx -- DOMAIN-GENERAL time-machine viewer with LOCAL MEDIA (no broken images). Reads the 2// target host from web_assets/archive/_target.txt (default page3.com), fetches its Wayback CDX, picks the EARLIEST 3// status-200 capture, reconstructs it from the capture's OWN original URL, then FETCHES every referenced image from 4// the archive, stores them under web_assets/archive/<host>/media/, and rewrites the page's <img src> to those local 5// copies (transparent-pixel placeholder for any image the archive doesn't have -> never a broken icon). Output: 6// web_assets/archive/<host>/{index.html,page.html,media/*}. Served by nx_archive_server. Fetch stack, cache-first, 7// status-aware retry. license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_x509_trust_store.nx" 10import "nx_trust_store_load_from_certdata.nx" 11import "nx_https_fetch_follow.nx" 12import "nx_cdx_parse.nx" 13const K_MAGIC_4194304: i64 = 4194304 14const K_MAGIC_1024: i64 = 1024 15const K_MAGIC_4096: i64 = 4096 16const K_MAGIC_2048: i64 = 2048 17const K_MAGIC_65536: i64 = 65536 18 19func gw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 20func gn(v: i64) -> i64 { if v==0 { sys_write(1,"0" as *u8,1); return 0 } var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } let t: *u8=sys_mmap(24); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } let o: *u8=sys_mmap(24); var w: i64=0; var q: i64=k-1; while q>=0 { o[w]=t[q]; w=w+1; q=q-1 } sys_write(1,o,w); return 0 } 21func ap(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { buf[off+i]=s[i]; i=i+1 } return off+i } 22func apn(buf: *u8, off: i64, v: i64) -> i64 { if v==0 { buf[off]=0x30 as u8; return off+1 } var m: i64=v; if m<0 { buf[off]=0x2d as u8; off=off+1; m=0-m } let t: *u8=sys_mmap(24); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } var o: i64=off; var q: i64=k-1; while q>=0 { buf[o]=t[q]; o=o+1; q=q-1 } return o } 23func apslice(dst: *u8, off: i64, src: *u8, so: i64, sl: i64) -> i64 { var i: i64=0; while i<sl { dst[off+i]=src[so+i]; i=i+1 } return off+sl } 24func dstarts(buf: *u8, off: i64, n: i64, lit: *u8, litlen: i64) -> i64 { if off+litlen>n { return 0 } var i: i64=0; while i<litlen { var a: i64=buf[off+i] as i64; if a>=0x41 { if a<=0x5a { a=a+0x20 } } if a != (lit[i] as i64) { return 0 } i=i+1 } return 1 } 25func esc_html(dst: *u8, doff: i64, src: *u8, n: i64) -> i64 { var o: i64=doff; var i: i64=0; while i<n { let c: i64=src[i] as i64; if c==0x3c { o=ap(dst,o,"&lt;" as *u8) } else { if c==0x3e { o=ap(dst,o,"&gt;" as *u8) } else { if c==0x26 { o=ap(dst,o,"&amp;" as *u8) } else { dst[o]=c as u8; o=o+1 } } } i=i+1 } return o } 26func save_file(path: *u8, buf: *u8, n: i64) -> i64 { let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 { return 0 } var off: i64=0; while off<n { let w: i64=sys_write(fd, ((buf as i64)+off) as *u8, n-off); if w<=0 { sys_close(fd); return 0 } off=off+w } sys_close(fd); return 1 } 27func read_file(path: *u8, buf: *u8, cap: i64, lenbox: *i64) -> i64 { let fd: i64=sys_openat_rd(path); if fd<0 { return 0 } let n: i64=sys_read(fd, buf, cap); sys_close(fd); if n<=0 { return 0 } lenbox[0]=n; return n } 28func is_image(buf: *u8, n: i64) -> i64 { if n<4 { return 0 } let a: i64=buf[0] as i64; let b: i64=buf[1] as i64; let c: i64=buf[2] as i64; let d: i64=buf[3] as i64; if a==0x47 { if b==0x49 { if c==0x46 { return 1 } } } if a==0xff { if b==0xd8 { if c==0xff { return 1 } } } if a==0x89 { if b==0x50 { if c==0x4e { if d==0x47 { return 1 } } } } if a==0x42 { if b==0x4d { return 1 } } return 0 } 29func fetch_retry(url: *u8, store: *TrustStore, out: *u8, cap: i64, st: *i64, tries: i64) -> i64 { var t: i64=0; var n: i64=0-1; while t<tries { n=nx_https_fetch_follow(url, store, out, cap, 6, st); if n>0 { if st[0]==200 { t=tries } else { t=t+1 } } else { t=t+1 } } return n } 30func mkpath(dst: *u8, a: *u8, b: *u8) -> i64 { var o: i64=ap(dst,0,a); o=ap(dst,o,b); dst[o]=0 as u8; return o } 31func next_imgsrc(buf: *u8, n: i64, from: i64, out2: *i64) -> i64 { var i: i64=from; while i<n { if dstarts(buf, i, n, "src=" as *u8, 4)==1 { if i+4<n { if (buf[i+4] as i64)==0x22 { let us: i64=i+5; var ue: i64=us; var go: i64=1; while go==1 { if ue<n { if (buf[ue] as i64)==0x22 { go=0 } else { ue=ue+1 } } else { go=0 } } out2[0]=us; out2[1]=ue-us; return ue+1 } } } i=i+1 } return 0-1 } 32// lowercase extension (<=4 alnum) of url[off,off+len) into extbuf; returns length (0 if none). 33func ext_of(buf: *u8, off: i64, len: i64, extbuf: *u8) -> i64 { 34 var dot: i64=0-1; var i: i64=off 35 while i<off+len { if (buf[i] as i64)==0x2e { dot=i } i=i+1 } 36 if dot<0 { return 0 } 37 var o: i64=0; var j: i64=dot+1 38 while j<off+len { if o<4 { var c: i64=buf[j] as i64; var ok: i64=0; if c>=0x30 { if c<=0x39 { ok=1 } } if c>=0x41 { if c<=0x5a { c=c+0x20; ok=1 } } if c>=0x61 { if c<=0x7a { ok=1 } } if ok==1 { extbuf[o]=c as u8; o=o+1 } else { j=off+len } } j=j+1 } 39 return o 40} 41 42func main() -> i64 { 43 gw("=== nx_archive_site_viewer (local-media): time machine for any domain ===\n" as *u8) 44 sys_mkdir("web_assets" as *u8, 0x1ed) 45 sys_mkdir("web_assets/archive" as *u8, 0x1ed) 46 let cap: i64=K_MAGIC_4194304 47 let lb: *i64=sys_mmap(8) as *i64 48 let st: *i64=sys_mmap(8) as *i64 49 50 let dbuf: *u8=sys_mmap(256) 51 var domain_len: i64=0 52 if read_file("web_assets/archive/_target.txt" as *u8, dbuf, 250, lb)>0 { var di: i64=0; var go: i64=1; while go==1 { if di<lb[0] { let c: i64=dbuf[di] as i64; if c<=0x20 { go=0 } else { di=di+1 } } else { go=0 } } domain_len=di } 53 if domain_len==0 { domain_len=ap(dbuf, 0, "page3.com" as *u8) } 54 dbuf[domain_len]=0 as u8 55 gw("host = " as *u8); gw(dbuf); gw("\n" as *u8) 56 57 let ddir: *u8=sys_mmap(512) 58 var dl: i64=ap(ddir, 0, "web_assets/archive/" as *u8); dl=ap(ddir, dl, dbuf); ddir[dl]=0 as u8 59 sys_mkdir(ddir, 0x1ed) 60 let mdir: *u8=sys_mmap(512); mkpath(mdir, ddir, "/media" as *u8); sys_mkdir(mdir, 0x1ed) 61 62 let r: i64=nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, K_MAGIC_4194304) 63 if r<=0 { gw("trust store load failed\n" as *u8); return 1 } 64 let store: *TrustStore=r as *TrustStore 65 let cdxbuf: *u8=sys_mmap(cap) 66 let pth: *u8=sys_mmap(K_MAGIC_1024) 67 68 mkpath(pth, ddir, "/_cache_cdx.txt" as *u8) 69 var ncdx: i64=read_file(pth, cdxbuf, cap, lb) 70 if ncdx>0 { gw("[1] CDX cache (" as *u8); gn(ncdx); gw(" B)\n" as *u8) } else { 71 let cu: *u8=sys_mmap(K_MAGIC_1024); var co: i64=ap(cu, 0, "https://web.archive.org/cdx/search/cdx?url=" as *u8); co=ap(cu, co, dbuf); co=ap(cu, co, "&limit=150" as *u8); cu[co]=0 as u8 72 gw("[1] fetch CDX " as *u8); gw(cu); gw("\n" as *u8) 73 ncdx=fetch_retry(cu, store, cdxbuf, cap, st, 6) 74 if ncdx<=0 { gw(" CDX fail\n" as *u8); return 1 } 75 if st[0]!=200 { gw(" rate-limited; retry shortly\n" as *u8); return 1 } 76 save_file(pth, cdxbuf, ncdx) 77 } 78 79 let RS: *i64=sys_mmap(K_MAGIC_4096) as *i64 80 let RE: *i64=sys_mmap(K_MAGIC_4096) as *i64 81 let nrows: i64=cdx_rows(cdxbuf, ncdx, RS, RE, 480) 82 let dates: *i64=sys_mmap(K_MAGIC_4096) as *i64 83 let tso: *i64=sys_mmap(K_MAGIC_4096) as *i64 84 let tsl: *i64=sys_mmap(K_MAGIC_4096) as *i64 85 let oro: *i64=sys_mmap(K_MAGIC_4096) as *i64 86 let orl: *i64=sys_mmap(K_MAGIC_4096) as *i64 87 let b2: *i64=sys_mmap(16) as *i64 88 var i: i64=0 89 var m: i64=0 90 while i<nrows { 91 cdx_field(cdxbuf, RS[i], RE[i], 4, b2) 92 var ok2: i64=0 93 if b2[1]==3 { if (cdxbuf[b2[0]] as i64)==0x32 { if (cdxbuf[b2[0]+1] as i64)==0x30 { if (cdxbuf[b2[0]+2] as i64)==0x30 { ok2=1 } } } } 94 if ok2==1 { cdx_field(cdxbuf, RS[i], RE[i], 1, b2); dates[m]=cdx_ts8(cdxbuf, b2[0], b2[1]); tso[m]=b2[0]; tsl[m]=b2[1]; cdx_field(cdxbuf, RS[i], RE[i], 2, b2); oro[m]=b2[0]; orl[m]=b2[1]; m=m+1 } 95 i=i+1 96 } 97 if m<=0 { gw(" no captures\n" as *u8); return 1 } 98 var q: i64=0; var qd: i64=dates[0]; var jj: i64=1 99 while jj<m { if dates[jj]<qd { qd=dates[jj]; q=jj } jj=jj+1 } 100 gw("[2] earliest capture " as *u8); gn(dates[q]); gw("\n" as *u8) 101 102 let cont: *u8=sys_mmap(cap) 103 mkpath(pth, ddir, "/_cache_page.txt" as *u8) 104 var ncont: i64=read_file(pth, cont, cap, lb) 105 if ncont>0 { gw("[3] page cache (" as *u8); gn(ncont); gw(" B)\n" as *u8) } else { 106 let url: *u8=sys_mmap(K_MAGIC_2048); var uo: i64=ap(url, 0, "https://web.archive.org/web/" as *u8); uo=apslice(url, uo, cdxbuf, tso[q], tsl[q]); uo=ap(url, uo, "id_/" as *u8); uo=apslice(url, uo, cdxbuf, oro[q], orl[q]); url[uo]=0 as u8 107 gw("[3] fetch page\n" as *u8) 108 ncont=fetch_retry(url, store, cont, cap, st, 6) 109 if ncont<=0 { gw(" page fail\n" as *u8); return 1 } 110 if st[0]!=200 { gw(" rate-limited; retry shortly\n" as *u8); return 1 } 111 save_file(pth, cont, ncont) 112 } 113 114 // -- LOCAL MEDIA: fetch each <img src>, store under <host>/media/, record local name (or 0 if absent) -- 115 let names: *i64=sys_mmap(64*8) as *i64 116 let namel: *i64=sys_mmap(64*8) as *i64 117 let uoffs_a: *i64=sys_mmap(64*8) as *i64 118 let ulens_a: *i64=sys_mmap(64*8) as *i64 119 let imgbuf: *u8=sys_mmap(cap) 120 let o2: *i64=sys_mmap(16) as *i64 121 let ext: *u8=sys_mmap(16) 122 var from: i64=0 123 var idx: i64=0 124 // base of the capture's original URL (up to last '/') for resolving relative <img src> 125 var obase_off: i64=oro[q] 126 var obls: i64=oro[q]+orl[q] 127 var ox: i64=oro[q] 128 while ox<oro[q]+orl[q] { if (cdxbuf[ox] as i64)==0x2f { obls=ox } ox=ox+1 } 129 var obase_len: i64=obls-oro[q]+1 130 gw("[4] localizing media ...\n" as *u8) 131 var more: i64=1 132 while more==1 { 133 let p: i64=next_imgsrc(cont, ncont, from, o2) 134 if p<0 { more=0 } else { 135 from=p 136 if idx<24 { 137 let uoff: i64=o2[0]; let ulen: i64=o2[1] 138 names[idx]=0; namel[idx]=0 139 var dup: i64=0-1; var dj: i64=0 140 while dj<idx { if ulens_a[dj]==ulen { var eq: i64=1; var dk: i64=0; while dk<ulen { if (cont[uoffs_a[dj]+dk] as i64) != (cont[uoff+dk] as i64) { eq=0; dk=ulen } else { dk=dk+1 } } if eq==1 { dup=dj; dj=idx } } dj=dj+1 } 141 uoffs_a[idx]=uoff; ulens_a[idx]=ulen 142 if dup>=0 { names[idx]=names[dup]; namel[idx]=namel[dup] } else { 143 let el: i64=ext_of(cont, uoff, ulen, ext); ext[el]=0 as u8 144 let lname: *u8=sys_mmap(64) 145 var lo: i64=ap(lname, 0, "media/img" as *u8); lo=apn(lname, lo, idx) 146 if el>0 { lname[lo]=0x2e as u8; lo=lo+1; lo=ap(lname, lo, ext) } 147 lname[lo]=0 as u8 148 let sp: *u8=sys_mmap(512); var spo: i64=ap(sp, 0, ddir); sp[spo]=0x2f as u8; spo=spo+1; spo=ap(sp, spo, lname); sp[spo]=0 as u8 149 var fn: i64=read_file(sp, imgbuf, cap, lb) 150 if fn>0 { fn=lb[0] } else { 151 // resolve the absolute image URL 152 let rimg: *u8=sys_mmap(K_MAGIC_2048); var ro: i64=0 153 if dstarts(cont, uoff, uoff+ulen, "http" as *u8, 4)==1 { ro=apslice(rimg, 0, cont, uoff, ulen) } else { ro=apslice(rimg, 0, cdxbuf, obase_off, obase_len); var us2: i64=uoff; if ulen>=2 { if (cont[uoff] as i64)==0x2e { if (cont[uoff+1] as i64)==0x2f { us2=uoff+2 } } } if us2==uoff { if (cont[uoff] as i64)==0x2f { us2=uoff+1 } } ro=apslice(rimg, ro, cont, us2, ulen-(us2-uoff)) } 154 rimg[ro]=0 as u8 155 // primary: the image AT this capture's timestamp 156 let iu: *u8=sys_mmap(K_MAGIC_2048); var io: i64=ap(iu, 0, "https://web.archive.org/web/" as *u8); io=apslice(iu, io, cdxbuf, tso[q], tsl[q]); io=ap(iu, io, "id_/" as *u8); io=ap(iu, io, rimg); iu[io]=0 as u8 157 fn=fetch_retry(iu, store, imgbuf, cap, st, 3) 158 if fn>0 { if st[0]==200 { if is_image(imgbuf, fn)==1 { } else { fn=0 } } else { fn=0 } } else { fn=0 } 159 // CROSS-TEMPORAL RECOVERY: gap at this date -> find the image at ANY date it was captured 160 if fn==0 { 161 let cq: *u8=sys_mmap(K_MAGIC_2048); var qo: i64=ap(cq, 0, "https://web.archive.org/cdx/search/cdx?url=" as *u8); qo=ap(cq, qo, rimg); qo=ap(cq, qo, "&limit=1&filter=statuscode:200" as *u8); cq[qo]=0 as u8 162 let cr: *u8=sys_mmap(K_MAGIC_65536) 163 let cn: i64=fetch_retry(cq, store, cr, K_MAGIC_65536, st, 2) 164 if cn>10 { if st[0]==200 { 165 let R1: *i64=sys_mmap(64) as *i64; let R2: *i64=sys_mmap(64) as *i64 166 let nr: i64=cdx_rows(cr, cn, R1, R2, 4) 167 if nr>0 { 168 let tb: *i64=sys_mmap(16) as *i64 169 cdx_field(cr, R1[0], R2[0], 1, tb) 170 let ru: *u8=sys_mmap(K_MAGIC_2048); var rio: i64=ap(ru, 0, "https://web.archive.org/web/" as *u8); rio=apslice(ru, rio, cr, tb[0], tb[1]); rio=ap(ru, rio, "id_/" as *u8); rio=ap(ru, rio, rimg); ru[rio]=0 as u8 171 fn=fetch_retry(ru, store, imgbuf, cap, st, 2) 172 if fn>0 { if st[0]==200 { if is_image(imgbuf, fn)==1 { gw(" img" as *u8); gn(idx); gw(" RECOVERED from another date\n" as *u8) } else { fn=0 } } else { fn=0 } } else { fn=0 } 173 } 174 } } 175 } 176 if fn>0 { save_file(sp, imgbuf, fn) } 177 } 178 if fn>0 { names[idx]=lname as i64; namel[idx]=lo; gw(" img" as *u8); gn(idx); gw(" ok " as *u8); gn(fn); gw("B\n" as *u8) } else { gw(" img" as *u8); gn(idx); gw(" miss\n" as *u8) } 179 } 180 idx=idx+1 181 } else { more=0 } 182 } 183 } 184 let nmedia: i64=idx 185 186 // -- page.html: rewrite each <img src> to its local copy (or transparent placeholder) -- 187 let pbuf: *u8=sys_mmap(cap) 188 var po: i64=ap(pbuf, 0, "<!DOCTYPE html><meta charset=utf-8><style>html,body{margin:0;padding:8px;background:#fff;color:#000;font-family:Times,Georgia,serif}img{max-width:100%;height:auto}table{max-width:100%}</style>" as *u8) 189 var ri: i64=0 190 var rj: i64=0 191 while rj<ncont { 192 var handled: i64=0 193 if dstarts(cont, rj, ncont, "src=" as *u8, 4)==1 { if rj+4<ncont { if (cont[rj+4] as i64)==0x22 { 194 po=ap(pbuf, po, "src=" as *u8); pbuf[po]=0x22 as u8; po=po+1 195 var nm: i64=0 196 if ri<nmedia { nm=names[ri] } 197 if nm != 0 { po=apslice(pbuf, po, nm as *u8, 0, namel[ri]) } else { po=ap(pbuf, po, "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" as *u8) } 198 ri=ri+1 199 rj=rj+5 200 var go: i64=1 201 while go==1 { if rj<ncont { if (cont[rj] as i64)==0x22 { go=0 } else { rj=rj+1 } } else { go=0 } } 202 handled=1 203 } } } 204 if handled==0 { pbuf[po]=cont[rj] as u8; po=po+1; rj=rj+1 } 205 } 206 mkpath(pth, ddir, "/page.html" as *u8) 207 if save_file(pth, pbuf, po)==0 { gw("page.html fail\n" as *u8); return 1 } 208 209 // -- index.html dashboard -- 210 let ob: *u8=sys_mmap(cap) 211 var o: i64=0 212 o=ap(ob,o,"<!DOCTYPE html><html><head><meta charset=utf-8><title>Nishi Time Machine - " as *u8); o=ap(ob,o,dbuf); o=ap(ob,o,"</title><style>body{font-family:system-ui,Arial,sans-serif;margin:0;background:#0e0f13;color:#e7e7ea}.wrap{max-width:1040px;margin:0 auto;padding:28px}h1{font-size:26px;margin:0 0 4px}.sub{color:#8a8f9a;margin:0 0 18px}.prov{background:#171a21;border:1px solid #2a2d36;border-radius:10px;padding:16px 18px;line-height:1.6}.prov b{color:#7bd88f}h3{color:#cdd}.tl{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0;max-height:140px;overflow:auto;background:#0b0c10;border:1px solid #2a2d36;border-radius:8px;padding:10px}.tl a{background:#23262f;border:1px solid #343846;border-radius:5px;padding:3px 8px;font-size:12px;color:#7bd8f0;text-decoration:none}.tl a:hover{background:#2f5d40;color:#fff}iframe{width:100%;height:540px;border:1px solid #2a2d36;border-radius:10px;background:#fff}pre{white-space:pre-wrap;word-break:break-all;background:#0b0c10;border:1px solid #2a2d36;border-radius:10px;padding:14px;font-size:12px;color:#9fe6c8;max-height:320px;overflow:auto}</style></head><body><div class=wrap>" as *u8) 213 o=ap(ob,o,"<h1>&#128336; Nishi Time Machine</h1><p class=sub>" as *u8); o=ap(ob,o,dbuf); o=ap(ob,o," &mdash; reconstructed with LOCAL media (served by our own stack; no broken images)</p>" as *u8) 214 o=ap(ob,o,"<div class=prov><b>What we pulled</b><br>CDX index " as *u8); o=apn(ob,o,ncdx); o=ap(ob,o," B &middot; <b>" as *u8); o=apn(ob,o,m); o=ap(ob,o,"</b> captures &middot; earliest <b>" as *u8); o=apn(ob,o,dates[q]); o=ap(ob,o,"</b> &middot; reconstructed page " as *u8); o=apn(ob,o,ncont); o=ap(ob,o," B &middot; <b>" as *u8); o=apn(ob,o,nmedia); o=ap(ob,o,"</b> media localized</div>" as *u8) 215 o=ap(ob,o,"<h3>Scroll the history &mdash; click any date to load that snapshot</h3><div class=tl>" as *u8) 216 var d: i64=0; while d<m { if d<130 { o=ap(ob,o,"<a target=tm href='https://web.archive.org/web/" as *u8); o=apslice(ob,o,cdxbuf,tso[d],tsl[d]); o=ap(ob,o,"/" as *u8); o=apslice(ob,o,cdxbuf,oro[d],orl[d]); o=ap(ob,o,"'>" as *u8); o=apn(ob,o,dates[d]); o=ap(ob,o,"</a>" as *u8) } d=d+1 } 217 o=ap(ob,o,"</div><p class=sub>Default frame below = OUR sovereign reconstruction (local media). Click a date to load that snapshot from the archive.</p><iframe name=tm src=page.html></iframe>" as *u8) 218 o=ap(ob,o,"<h3>Raw archived HTML</h3><pre>" as *u8); o=esc_html(ob, o, cont, ncont); o=ap(ob,o,"</pre><div class=prov>Source: Internet Archive Wayback Machine &middot; fetched, decoded &amp; served sovereignly by Nishi</div></div></body></html>" as *u8) 219 mkpath(pth, ddir, "/index.html" as *u8) 220 if save_file(pth, ob, o)==0 { gw("index.html fail\n" as *u8); return 1 } 221 222 gw("\nSITE-VIEWER DONE: " as *u8); gn(nmedia); gw(" media localized -> web_assets/archive/" as *u8); gw(dbuf); gw("/index.html\n" as *u8) 223 return 0 224}