1package kittygraphics
2
3import "bytes"
4
5var apcPrefix = [...]byte{0x1b, '_', 'G'}
6
7// Feed consumes data and returns text and complete graphics events. The
8// parser retains only an incomplete APC or a possible split prefix between
9// calls. A malformed APC is reported as an EventError and discarded through
10// its string terminator, allowing following text and APCs to be processed.
11func (p *Parser) Feed(data []byte) []Event {
12 if p == nil {
13 if len(data) == 0 {
14 return nil
15 }
16 return []Event{{Kind: EventText, Text: append([]byte(nil), data...)}}
17 }
18 var events []Event
19 text := make([]byte, 0, len(data))
20 flushText := func() {
21 if len(text) == 0 {
22 return
23 }
24 events = append(events, Event{Kind: EventText, Text: append([]byte(nil), text...)})
25 text = text[:0]
26 }
27
28 for _, b := range data {
29 if !p.inAPC {
30 p.candidate = append(p.candidate, b)
31 for len(p.candidate) != 0 {
32 if len(p.candidate) <= len(apcPrefix) && bytes.Equal(p.candidate, apcPrefix[:len(p.candidate)]) {
33 break
34 }
35 text = append(text, p.candidate[0])
36 p.candidate = p.candidate[1:]
37 }
38 if len(p.candidate) == len(apcPrefix) {
39 flushText()
40 p.candidate = p.candidate[:0]
41 p.inAPC = true
42 p.escaped = false
43 p.oversize = false
44 p.apc = p.apc[:0]
45 }
46 continue
47 }
48
49 if p.escaped {
50 if b == '\\' {
51 if p.oversize {
52 events = append(events, p.errorEvent())
53 } else {
54 events = append(events, p.commandEvent())
55 }
56 p.resetAPC()
57 continue
58 }
59 // An ESC that is not followed by backslash is payload. Keep the
60 // byte and continue looking for a terminator. This is necessary
61 // for raw payloads and is harmless for base64 payloads.
62 p.appendAPC(0x1b)
63 p.escaped = false
64 }
65 if b == 0x9c { // C1 ST is also a valid APC terminator.
66 if p.oversize {
67 events = append(events, p.errorEvent())
68 } else {
69 events = append(events, p.commandEvent())
70 }
71 p.resetAPC()
72 continue
73 }
74 if b == 0x1b {
75 p.escaped = true
76 continue
77 }
78 p.appendAPC(b)
79 }
80 flushText()
81 return events
82}
83
84// Parse is an alias for Feed.
85func (p *Parser) Parse(data []byte) []Event { return p.Feed(data) }
86
87// FeedWithErrors is a conventional error-returning facade over Feed. Parser
88// errors are also retained in the returned Event values.
89func (p *Parser) FeedWithErrors(data []byte) ([]Event, error) {
90 events := p.Feed(data)
91 for _, event := range events {
92 if event.Kind == EventError && event.Err != nil {
93 return events, event.Err
94 }
95 }
96 return events, nil
97}
98
99// Finish reports an unterminated APC and resets the parser. Text which is a
100// partial ordinary escape sequence is returned as text. Finish is useful at a
101// stream boundary; a caller that will feed more data must not call it.
102func (p *Parser) Finish() []Event {
103 if p == nil {
104 return nil
105 }
106 var events []Event
107 if p.inAPC {
108 err := ErrAPCTruncated
109 if p.oversize {
110 err = ErrAPCTooLarge
111 }
112 events = append(events, Event{Kind: EventError, Err: err})
113 p.resetAPC()
114 }
115 if len(p.candidate) != 0 {
116 events = append(events, Event{Kind: EventText, Text: append([]byte(nil), p.candidate...)})
117 p.candidate = p.candidate[:0]
118 }
119 return events
120}
121
122// Flush is an alias for Finish.
123func (p *Parser) Flush() []Event { return p.Finish() }
124
125func (p *Parser) appendAPC(b byte) {
126 if p.oversize {
127 return
128 }
129 if uint64(len(p.apc))+1 > p.limits.MaxAPCBytes {
130 p.oversize = true
131 return
132 }
133 p.apc = append(p.apc, b)
134}
135
136func (p *Parser) resetAPC() {
137 p.inAPC = false
138 p.escaped = false
139 p.oversize = false
140 p.apc = p.apc[:0]
141}
142
143func (p *Parser) errorEvent() Event {
144 if p.oversize {
145 return Event{Kind: EventError, Err: ErrAPCTooLarge}
146 }
147 return Event{Kind: EventError, Err: ErrInvalidAPC}
148}
149
150func (p *Parser) commandEvent() Event {
151 command, err := ParseCommand(p.apc, p.limits)
152 if err != nil {
153 return Event{Kind: EventError, Err: err}
154 }
155 return Event{Kind: EventCommand, Command: command}
156}
157
158// ParseCommand parses the bytes between APC's G and its ST terminator. It is
159// exported separately so adapters can validate commands without maintaining a
160// stream parser.
161func ParseCommand(body []byte, config ...Limits) (Command, error) {
162 return parseCommand(body, true, config)
163}
164
165func parseCommand(body []byte, copyPayload bool, config []Limits) (Command, error) {
166 var l Limits
167 if len(config) != 0 {
168 l = config[0]
169 }
170 l = normalizeLimits(l)
171 if uint64(len(body)) > l.MaxAPCBytes {
172 return Command{}, ErrAPCTooLarge
173 }
174 controlEnd, payloadStart := len(body), len(body)
175 if sep := bytes.IndexByte(body, ';'); sep >= 0 {
176 controlEnd, payloadStart = sep, sep+1
177 }
178 controls, err := parseControls(body[:controlEnd])
179 if err != nil {
180 return Command{}, err
181 }
182 payload := body[payloadStart:]
183 if uint64(len(payload)) > l.MaxPayloadBytes {
184 return Command{}, ErrPayloadTooLarge
185 }
186 if copyPayload {
187 payload = append([]byte(nil), payload...)
188 }
189 return Command{Controls: controls, Payload: payload}, nil
190}
191
192// ParseAPC parses a complete ESC _ G ... ST sequence.
193func ParseAPC(apc []byte, config ...Limits) (Command, error) {
194 return parseAPC(apc, true, config)
195}
196
197// ParseAPCBorrowed is ParseAPC without copying the payload: the returned
198// Command.Payload aliases apc. The caller must not modify apc while the
199// command is in use and must not retain the command beyond the lifetime of
200// apc. Session.Process copies whatever it keeps from a command's payload, so
201// a borrowed command may be passed to it and apc reused afterwards.
202func ParseAPCBorrowed(apc []byte, config ...Limits) (Command, error) {
203 return parseAPC(apc, false, config)
204}
205
206func parseAPC(apc []byte, copyPayload bool, config []Limits) (Command, error) {
207 if len(apc) < 4 || apc[0] != 0x1b || apc[1] != '_' || apc[2] != 'G' {
208 return Command{}, ErrInvalidAPC
209 }
210 body := apc[3:]
211 if body[len(body)-1] == 0x9c {
212 body = body[:len(body)-1]
213 } else if len(body) >= 2 && body[len(body)-2] == 0x1b && body[len(body)-1] == '\\' {
214 body = body[:len(body)-2]
215 } else {
216 return Command{}, ErrAPCTruncated
217 }
218 return parseCommand(body, copyPayload, config)
219}