File: response.go

package info (click to toggle)
golang-github-cretz-bine 0.2.0%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-backports, sid, trixie
  • size: 652 kB
  • sloc: makefile: 3
file content (106 lines) | stat: -rw-r--r-- 3,112 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package control

import (
	"net/textproto"
	"strconv"
	"strings"
)

// Response is a response to a control port command or an asynchronous event.
type Response struct {
	// Err is the status code and string representation associated with a
	// response. Responses that have completed successfully will also have Err
	// set to indicate such.
	Err *textproto.Error

	// Reply is the text on the EndReplyLine of the response.
	Reply string

	// Data is the MidReplyLines/DataReplyLines of the response. Dot encoded
	// data is "decoded" and presented as a single string (terminal ".CRLF"
	// removed, all intervening CRs stripped).
	Data []string

	// RawLines is all of the lines of a response, without CRLFs.
	RawLines []string
}

// IsOk returns true if the response status code indicates success or an
// asynchronous event.
func (r *Response) IsOk() bool {
	switch r.Err.Code {
	case StatusOk, StatusOkUnnecessary, StatusAsyncEvent:
		return true
	default:
		return false
	}
}

// DataWithReply returns a combination of Data and Reply to give a full set of
// the lines of the response.
func (r *Response) DataWithReply() []string {
	ret := make([]string, len(r.Data)+1)
	copy(ret, r.Data)
	ret[len(ret)-1] = r.Reply
	return ret
}

// IsAsync returns true if the response is an asynchronous event.
func (r *Response) IsAsync() bool {
	return r.Err.Code == StatusAsyncEvent
}

// ReadResponse returns the next response object.
func (c *Conn) ReadResponse() (*Response, error) {
	var resp *Response
	var statusCode int
	for {
		line, err := c.conn.ReadLine()
		if err != nil {
			return nil, err
		}
		c.debugf("Read line: %v", line)

		// Parse the line that was just read.
		if len(line) < 4 {
			return nil, c.protoErr("Truncated response: %v", line)
		}
		if code, err := strconv.Atoi(line[0:3]); err != nil || code < 100 {
			return nil, c.protoErr("Invalid status code: %v", line[0:3])
		} else if resp == nil {
			resp = &Response{}
			statusCode = code
		} else if code != statusCode {
			// The status code should stay fixed for all lines of the response, since events can't be interleaved with
			// response lines.
			return nil, c.protoErr("Status code changed: %v != %v", code, statusCode)
		}
		resp.RawLines = append(resp.RawLines, line)
		switch line[3] {
		case ' ':
			// Final line in the response.
			resp.Reply = line[4:]
			resp.Err = statusCodeToError(statusCode, resp.Reply)
			return resp, nil
		case '-':
			// Continuation, keep reading.
			resp.Data = append(resp.Data, line[4:])
		case '+':
			// A "dot-encoded" payload follows.
			dotBody, err := c.conn.ReadDotBytes()
			if err != nil {
				return nil, err
			}
			dotBodyStr := strings.TrimRight(string(dotBody), "\n\r")
			// c.debugf("Read dot body:\n---\n%v\n---", dotBodyStr)
			resp.Data = append(resp.Data, line[4:]+"\r\n"+dotBodyStr)
			dotLines := strings.Split(dotBodyStr, "\n")
			for _, dotLine := range dotLines[:len(dotLines)-1] {
				resp.RawLines = append(resp.RawLines, dotLine)
			}
			resp.RawLines = append(resp.RawLines, ".")
		default:
			return nil, c.protoErr("Invalid separator: '%v'", line[3])
		}
	}
}