-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathappend.go
More file actions
114 lines (98 loc) · 3.25 KB
/
Copy pathappend.go
File metadata and controls
114 lines (98 loc) · 3.25 KB
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
107
108
109
110
111
112
113
114
package imap
import (
"bufio"
"bytes"
"fmt"
"strings"
"time"
"github.com/rs/xid"
)
// waitForTaggedOK reads lines from r until it finds the tagged response matching tag.
// It returns nil if the response is OK, or an error otherwise.
func (d *Dialer) waitForTaggedOK(r *bufio.Reader, tag []byte) error {
taglen := len(tag)
for {
line, err := r.ReadBytes('\n')
if err != nil {
_ = d.Close()
return fmt.Errorf("imap append read response: %w", err)
}
if Verbose && !SkipResponses {
debugLog(d.ConnNum, d.Folder, "server response", "response", string(dropNl(line)))
}
if len(line) >= taglen+3 && bytes.Equal(line[:taglen], tag) {
if !bytes.Equal(line[taglen+1:taglen+3], []byte("OK")) {
return fmt.Errorf("imap append failed: %s", dropNl(line[taglen+1:]))
}
return nil
}
}
}
// Append uploads a message to the specified folder.
//
// The flags parameter specifies initial flags for the message (e.g., `\Seen`, `\Draft`).
// Pass nil or an empty slice for no flags. The date parameter sets the internal date;
// pass a zero time.Time to let the server use the current time.
//
// The message parameter should be a complete RFC 2822 message (headers + body).
//
// Note: Append does not retry on failure because the IMAP APPEND protocol uses a
// two-phase literal transfer. A partial send cannot be safely retried without
// risking duplicate messages.
//
// Example:
//
// msg := []byte("From: a@b.com\r\nTo: c@d.com\r\nSubject: Hi\r\n\r\nHello!")
// err := conn.Append("INBOX", []string{`\Seen`}, time.Time{}, msg)
func (d *Dialer) Append(folder string, flags []string, date time.Time, message []byte) error {
// Build the APPEND command prefix
flagStr := ""
if len(flags) > 0 {
flagStr = " (" + strings.Join(flags, " ") + ")"
}
dateStr := ""
if !date.IsZero() {
dateStr = fmt.Sprintf(` "%s"`, date.Format(TimeFormat))
}
cmd := fmt.Sprintf(`APPEND "%s"%s%s {%d}`,
AddSlashes.Replace(folder), flagStr, dateStr, len(message))
tag := []byte(strings.ToUpper(xid.New().String()))
if CommandTimeout != 0 {
_ = d.conn.SetDeadline(time.Now().Add(CommandTimeout))
defer func() { _ = d.conn.SetDeadline(time.Time{}) }()
}
if Verbose {
debugLog(d.ConnNum, d.Folder, "sending command", "command", string(tag)+" "+cmd)
}
// Phase 1: Send the APPEND command with literal size
_, err := fmt.Fprintf(d.conn, "%s %s\r\n", tag, cmd)
if err != nil {
return fmt.Errorf("imap append write command: %w", err)
}
// Phase 2: Wait for continuation response (+)
r := bufio.NewReader(d.conn)
line, err := r.ReadBytes('\n')
if err != nil {
_ = d.Close()
return fmt.Errorf("imap append read continuation: %w", err)
}
if Verbose && !SkipResponses {
debugLog(d.ConnNum, d.Folder, "server response", "response", string(dropNl(line)))
}
if !bytes.HasPrefix(bytes.TrimSpace(line), []byte("+")) {
return fmt.Errorf("imap append: expected continuation (+), got: %s", dropNl(line))
}
// Phase 3: Send the literal message bytes
_, err = d.conn.Write(message)
if err != nil {
_ = d.Close()
return fmt.Errorf("imap append write literal: %w", err)
}
_, err = d.conn.Write([]byte("\r\n"))
if err != nil {
_ = d.Close()
return fmt.Errorf("imap append write crlf: %w", err)
}
// Phase 4: Read the tagged response
return d.waitForTaggedOK(r, tag)
}