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