1package imap
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"strconv"
9	"time"
10	"unicode"
11)
12
13type flusher interface {
14	Flush() error
15}
16
17type (
18	// A raw string.
19	RawString string
20)
21
22type WriterTo interface {
23	WriteTo(w *Writer) error
24}
25
26func formatNumber(num uint32) string {
27	return strconv.FormatUint(uint64(num), 10)
28}
29
30// Convert a string list to a field list.
31func FormatStringList(list []string) (fields []interface{}) {
32	fields = make([]interface{}, len(list))
33	for i, v := range list {
34		fields[i] = v
35	}
36	return
37}
38
39// Check if a string is 8-bit clean.
40func isAscii(s string) bool {
41	for _, c := range s {
42		if c > unicode.MaxASCII || unicode.IsControl(c) {
43			return false
44		}
45	}
46	return true
47}
48
49// An IMAP writer.
50type Writer struct {
51	io.Writer
52
53	AllowAsyncLiterals bool
54
55	continues <-chan bool
56}
57
58// Helper function to write a string to w.
59func (w *Writer) writeString(s string) error {
60	_, err := io.WriteString(w.Writer, s)
61	return err
62}
63
64func (w *Writer) writeCrlf() error {
65	if err := w.writeString(crlf); err != nil {
66		return err
67	}
68
69	return w.Flush()
70}
71
72func (w *Writer) writeNumber(num uint32) error {
73	return w.writeString(formatNumber(num))
74}
75
76func (w *Writer) writeQuoted(s string) error {
77	return w.writeString(strconv.Quote(s))
78}
79
80func (w *Writer) writeQuotedOrLiteral(s string) error {
81	if !isAscii(s) {
82		// IMAP doesn't allow 8-bit data outside literals
83		return w.writeLiteral(bytes.NewBufferString(s))
84	}
85
86	return w.writeQuoted(s)
87}
88
89func (w *Writer) writeDateTime(t time.Time, layout string) error {
90	if t.IsZero() {
91		return w.writeString(nilAtom)
92	}
93	return w.writeQuoted(t.Format(layout))
94}
95
96func (w *Writer) writeFields(fields []interface{}) error {
97	for i, field := range fields {
98		if i > 0 { // Write separator
99			if err := w.writeString(string(sp)); err != nil {
100				return err
101			}
102		}
103
104		if err := w.writeField(field); err != nil {
105			return err
106		}
107	}
108
109	return nil
110}
111
112func (w *Writer) writeList(fields []interface{}) error {
113	if err := w.writeString(string(listStart)); err != nil {
114		return err
115	}
116
117	if err := w.writeFields(fields); err != nil {
118		return err
119	}
120
121	return w.writeString(string(listEnd))
122}
123
124// LiteralLengthErr is returned when the Len() of the Literal object does not
125// match the actual length of the byte stream.
126type LiteralLengthErr struct {
127	Actual   int
128	Expected int
129}
130
131func (e LiteralLengthErr) Error() string {
132	return fmt.Sprintf("imap: size of Literal is not equal to Len() (%d != %d)", e.Expected, e.Actual)
133}
134
135func (w *Writer) writeLiteral(l Literal) error {
136	if l == nil {
137		return w.writeString(nilAtom)
138	}
139
140	unsyncLiteral := w.AllowAsyncLiterals && l.Len() <= 4096
141
142	header := string(literalStart) + strconv.Itoa(l.Len())
143	if unsyncLiteral {
144		header += string('+')
145	}
146	header += string(literalEnd) + crlf
147	if err := w.writeString(header); err != nil {
148		return err
149	}
150
151	// If a channel is available, wait for a continuation request before sending data
152	if !unsyncLiteral && w.continues != nil {
153		// Make sure to flush the writer, otherwise we may never receive a continuation request
154		if err := w.Flush(); err != nil {
155			return err
156		}
157
158		if !<-w.continues {
159			return fmt.Errorf("imap: cannot send literal: no continuation request received")
160		}
161	}
162
163	// In case of bufio.Buffer, it will be 0 after io.Copy.
164	literalLen := int64(l.Len())
165
166	n, err := io.CopyN(w, l, literalLen)
167	if err != nil {
168		if err == io.EOF && n != literalLen {
169			return LiteralLengthErr{int(n), l.Len()}
170		}
171		return err
172	}
173	extra, _ := io.Copy(ioutil.Discard, l)
174	if extra != 0 {
175		return LiteralLengthErr{int(n + extra), l.Len()}
176	}
177
178	return nil
179}
180
181func (w *Writer) writeField(field interface{}) error {
182	if field == nil {
183		return w.writeString(nilAtom)
184	}
185
186	switch field := field.(type) {
187	case RawString:
188		return w.writeString(string(field))
189	case string:
190		return w.writeQuotedOrLiteral(field)
191	case int:
192		return w.writeNumber(uint32(field))
193	case uint32:
194		return w.writeNumber(field)
195	case Literal:
196		return w.writeLiteral(field)
197	case []interface{}:
198		return w.writeList(field)
199	case envelopeDateTime:
200		return w.writeDateTime(time.Time(field), envelopeDateTimeLayout)
201	case searchDate:
202		return w.writeDateTime(time.Time(field), searchDateLayout)
203	case Date:
204		return w.writeDateTime(time.Time(field), DateLayout)
205	case DateTime:
206		return w.writeDateTime(time.Time(field), DateTimeLayout)
207	case time.Time:
208		return w.writeDateTime(field, DateTimeLayout)
209	case *SeqSet:
210		return w.writeString(field.String())
211	case *BodySectionName:
212		// Can contain spaces - that's why we don't just pass it as a string
213		return w.writeString(string(field.FetchItem()))
214	}
215
216	return fmt.Errorf("imap: cannot format field: %v", field)
217}
218
219func (w *Writer) writeRespCode(code StatusRespCode, args []interface{}) error {
220	if err := w.writeString(string(respCodeStart)); err != nil {
221		return err
222	}
223
224	fields := []interface{}{RawString(code)}
225	fields = append(fields, args...)
226
227	if err := w.writeFields(fields); err != nil {
228		return err
229	}
230
231	return w.writeString(string(respCodeEnd))
232}
233
234func (w *Writer) writeLine(fields ...interface{}) error {
235	if err := w.writeFields(fields); err != nil {
236		return err
237	}
238
239	return w.writeCrlf()
240}
241
242func (w *Writer) Flush() error {
243	if f, ok := w.Writer.(flusher); ok {
244		return f.Flush()
245	}
246	return nil
247}
248
249func NewWriter(w io.Writer) *Writer {
250	return &Writer{Writer: w}
251}
252
253func NewClientWriter(w io.Writer, continues <-chan bool) *Writer {
254	return &Writer{Writer: w, continues: continues}
255}
256