mirror of
https://github.com/mjl-/mox.git
synced 2025-06-27 23:08:14 +03:00
imapserver: return proper response for FETCH of "BODY[1.MIME]" where 1 is a message
MIME returns the part headers. If 1 is a message, i.e. a message/rfc822 or message/global, for example when top-level is a multipart/mixed, we were returning the MIME headers from the message, not from the part. We also shouldn't be returning a MIME-Version header or the separating newline for MIME. Those are for MIME headers of a message, but the "MIME" fetch body part is always about the part. Found after looking into FETCH BODY handling for issue #327.
This commit is contained in:
parent
462568d878
commit
39c21f80cd
@ -826,35 +826,40 @@ func (cmd *fetchCmd) xpartnumsDeref(nums []uint32, p *message.Part) *message.Par
|
||||
}
|
||||
|
||||
func (cmd *fetchCmd) xsection(section *sectionSpec, p *message.Part) io.Reader {
|
||||
// msgtext is not nil, i.e. HEADER* or TEXT (not MIME), for the top-level part (a message).
|
||||
if section.part == nil {
|
||||
return cmd.xsectionMsgtext(section.msgtext, p)
|
||||
}
|
||||
|
||||
p = cmd.xpartnumsDeref(section.part.part, p)
|
||||
|
||||
// If there is no sectionMsgText, then this isn't for HEADER*, TEXT or MIME, i.e. a
|
||||
// part body, e.g. "BODY[1]".
|
||||
if section.part.text == nil {
|
||||
return p.RawReader()
|
||||
}
|
||||
|
||||
// ../rfc/9051:4535
|
||||
if p.Message != nil {
|
||||
// MIME is defined for all parts. Otherwise it's HEADER* or TEXT, which is only
|
||||
// defined for parts that are messages. ../rfc/9051:4500 ../rfc/9051:4517
|
||||
if !section.part.text.mime {
|
||||
if p.Message == nil {
|
||||
cmd.xerrorf("part is not a message, cannot request header* or text")
|
||||
}
|
||||
|
||||
err := p.SetMessageReaderAt()
|
||||
cmd.xcheckf(err, "preparing submessage")
|
||||
p = p.Message
|
||||
}
|
||||
|
||||
if !section.part.text.mime {
|
||||
return cmd.xsectionMsgtext(section.part.text.msgtext, p)
|
||||
}
|
||||
|
||||
// MIME header, see ../rfc/9051:4534 ../rfc/2045:1645
|
||||
// MIME header, see ../rfc/9051:4514 ../rfc/2045:1652
|
||||
h, err := io.ReadAll(p.HeaderReader())
|
||||
cmd.xcheckf(err, "reading header")
|
||||
|
||||
matchesFields := func(line []byte) bool {
|
||||
k := textproto.CanonicalMIMEHeaderKey(string(bytes.TrimRight(bytes.SplitN(line, []byte(":"), 2)[0], " \t")))
|
||||
// Only add MIME-Version and additional CRLF for messages, not other parts. ../rfc/2045:1645 ../rfc/2045:1652
|
||||
return (p.Envelope != nil && k == "Mime-Version") || strings.HasPrefix(k, "Content-")
|
||||
return strings.HasPrefix(k, "Content-")
|
||||
}
|
||||
|
||||
var match bool
|
||||
@ -868,7 +873,7 @@ func (cmd *fetchCmd) xsection(section *sectionSpec, p *message.Part) io.Reader {
|
||||
h = h[len(line):]
|
||||
|
||||
match = matchesFields(line) || match && (bytes.HasPrefix(line, []byte(" ")) || bytes.HasPrefix(line, []byte("\t")))
|
||||
if match || len(line) == 2 {
|
||||
if match {
|
||||
hb.Write(line)
|
||||
}
|
||||
}
|
||||
@ -876,11 +881,10 @@ func (cmd *fetchCmd) xsection(section *sectionSpec, p *message.Part) io.Reader {
|
||||
}
|
||||
|
||||
func (cmd *fetchCmd) xsectionMsgtext(smt *sectionMsgtext, p *message.Part) io.Reader {
|
||||
if smt.s == "HEADER" {
|
||||
return p.HeaderReader()
|
||||
}
|
||||
|
||||
switch smt.s {
|
||||
case "HEADER":
|
||||
return p.HeaderReader()
|
||||
|
||||
case "HEADER.FIELDS":
|
||||
return cmd.xmodifiedHeader(p, smt.headers, false)
|
||||
|
||||
@ -888,8 +892,8 @@ func (cmd *fetchCmd) xsectionMsgtext(smt *sectionMsgtext, p *message.Part) io.Re
|
||||
return cmd.xmodifiedHeader(p, smt.headers, true)
|
||||
|
||||
case "TEXT":
|
||||
// It appears imap clients expect to get the body of the message, not a "text body"
|
||||
// which sounds like it means a text/* part of a message. ../rfc/9051:4517
|
||||
// TEXT the body (excluding headers) of a message, either the top-level message, or
|
||||
// a nested as message/rfc822 or message/global. ../rfc/9051:4517
|
||||
return p.RawReader()
|
||||
}
|
||||
panic(serverError{fmt.Errorf("missing case")})
|
||||
|
@ -78,9 +78,7 @@ func TestFetch(t *testing.T) {
|
||||
headerSplit := strings.SplitN(exampleMsgHeader, "\r\n", 2)
|
||||
dateheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER.FIELDS (Date)]", Section: "HEADER.FIELDS (Date)", Body: headerSplit[0] + "\r\n\r\n"}
|
||||
nodateheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER.FIELDS.NOT (Date)]", Section: "HEADER.FIELDS.NOT (Date)", Body: headerSplit[1]}
|
||||
date1header1 := imapclient.FetchBody{RespAttr: "BODY[1.HEADER.FIELDS (Date)]", Section: "1.HEADER.FIELDS (Date)", Body: headerSplit[0] + "\r\n\r\n"}
|
||||
nodate1header1 := imapclient.FetchBody{RespAttr: "BODY[1.HEADER.FIELDS.NOT (Date)]", Section: "1.HEADER.FIELDS.NOT (Date)", Body: headerSplit[1]}
|
||||
mime1 := imapclient.FetchBody{RespAttr: "BODY[1.MIME]", Section: "1.MIME", Body: "MIME-Version: 1.0\r\nContent-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n\r\n"}
|
||||
mime1 := imapclient.FetchBody{RespAttr: "BODY[1.MIME]", Section: "1.MIME", Body: "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n"}
|
||||
|
||||
flagsSeen := imapclient.FetchFlags{`\Seen`}
|
||||
|
||||
@ -212,11 +210,10 @@ func TestFetch(t *testing.T) {
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, dateheader1}})
|
||||
tc.transactf("ok", "fetch 1 body.peek[header.fields.not (date)]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodateheader1}})
|
||||
// For non-multipart messages, 1 means the whole message. ../rfc/9051:4481
|
||||
tc.transactf("ok", "fetch 1 body.peek[1.header.fields (date)]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1header1}})
|
||||
tc.transactf("ok", "fetch 1 body.peek[1.header.fields.not (date)]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodate1header1}})
|
||||
// For non-multipart messages, 1 means the whole message, but since it's not of
|
||||
// type message/{rfc822,global} (a message), you can't get the message headers.
|
||||
// ../rfc/9051:4481
|
||||
tc.transactf("no", "fetch 1 body.peek[1.header]")
|
||||
|
||||
// MIME, part 1 for non-multipart messages is the message itself. ../rfc/9051:4481
|
||||
tc.transactf("ok", "fetch 1 body.peek[1.mime]")
|
||||
@ -407,9 +404,7 @@ Content-Disposition: inline; filename=image.jpg
|
||||
tc.transactf("ok", "fetch 2 body.peek[3]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3]", Section: "3", Body: part3}}})
|
||||
|
||||
part2mime := tocrlf(`Content-type: text/plain; charset=US-ASCII
|
||||
|
||||
`)
|
||||
part2mime := "Content-type: text/plain; charset=US-ASCII\r\n"
|
||||
tc.transactf("ok", "fetch 2 body.peek[2.mime]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[2.MIME]", Section: "2.MIME", Body: part2mime}}})
|
||||
|
||||
@ -434,19 +429,26 @@ Content-Transfer-Encoding: Quoted-printable
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.header]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.HEADER]", Section: "5.HEADER", Body: part5header}}})
|
||||
|
||||
part5mime := tocrlf(`Content-Type: Text/plain; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: Quoted-printable
|
||||
|
||||
part5mime := tocrlf(`Content-Type: message/rfc822
|
||||
Content-MD5: MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
|
||||
Content-Language: en,de
|
||||
Content-Location: http://localhost
|
||||
`)
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.mime]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.MIME]", Section: "5.MIME", Body: part5mime}}})
|
||||
|
||||
part5text := " ... Additional text in ISO-8859-1 goes here ...\r\n"
|
||||
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.text]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.TEXT]", Section: "5.TEXT", Body: part5text}}})
|
||||
|
||||
part5body := " ... Additional text in ISO-8859-1 goes here ...\r\n"
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.1]", Section: "5.1", Body: part5text}}})
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.1]", Section: "5.1", Body: part5body}}})
|
||||
|
||||
// 5.1 is the part that is the sub message, but not as message/rfc822, but as part,
|
||||
// so we cannot request a header.
|
||||
tc.transactf("no", "fetch 2 body.peek[5.1.header]")
|
||||
|
||||
// In case of EXAMINE instead of SELECT, we should not be seeing any changed \Seen flags for non-peek commands.
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
|
Loading…
x
Reference in New Issue
Block a user