code wiki / _hdl_build / nx_galx_shell_jslint.nx
nx_galx_shell_jslint.nx source
↩ module page · 192 lines · 8356 B
1// nx_galx_shell_jslint.nx -- SOVEREIGN JS syntax sanity guard for the gallery shell's inline <script>.
2//
3// WHY: the gallery JS broke (operator: "its not working", browser: "Uncaught SyntaxError: Invalid or unexpected
4// token") because an HTML attribute with single quotes (href='#') was embedded inside a single-quoted JS string
5// (lb.innerHTML='...href='#'...') -> the string terminated early and the '#' was a stray token. There is NO JS
6// engine in this sovereign environment, so nothing caught it. This is a tiny string state-machine that catches
7// exactly that class (premature string termination -> stray token after a string) plus unbalanced strings/
8// brackets. NOT a full JS parser -- a focused, no-false-positive guard for hand-written shell JS.
9//
10// LIMITATION (documented): regex literals are treated as division; this is balance-neutral and produces no false
11// positive for regexes without embedded quotes (the gallery's only regex is /[<&>]/g). A regex literal containing
12// a quote would need real regex handling -- add it the day the shell needs one.
13// license_tier: ORIGINAL
14import "nx_syscalls.nx"
15const K_MAGIC_2048: i64 = 2048
16
17// Scan a JS source [0..n). Returns the byte offset of the first syntax problem, or -1 if clean.
18// States: 0=CODE 1=SQ('...') 2=DQ("...") 3=TMPL(`...`) 4=line-comment 5=block-comment.
19func jslint_scan_script(s: *u8, n: i64) -> i64 {
20 var st: i64 = 0
21 var depth: i64 = 0 // ()[]{} nesting
22 var jcs: i64 = 0 // 1 = we just closed a string; the next non-space CODE char must be an operator/punct
23 var i: i64 = 0
24 while i < n {
25 let c: i64 = s[i] as i64
26 if st == 0 {
27 if c == 47 { // '/'
28 if i + 1 < n {
29 let d: i64 = s[i+1] as i64
30 if d == 47 { st = 4; i = i + 2; jcs = 0; continue }
31 if d == 42 { st = 5; i = i + 2; jcs = 0; continue }
32 }
33 jcs = 0; i = i + 1; continue // division operator
34 }
35 if c == 39 { st = 1; jcs = 0; i = i + 1; continue }
36 if c == 34 { st = 2; jcs = 0; i = i + 1; continue }
37 if c == 96 { st = 3; jcs = 0; i = i + 1; continue }
38 if c == 40 { depth = depth + 1; jcs = 0; i = i + 1; continue } // (
39 if c == 91 { depth = depth + 1; jcs = 0; i = i + 1; continue } // [
40 if c == 123 { depth = depth + 1; jcs = 0; i = i + 1; continue } // {
41 if c == 41 { depth = depth - 1; if depth < 0 { return i } jcs = 0; i = i + 1; continue } // )
42 if c == 93 { depth = depth - 1; if depth < 0 { return i } jcs = 0; i = i + 1; continue } // ]
43 if c == 125 { depth = depth - 1; if depth < 0 { return i } jcs = 0; i = i + 1; continue } // }
44 if c == 32 { i = i + 1; continue } // keep jcs across whitespace
45 if c == 9 { i = i + 1; continue }
46 if c == 10 { i = i + 1; continue }
47 if c == 13 { i = i + 1; continue }
48 if jcs == 1 {
49 // stray identifier-start / digit / '#' / '@' immediately after a string with no operator = the bug
50 if c >= 65 { if c <= 90 { return i } }
51 if c >= 97 { if c <= 122 { return i } }
52 if c == 95 { return i }
53 if c == 36 { return i }
54 if c == 35 { return i }
55 if c == 64 { return i }
56 if c >= 48 { if c <= 57 { return i } }
57 }
58 jcs = 0; i = i + 1; continue
59 }
60 if st == 1 {
61 if c == 92 { i = i + 2; continue }
62 if c == 39 { st = 0; jcs = 1; i = i + 1; continue }
63 if c == 10 { return i } // newline inside a '...' string = unterminated
64 i = i + 1; continue
65 }
66 if st == 2 {
67 if c == 92 { i = i + 2; continue }
68 if c == 34 { st = 0; jcs = 1; i = i + 1; continue }
69 if c == 10 { return i }
70 i = i + 1; continue
71 }
72 if st == 3 {
73 if c == 92 { i = i + 2; continue }
74 if c == 96 { st = 0; jcs = 1; i = i + 1; continue }
75 i = i + 1; continue
76 }
77 if st == 4 {
78 if c == 10 { st = 0 }
79 i = i + 1; continue
80 }
81 if st == 5 {
82 if c == 42 { if i + 1 < n { if s[i+1] == (47 as u8) { st = 0; i = i + 2; continue } } }
83 i = i + 1; continue
84 }
85 i = i + 1
86 }
87 if st != 0 { return n } // unterminated string or comment
88 if depth != 0 { return n } // unbalanced ()[]{}
89 return 0 - 1
90}
91
92// Locate the FIRST <script>...</script> body in html[0..n). Returns body length (>=0) and sets *out_start to the
93// body offset; returns -1 if no <script> found.
94func jslint_find_script(html: *u8, n: i64, out_start: *i64) -> i64 {
95 var i: i64 = 0
96 while i + 8 <= n {
97 if html[i]==(60 as u8) { if html[i+1]==(115 as u8) { if html[i+2]==(99 as u8) { if html[i+3]==(114 as u8) {
98 if html[i+4]==(105 as u8) { if html[i+5]==(112 as u8) { if html[i+6]==(116 as u8) {
99 // found "<script"; skip to the next '>'
100 var j: i64 = i + 7
101 while j < n { if html[j]==(62 as u8) { j = j + 1; break } j = j + 1 }
102 let bs: i64 = j
103 // find "</script"
104 var k: i64 = bs
105 while k + 8 <= n {
106 if html[k]==(60 as u8) { if html[k+1]==(47 as u8) { if html[k+2]==(115 as u8) { if html[k+3]==(99 as u8) {
107 out_start[0] = bs
108 return k - bs
109 } } } }
110 k = k + 1
111 }
112 return 0 - 1
113 } } } } } } }
114 i = i + 1
115 }
116 return 0 - 1
117}
118
119// Returns 1 if html[0..n) is a COMPLETE page: starts with "<!doctype" (any case) AND the last non-space run is
120// "</html>". Catches a truncated / short-write shell (a blank or half-rendered gallery = a hidden failure).
121func shell_complete(html: *u8, n: i64) -> i64 {
122 if n < 16 { return 0 }
123 // find "<!doctype" within the first 2048 bytes (skips any HTTP headers the serve prepends to the body)
124 let dt: *u8 = "<!doctype" as *u8
125 var ds: i64 = 0 - 1
126 var p: i64 = 0
127 var plim: i64 = n - 9
128 if plim > K_MAGIC_2048 { plim = K_MAGIC_2048 }
129 while p <= plim {
130 var m: i64 = 1
131 var q: i64 = 0
132 while q < 9 {
133 var a: i64 = html[p+q] as i64
134 if a >= 65 { if a <= 90 { a = a + 32 } }
135 if a != (dt[q] as i64) { m = 0; q = 9 }
136 q = q + 1
137 }
138 if m == 1 { ds = p; p = plim + 1 } else { p = p + 1 }
139 }
140 if ds < 0 { return 0 }
141 var e: i64 = n
142 var go: i64 = 1
143 while go == 1 {
144 if e <= 0 { go = 0 } else {
145 let c: i64 = html[e-1] as i64
146 var ws: i64 = 0
147 if c == 32 { ws = 1 }
148 if c == 10 { ws = 1 }
149 if c == 13 { ws = 1 }
150 if c == 9 { ws = 1 }
151 if ws == 1 { e = e - 1 } else { go = 0 }
152 }
153 }
154 if e < 7 { return 0 }
155 let ht: *u8 = "</html>" as *u8
156 var j: i64 = 0
157 while j < 7 {
158 var a2: i64 = html[e-7+j] as i64
159 if a2 >= 65 { if a2 <= 90 { a2 = a2 + 32 } }
160 if a2 != (ht[j] as i64) { return 0 }
161 j = j + 1
162 }
163 return 1
164}
165
166// Returns 1 if the FIRST <style>...</style> block has balanced { } (0 if no style block or unbalanced braces).
167func shell_style_balanced(html: *u8, n: i64) -> i64 {
168 var i: i64 = 0
169 var bs: i64 = 0 - 1
170 while i + 6 <= n {
171 if html[i]==(60 as u8) { if html[i+1]==(115 as u8) { if html[i+2]==(116 as u8) { if html[i+3]==(121 as u8) { if html[i+4]==(108 as u8) { if html[i+5]==(101 as u8) {
172 var j: i64 = i + 6
173 while j < n { if html[j]==(62 as u8) { j = j + 1; break } j = j + 1 }
174 bs = j
175 i = n
176 } } } } } }
177 i = i + 1
178 }
179 if bs < 0 { return 0 }
180 var depth: i64 = 0
181 var k: i64 = bs
182 while k + 7 <= n {
183 if html[k]==(60 as u8) { if html[k+1]==(47 as u8) { if html[k+2]==(115 as u8) { if html[k+3]==(116 as u8) {
184 if depth == 0 { return 1 }
185 return 0
186 } } } }
187 if html[k]==(123 as u8) { depth = depth + 1 }
188 if html[k]==(125 as u8) { depth = depth - 1; if depth < 0 { return 0 } }
189 k = k + 1
190 }
191 return 0
192}