diff parser.go @ 0:6369453d47a3

Initial revision
author Guido Berhoerster <guido+managesieve@berhoerster.name>
date Thu, 15 Oct 2020 09:11:05 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/parser.go	Thu Oct 15 09:11:05 2020 +0200
@@ -0,0 +1,197 @@
+// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package managesieve
+
+import (
+	"strings"
+)
+
+type response int
+
+const (
+	responseInvalid response = iota
+	responseOk
+	responseNo
+	responseBye
+)
+
+func lookupResponse(s string) response {
+	switch s {
+	case "OK":
+		return responseOk
+	case "NO":
+		return responseNo
+	case "BYE":
+		return responseBye
+	}
+	return responseInvalid
+}
+
+type reply struct {
+	lines    [][]*token
+	resp     response
+	code     string
+	codeArgs []string
+	msg      string
+}
+
+func parseCapabilities(r *reply) (map[string]string, error) {
+	capa := make(map[string]string)
+	for _, tokens := range r.lines {
+		var k, v string
+		if tokens[0].typ != tokenQuotedString &&
+			tokens[0].typ != tokenLiteralString {
+			return nil, ParserError("failed to parse capability name: expected string")
+		}
+		k = strings.ToUpper(tokens[0].literal)
+
+		if len(tokens) > 1 {
+			if tokens[1].typ != tokenQuotedString &&
+				tokens[1].typ != tokenLiteralString {
+				return nil, ParserError("failed to parse capability value: expected string")
+			}
+			v = tokens[1].literal
+		}
+		capa[k] = v
+	}
+	return capa, nil
+}
+
+type parser struct {
+	s *scanner
+}
+
+func (p *parser) isResponseLine(tokens []*token) bool {
+	return tokens[0].typ == tokenAtom &&
+		lookupResponse(tokens[0].literal) != responseInvalid
+}
+
+func (p *parser) parseResponseLine(tokens []*token) (*reply, error) {
+	var i int
+	next := func() (*token, bool) {
+		if i >= len(tokens) {
+			return nil, false
+		}
+		tok := tokens[i]
+		i++
+		return tok, true
+	}
+
+	// response
+	tok, cont := next()
+	r := &reply{resp: lookupResponse(tok.literal)}
+
+	// code starts with left parenthesis
+	tok, cont = next()
+	if !cont {
+		// only response without code and/or message
+		return r, nil
+	}
+	if tok.typ == tokenLeftParenthesis {
+		// code atom
+		tok, cont = next()
+		if !cont || tok.typ != tokenAtom {
+			return nil, ParserError("failed to parse response code: expected atom")
+		}
+		r.code = tok.literal
+
+		// followed by zero or more string arguments
+		for {
+			tok, cont = next()
+			if !cont {
+				return nil, ParserError("failed to parse response code: unexpected end of line")
+			}
+			if tok.typ != tokenQuotedString &&
+				tok.typ != tokenLiteralString {
+				break
+			}
+			r.codeArgs = append(r.codeArgs, tok.literal)
+		}
+
+		// terminated by a right parenthesis
+		if tok.typ != tokenRightParenthesis {
+			return nil, ParserError("failed to parse response code: expected right parenthesis")
+		}
+
+		tok, cont = next()
+		if !cont {
+			// response with code but no message
+			return r, nil
+		}
+	}
+
+	// message string
+	if tok.typ != tokenQuotedString &&
+		tok.typ != tokenLiteralString {
+		return nil, ParserError("failed to parse response message: expected string")
+	}
+	r.msg = strings.TrimSpace(tok.literal)
+
+	// end of line
+	if _, cont = next(); cont {
+		return nil, ParserError("failed to parse response line: unexpected trailing data")
+	}
+
+	return r, nil
+}
+
+func (p *parser) readLine() ([]*token, error) {
+	tokens := make([]*token, 0)
+	for {
+		tok, err := p.s.scan()
+		if err != nil {
+			return nil, err
+		}
+		if tok.typ == tokenCRLF {
+			break
+		}
+		tokens = append(tokens, tok)
+	}
+
+	return tokens, nil
+}
+
+func (p *parser) readReply() (*reply, error) {
+	var r *reply
+	var lines [][]*token = make([][]*token, 0, 1)
+	for {
+		tokens, err := p.readLine()
+		if err != nil {
+			return nil, err
+		}
+		if len(tokens) == 0 {
+			return nil, ParserError("unexpected empty line")
+		}
+		// check for response tokens
+		if p.isResponseLine(tokens) {
+			r, err = p.parseResponseLine(tokens)
+			if err != nil {
+				return nil, err
+			}
+			r.lines = lines
+			break
+		}
+		lines = append(lines, tokens)
+	}
+
+	return r, nil
+}