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}