comparison parser.go @ 0:6369453d47a3

Initial revision
author Guido Berhoerster <guido+managesieve@berhoerster.name>
date Thu, 15 Oct 2020 09:11:05 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:6369453d47a3
1 // Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 package managesieve
23
24 import (
25 "strings"
26 )
27
28 type response int
29
30 const (
31 responseInvalid response = iota
32 responseOk
33 responseNo
34 responseBye
35 )
36
37 func lookupResponse(s string) response {
38 switch s {
39 case "OK":
40 return responseOk
41 case "NO":
42 return responseNo
43 case "BYE":
44 return responseBye
45 }
46 return responseInvalid
47 }
48
49 type reply struct {
50 lines [][]*token
51 resp response
52 code string
53 codeArgs []string
54 msg string
55 }
56
57 func parseCapabilities(r *reply) (map[string]string, error) {
58 capa := make(map[string]string)
59 for _, tokens := range r.lines {
60 var k, v string
61 if tokens[0].typ != tokenQuotedString &&
62 tokens[0].typ != tokenLiteralString {
63 return nil, ParserError("failed to parse capability name: expected string")
64 }
65 k = strings.ToUpper(tokens[0].literal)
66
67 if len(tokens) > 1 {
68 if tokens[1].typ != tokenQuotedString &&
69 tokens[1].typ != tokenLiteralString {
70 return nil, ParserError("failed to parse capability value: expected string")
71 }
72 v = tokens[1].literal
73 }
74 capa[k] = v
75 }
76 return capa, nil
77 }
78
79 type parser struct {
80 s *scanner
81 }
82
83 func (p *parser) isResponseLine(tokens []*token) bool {
84 return tokens[0].typ == tokenAtom &&
85 lookupResponse(tokens[0].literal) != responseInvalid
86 }
87
88 func (p *parser) parseResponseLine(tokens []*token) (*reply, error) {
89 var i int
90 next := func() (*token, bool) {
91 if i >= len(tokens) {
92 return nil, false
93 }
94 tok := tokens[i]
95 i++
96 return tok, true
97 }
98
99 // response
100 tok, cont := next()
101 r := &reply{resp: lookupResponse(tok.literal)}
102
103 // code starts with left parenthesis
104 tok, cont = next()
105 if !cont {
106 // only response without code and/or message
107 return r, nil
108 }
109 if tok.typ == tokenLeftParenthesis {
110 // code atom
111 tok, cont = next()
112 if !cont || tok.typ != tokenAtom {
113 return nil, ParserError("failed to parse response code: expected atom")
114 }
115 r.code = tok.literal
116
117 // followed by zero or more string arguments
118 for {
119 tok, cont = next()
120 if !cont {
121 return nil, ParserError("failed to parse response code: unexpected end of line")
122 }
123 if tok.typ != tokenQuotedString &&
124 tok.typ != tokenLiteralString {
125 break
126 }
127 r.codeArgs = append(r.codeArgs, tok.literal)
128 }
129
130 // terminated by a right parenthesis
131 if tok.typ != tokenRightParenthesis {
132 return nil, ParserError("failed to parse response code: expected right parenthesis")
133 }
134
135 tok, cont = next()
136 if !cont {
137 // response with code but no message
138 return r, nil
139 }
140 }
141
142 // message string
143 if tok.typ != tokenQuotedString &&
144 tok.typ != tokenLiteralString {
145 return nil, ParserError("failed to parse response message: expected string")
146 }
147 r.msg = strings.TrimSpace(tok.literal)
148
149 // end of line
150 if _, cont = next(); cont {
151 return nil, ParserError("failed to parse response line: unexpected trailing data")
152 }
153
154 return r, nil
155 }
156
157 func (p *parser) readLine() ([]*token, error) {
158 tokens := make([]*token, 0)
159 for {
160 tok, err := p.s.scan()
161 if err != nil {
162 return nil, err
163 }
164 if tok.typ == tokenCRLF {
165 break
166 }
167 tokens = append(tokens, tok)
168 }
169
170 return tokens, nil
171 }
172
173 func (p *parser) readReply() (*reply, error) {
174 var r *reply
175 var lines [][]*token = make([][]*token, 0, 1)
176 for {
177 tokens, err := p.readLine()
178 if err != nil {
179 return nil, err
180 }
181 if len(tokens) == 0 {
182 return nil, ParserError("unexpected empty line")
183 }
184 // check for response tokens
185 if p.isResponseLine(tokens) {
186 r, err = p.parseResponseLine(tokens)
187 if err != nil {
188 return nil, err
189 }
190 r.lines = lines
191 break
192 }
193 lines = append(lines, tokens)
194 }
195
196 return r, nil
197 }