limage - xcf/parasites.go
1 package xcf
2
3 import (
4 "errors"
5 "io"
6 "strconv"
7 "strings"
8
9 "vimagination.zapto.org/parser"
10 )
11
12 const (
13 //iccProfileParasiteName = "icc-profile"
14 //commentParasiteName = "gimp-comment"
15 textParasiteName = "gimp-text-layer"
16 )
17
18 type parasite struct {
19 name string
20 flags uint32
21 data []byte
22 }
23
24 type parasites []parasite
25
26 func (p parasites) Get(name string) *parasite {
27 for n := range p {
28 if p[n].name == name {
29 return &p[n]
30 }
31 }
32 return nil
33 }
34
35 func (d *reader) ReadParasites(l uint32) parasites {
36 ps := make(parasites, 0, 32)
37 for l > 0 {
38 var p parasite
39 p.name = d.ReadString()
40 p.flags = d.ReadUint32()
41 pplength := d.ReadUint32()
42 read := 4 + uint32(len(p.name)) + 1 // length (uint32) + string([]byte) + \0 (byte)
43 read += 4 // flags
44 read += 4 // pplength
45 read += pplength // len(data)
46 if read > l {
47 d.SetError(ErrInvalidParasites)
48 return nil
49 }
50 l -= read
51 p.data = make([]byte, pplength)
52 d.Read(p.data)
53
54 ps = append(ps, p)
55 }
56 return ps
57 }
58
59 func (d *reader) ReadParasite() parasite {
60 var p parasite
61
62 p.name = d.ReadString()
63 p.flags = d.ReadUint32()
64 pplength := d.ReadUint32()
65
66 p.data = make([]byte, pplength)
67 d.Read(p.data)
68
69 return p
70 }
71
72 func (ps *parasite) Parse() ([]tag, error) {
73 p := parser.New(parser.NewByteTokeniser(ps.data))
74 p.TokeniserState(openTK)
75 tags := make([]tag, 0, 32)
76 for {
77 tag, err := readTag(&p)
78 if err != nil {
79 if p.Err != io.EOF {
80 return nil, err
81 }
82 break
83 }
84 tags = append(tags, tag)
85 }
86 return tags, nil
87 }
88
89 const (
90 open = "("
91 close = ")"
92 chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
93 valName = chars + "-"
94 digit = "1234567890"
95 quoted = "\""
96 whitespace = "\n\r "
97 )
98
99 const (
100 tokenOpen parser.TokenType = iota
101 tokenClose
102 tokenName
103 tokenValueString
104 tokenValueNumber
105 )
106
107 // Tag represents a single tag from a parsed Parasite
108 type tag struct {
109 Name string
110 Values []interface{}
111 }
112
113 func readTag(p *parser.Parser) (tag, error) {
114 if p.Accept(parser.TokenDone) {
115 return tag{}, io.EOF
116 }
117 if !p.Accept(tokenOpen) {
118 return tag{}, ErrNoOpen
119 }
120 p.Get()
121 if !p.Accept(tokenName) {
122 return tag{}, ErrNoName
123 }
124 nt := p.Get()
125 var tg tag
126 tg.Name = nt[0].Data
127 for {
128 tt := p.AcceptRun(tokenValueString, tokenValueNumber)
129 for _, v := range p.Get() {
130 switch v.Type {
131 case tokenValueString:
132 tg.Values = append(tg.Values, v.Data)
133 case tokenValueNumber:
134 num, err := strconv.ParseFloat(v.Data, 64)
135 if err != nil {
136 return tag{}, err
137 }
138 tg.Values = append(tg.Values, num)
139 }
140 }
141 switch tt {
142 case tokenClose:
143 p.Accept(tokenClose)
144 p.Get()
145 return tg, nil
146 case tokenOpen:
147 ttg, err := readTag(p)
148 p.TokeniserState(valueTK)
149 if err != nil {
150 return tag{}, err
151 }
152 tg.Values = append(tg.Values, ttg)
153 case parser.TokenDone:
154 return tag{}, io.EOF
155 default:
156 return tag{}, ErrInvalidParasites
157 }
158 }
159 }
160
161 func openTK(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
162 t.AcceptRun(whitespace)
163 switch t.Peek() {
164 case -1, 0:
165 return t.Done()
166 }
167 if !t.Accept(open) {
168 t.Err = ErrInvalidParasites
169 return t.Error()
170 }
171 t.Get()
172 return parser.Token{
173 Type: tokenOpen,
174 }, nameTK
175 }
176
177 func closeTK(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
178 t.Accept(close)
179 t.Get()
180 return parser.Token{
181 Type: tokenClose,
182 }, openTK
183 }
184
185 func nameTK(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
186 if !t.Accept(valName) {
187 t.Err = ErrInvalidParasites
188 return t.Error()
189 }
190 t.AcceptRun(valName)
191 return parser.Token{
192 Type: tokenName,
193 Data: t.Get(),
194 }, valueTK
195 }
196
197 func valueTK(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
198 t.AcceptRun(whitespace)
199 t.Get()
200 c := t.Peek()
201 if c == 0 {
202 return t.Done()
203 } else if c < 0 {
204 t.Err = ErrInvalidParasites
205 return t.Error()
206 }
207 switch string(c) {
208 case open:
209 return openTK(t)
210 case close:
211 return closeTK(t)
212 case quoted:
213 return parser.Token{
214 Type: tokenValueString,
215 Data: quotedString(t),
216 }, valueTK
217 }
218 if strings.ContainsRune(digit, c) {
219 t.AcceptRun(digit)
220 t.Accept(".")
221 t.AcceptRun(digit)
222 return parser.Token{
223 Type: tokenValueNumber,
224 Data: t.Get(),
225 }, valueTK
226 }
227 t.AcceptRun(valName)
228 return parser.Token{
229 Type: tokenValueString,
230 Data: t.Get(),
231 }, valueTK
232 }
233
234 func quotedString(t *parser.Tokeniser) string {
235 t.Accept(quoted)
236 t.Get()
237 var s string
238 for {
239 t.ExceptRun(quoted + "\\")
240 s += t.Get()
241 if t.Accept("\\") {
242 c := string(t.Peek())
243 switch c {
244 case "\"", "\\":
245 s += c
246 default:
247 s += "\\" + c
248 }
249 t.Accept(c)
250 t.Get()
251 continue
252 }
253 break
254 }
255 t.Accept(quoted)
256 t.Get()
257 return s
258 }
259
260 // Errors
261 var (
262 ErrInvalidParasites = errors.New("invalid parasites layout")
263 ErrNoOpen = errors.New("didn't receive Open token")
264 ErrNoName = errors.New("didn't receive Name token")
265 )
266