code wiki / _hdl_build / nx_cms_migrate.nx

nx_cms_migrate.nx source

↩ module page · 177 lines · 7300 B

1// nx_cms_migrate.nx -- CMS IMPORT / EXPORT / MIGRATE capability (W-RE, the import-export-migrate class). 2// Sovereign site portability with no lock-in: composes the real nx_cms_store serialization (@key\nvalue, 3// already portable + auditable) with nx_sha256 content-integrity, plus a cross-format importer that maps a 4// foreign WordPress-style export (post_title/post_content/post_status) into the native store. 5// - cms_migrate_export : wrap a store in a self-describing, integrity-stamped bundle (@@NXEXPORT v1 ...) 6// - cms_migrate_import : verify the bundle's sha256 then unwrap -> REFUSES a tampered/corrupt bundle 7// (the exceed over all-in-one-migration, which silently corrupts on size/format) 8// - cms_migrate_wp_import : migrate FROM WordPress (foreign schema -> native cms_store keys + values) 9// module: nishi-core.cms.migrate 10// depends: nishi-core.cms.store + nishi-core.crypto.sha256 + nishi-core.io.syscalls 11// capability: CMS 12// license_tier: ORIGINAL 13import "nx_cms_store.nx" 14import "nx_sha256.nx" 15import "nx_syscalls.nx" 16const K_MAGIC_2048: i64 = 2048 17 18func cmm_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 19 20func cmm_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var k: i64=0; while s[k]!=(0 as u8){dst[o]=s[k];o=o+1;k=k+1} return o } 21 22func cmm_catnum(dst: *u8, off: i64, v: i64) -> i64 { 23 var o: i64=off; let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; var k: i64=0 24 if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1} 25 var i: i64=0; while i<k {dst[o]=t[k-1-i]; o=o+1; i=i+1} return o 26} 27 28func cmm_hex(dig: *u8, out: *u8) -> i64 { 29 let hx: *u8 = "0123456789abcdef" as *u8 30 var i: i64 = 0 31 while i < 32 { 32 out[i*2] = hx[((dig[i] as i64) >> 4) & 15] 33 out[i*2+1] = hx[(dig[i] as i64) & 15] 34 i = i + 1 35 } 36 out[64] = 0 as u8 37 return 64 38} 39 40// first offset of NUL-terminated needle in hay[0..n), or -1 41func cmm_find(hay: *u8, n: i64, needle: *u8) -> i64 { 42 let nl: i64 = cmm_slen(needle) 43 var i: i64 = 0 44 while i + nl <= n { 45 var q: i64 = 0 46 var ok: i64 = 1 47 while q < nl { if (hay[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nl } q = q + 1 } 48 if ok == 1 { return i } 49 i = i + 1 50 } 51 return 0 - 1 52} 53 54// decimal value starting at buf[start], stopping at first non-digit / end 55func cmm_pdec(buf: *u8, start: i64, n: i64) -> i64 { 56 var v: i64 = 0 57 var i: i64 = start 58 var go: i64 = 1 59 while go == 1 { 60 if i >= n { go = 0 } else { 61 let c: i64 = buf[i] as i64 62 if c < 48 { go = 0 } else { if c > 57 { go = 0 } else { v = v*10 + (c - 48); i = i + 1 } } 63 } 64 } 65 return v 66} 67 68// exact key compare: buf[off..off+kl) == lit (lit must be exactly kl long) 69func cmm_keyeq(buf: *u8, off: i64, kl: i64, lit: *u8) -> i64 { 70 var i: i64 = 0 71 while i < kl { if (buf[off+i] as i64) != (lit[i] as i64) { return 0 } i = i + 1 } 72 if (lit[kl] as i64) != 0 { return 0 } 73 return 1 74} 75 76// ===== EXPORT: store -> integrity-stamped portable bundle ========= 77func cms_migrate_export(store: *u8, n: i64, out: *u8, cap: i64) -> i64 { 78 let dig: *u8 = sys_mmap(32) 79 sha256_digest(store, n, dig) 80 let hex: *u8 = sys_mmap(72) 81 cmm_hex(dig, hex) 82 var o: i64 = 0 83 o = cmm_cat(out, o, "@@NXEXPORT v1 n=" as *u8) 84 o = cmm_catnum(out, o, n) 85 o = cmm_cat(out, o, " sha=" as *u8) 86 o = cmm_cat(out, o, hex) 87 out[o] = 10 as u8; o = o + 1 88 var i: i64 = 0 89 while i < n { if o < cap { out[o] = store[i]; o = o + 1 } i = i + 1 } 90 return o 91} 92 93// ===== IMPORT: verify integrity then unwrap ====================== 94// Returns the unwrapped store length, or -1 if the bundle is malformed, 95// the length disagrees, or the sha256 does not match (corrupt/tampered). 96func cms_migrate_import(bundle: *u8, bn: i64, out_store: *u8, cap: i64) -> i64 { 97 if cmm_find(bundle, bn, "@@NXEXPORT" as *u8) != 0 { return 0 - 1 } 98 let np: i64 = cmm_find(bundle, bn, " n=" as *u8) 99 if np < 0 { return 0 - 1 } 100 let expect_n: i64 = cmm_pdec(bundle, np + 3, bn) 101 let sp: i64 = cmm_find(bundle, bn, " sha=" as *u8) 102 if sp < 0 { return 0 - 1 } 103 let sha_off: i64 = sp + 5 104 // header ends at the first newline 105 var he: i64 = 0 - 1 106 var fi: i64 = 0 107 while fi < bn { if (bundle[fi] as i64) == 10 { he = fi; fi = bn } else { fi = fi + 1 } } 108 if he < 0 { return 0 - 1 } 109 if sha_off + 64 > he { return 0 - 1 } 110 let body_off: i64 = he + 1 111 let body_len: i64 = bn - body_off 112 if body_len != expect_n { return 0 - 1 } 113 if body_len < 0 { return 0 - 1 } 114 let dig: *u8 = sys_mmap(32) 115 sha256_digest((bundle as i64 + body_off) as *u8, body_len, dig) 116 let hex: *u8 = sys_mmap(72) 117 cmm_hex(dig, hex) 118 var q: i64 = 0 119 while q < 64 { if (bundle[sha_off + q] as i64) != (hex[q] as i64) { return 0 - 1 } q = q + 1 } 120 if body_len > cap { return 0 - 1 } 121 var c: i64 = 0 122 while c < body_len { out_store[c] = bundle[body_off + c]; c = c + 1 } 123 return body_len 124} 125 126// map a WP field name -> native cms_store key; returns native key ptr or 0 127func cms_migrate_map_key(buf: *u8, off: i64, kl: i64) -> *u8 { 128 if cmm_keyeq(buf, off, kl, "post_title" as *u8) == 1 { return "title" as *u8 } 129 if cmm_keyeq(buf, off, kl, "post_content" as *u8) == 1 { return "body" as *u8 } 130 if cmm_keyeq(buf, off, kl, "post_status" as *u8) == 1 { return "status" as *u8 } 131 return 0 as *u8 132} 133 134// ===== MIGRATE: foreign WordPress-style export -> native store ==== 135// Input lines "key: value\n". Mapped fields become native @key records; 136// the WP status value "publish" is migrated to the native "published". 137func cms_migrate_wp_import(wp: *u8, wn: i64, out: *u8, cap: i64) -> i64 { 138 let tmp: *u8 = sys_mmap(cap) 139 let valb: *u8 = sys_mmap(K_MAGIC_2048) 140 var n: i64 = 0 141 var i: i64 = 0 142 while i < wn { 143 // line span [i, le) 144 var le: i64 = i 145 var sc: i64 = 1 146 while sc == 1 { if le >= wn { sc = 0 } else { if (wp[le] as i64) == 10 { sc = 0 } else { le = le + 1 } } } 147 // find ':' within the line 148 var kc: i64 = 0 - 1 149 var j: i64 = i 150 while j < le { if (wp[j] as i64) == 58 { kc = j; j = le } else { j = j + 1 } } 151 if kc >= 0 { 152 let kl: i64 = kc - i 153 var vs: i64 = kc + 1 154 if vs < le { if (wp[vs] as i64) == 32 { vs = vs + 1 } } 155 let vl: i64 = le - vs 156 let native: *u8 = cms_migrate_map_key(wp, i, kl) 157 if native != (0 as *u8) { 158 // copy value 159 var vc: i64 = 0 160 while vc < vl { valb[vc] = wp[vs + vc]; vc = vc + 1 } 161 valb[vl] = 0 as u8 162 var fvl: i64 = vl 163 // WP status value migration: publish -> published 164 if cmm_keyeq(native, 0, 6, "status" as *u8) == 1 { 165 if cmm_keyeq(valb, 0, vl, "publish" as *u8) == 1 { 166 fvl = cmm_cat(valb, 0, "published" as *u8) 167 } 168 } 169 n = cst_set(out, n, native, valb, fvl, tmp, cap) 170 var cc: i64 = 0 171 while cc < n { out[cc] = tmp[cc]; cc = cc + 1 } 172 } 173 } 174 i = le + 1 175 } 176 return n 177}