1package terminfo
2
3import (
4 "sort"
5)
6
7const (
8 // maxFileLength is the max file length.
9 maxFileLength = 4096
10 // magic is the file magic for terminfo files.
11 magic = 0o432
12 // magicExtended is the file magic for terminfo files with the extended
13 // number format.
14 magicExtended = 0o1036
15)
16
17// header fields.
18const (
19 fieldMagic = iota
20 fieldNameSize
21 fieldBoolCount
22 fieldNumCount
23 fieldStringCount
24 fieldTableSize
25)
26
27// header extended fields.
28const (
29 fieldExtBoolCount = iota
30 fieldExtNumCount
31 fieldExtStringCount
32 fieldExtOffsetCount
33 fieldExtTableSize
34)
35
36// hasInvalidCaps determines if the capabilities in h are invalid.
37func hasInvalidCaps(h []int) bool {
38 return h[fieldBoolCount] > CapCountBool ||
39 h[fieldNumCount] > CapCountNum ||
40 h[fieldStringCount] > CapCountString
41}
42
43// capLength returns the total length of the capabilities in bytes.
44func capLength(h []int) int {
45 return h[fieldNameSize] +
46 h[fieldBoolCount] +
47 (h[fieldNameSize]+h[fieldBoolCount])%2 + // account for word align
48 h[fieldNumCount]*2 +
49 h[fieldStringCount]*2 +
50 h[fieldTableSize]
51}
52
53// hasInvalidExtOffset determines if the extended offset field is valid.
54func hasInvalidExtOffset(h []int) bool {
55 return h[fieldExtBoolCount]+
56 h[fieldExtNumCount]+
57 h[fieldExtStringCount]*2 != h[fieldExtOffsetCount]
58}
59
60// extCapLength returns the total length of extended capabilities in bytes.
61func extCapLength(h []int, numWidth int) int {
62 return h[fieldExtBoolCount] +
63 h[fieldExtBoolCount]%2 + // account for word align
64 h[fieldExtNumCount]*(numWidth/8) +
65 h[fieldExtOffsetCount]*2 +
66 h[fieldExtTableSize]
67}
68
69// findNull finds the position of null in buf.
70func findNull(buf []byte, i int) int {
71 for ; i < len(buf); i++ {
72 if buf[i] == 0 {
73 return i
74 }
75 }
76 return -1
77}
78
79// readStrings decodes n strings from string data table buf using the indexes in idx.
80func readStrings(idx []int, buf []byte, n int) (map[int][]byte, int, error) {
81 var last int
82 m := make(map[int][]byte)
83 for i := 0; i < n; i++ {
84 start := idx[i]
85 if start < 0 {
86 continue
87 }
88 if end := findNull(buf, start); end != -1 {
89 m[i], last = buf[start:end], end+1
90 } else {
91 return nil, 0, ErrInvalidStringTable
92 }
93 }
94 return m, last, nil
95}
96
97// decoder holds state info while decoding a terminfo file.
98type decoder struct {
99 buf []byte
100 pos int
101 n int
102}
103
104// readBytes reads the next n bytes of buf, incrementing pos by n.
105func (d *decoder) readBytes(n int) ([]byte, error) {
106 if d.n < d.pos+n {
107 return nil, ErrUnexpectedFileEnd
108 }
109 n, d.pos = d.pos, d.pos+n
110 return d.buf[n:d.pos], nil
111}
112
113// readInts reads n number of ints with width w.
114func (d *decoder) readInts(n, w int) ([]int, error) {
115 w /= 8
116 l := n * w
117 buf, err := d.readBytes(l)
118 if err != nil {
119 return nil, err
120 }
121 // align
122 d.pos += d.pos % 2
123 z := make([]int, n)
124 for i, j := 0, 0; i < l; i, j = i+w, j+1 {
125 switch w {
126 case 1:
127 z[i] = int(buf[i])
128 case 2:
129 z[j] = int(int16(buf[i+1])<<8 | int16(buf[i]))
130 case 4:
131 z[j] = int(buf[i+3])<<24 | int(buf[i+2])<<16 | int(buf[i+1])<<8 | int(buf[i])
132 }
133 }
134 return z, nil
135}
136
137// readBools reads the next n bools.
138func (d *decoder) readBools(n int) (map[int]bool, map[int]bool, error) {
139 buf, err := d.readInts(n, 8)
140 if err != nil {
141 return nil, nil, err
142 }
143 // process
144 bools, boolsM := make(map[int]bool), make(map[int]bool)
145 for i, b := range buf {
146 bools[i] = b == 1
147 if int8(b) == -2 {
148 boolsM[i] = true
149 }
150 }
151 return bools, boolsM, nil
152}
153
154// readNums reads the next n nums.
155func (d *decoder) readNums(n, w int) (map[int]int, map[int]bool, error) {
156 buf, err := d.readInts(n, w)
157 if err != nil {
158 return nil, nil, err
159 }
160 // process
161 nums, numsM := make(map[int]int), make(map[int]bool)
162 for i := 0; i < n; i++ {
163 nums[i] = buf[i]
164 if buf[i] == -2 {
165 numsM[i] = true
166 }
167 }
168 return nums, numsM, nil
169}
170
171// readStringTable reads the string data for n strings and the accompanying data
172// table of length sz.
173func (d *decoder) readStringTable(n, sz int) ([][]byte, []int, error) {
174 buf, err := d.readInts(n, 16)
175 if err != nil {
176 return nil, nil, err
177 }
178 // read string data table
179 data, err := d.readBytes(sz)
180 if err != nil {
181 return nil, nil, err
182 }
183 // align
184 d.pos += d.pos % 2
185 // process
186 s := make([][]byte, n)
187 var m []int
188 for i := 0; i < n; i++ {
189 start := buf[i]
190 if start == -2 {
191 m = append(m, i)
192 } else if start >= 0 {
193 if end := findNull(data, start); end != -1 {
194 s[i] = data[start:end]
195 } else {
196 return nil, nil, ErrInvalidStringTable
197 }
198 }
199 }
200 return s, m, nil
201}
202
203// readStrings reads the next n strings and processes the string data table of
204// length sz.
205func (d *decoder) readStrings(n, sz int) (map[int][]byte, map[int]bool, error) {
206 s, m, err := d.readStringTable(n, sz)
207 if err != nil {
208 return nil, nil, err
209 }
210 strs := make(map[int][]byte)
211 for k, v := range s {
212 if k == AcsChars {
213 v = canonicalizeAscChars(v)
214 }
215 strs[k] = v
216 }
217 strsM := make(map[int]bool, len(m))
218 for _, k := range m {
219 strsM[k] = true
220 }
221 return strs, strsM, nil
222}
223
224// canonicalizeAscChars reorders chars to be unique, in order.
225//
226// see repair_ascc in ncurses-6.3/progs/dump_entry.c
227func canonicalizeAscChars(z []byte) []byte {
228 var c []byte
229 enc := make(map[byte]byte, len(z)/2)
230 for i := 0; i < len(z); i += 2 {
231 if _, ok := enc[z[i]]; !ok {
232 a, b := z[i], z[i+1]
233 // log.Printf(">>> a: %d %c, b: %d %c", a, a, b, b)
234 c, enc[a] = append(c, b), b
235 }
236 }
237 sort.Slice(c, func(i, j int) bool {
238 return c[i] < c[j]
239 })
240 r := make([]byte, 2*len(c))
241 for i := 0; i < len(c); i++ {
242 r[i*2], r[i*2+1] = c[i], enc[c[i]]
243 }
244 return r
245}