mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 08:23:48 +03:00
imapserver: for the "bodystructure" fetch response item, add the content-type parameters for multiparts so clients will get the mime boundary without having to parse the message themselves
"bodystructure" is like "body", but bodystructure allows returning more information. we chose not to do that, initially because it was easier to implement, and more recently because we can't easily return the additional content-md5 field for leaf parts (since we don't have it in parsed form). but now we just return the extended form for multiparts, and non-extended form for leaf parts. likely no one would be looking for any content-md5-value for leaf parts anyway. knowing the boundary is much more likely to be useful. for issue #217 by danieleggert, thanks for reporting!
This commit is contained in:
parent
598c5ea6ac
commit
8fa197b19d
2 changed files with 43 additions and 25 deletions
|
@ -406,7 +406,7 @@ func (cmd *fetchCmd) xprocessAtt(a fetchAtt) []token {
|
||||||
|
|
||||||
case "BODYSTRUCTURE":
|
case "BODYSTRUCTURE":
|
||||||
_, part := cmd.xensureParsed()
|
_, part := cmd.xensureParsed()
|
||||||
bs := xbodystructure(part)
|
bs := xbodystructure(part, true)
|
||||||
return []token{bare("BODYSTRUCTURE"), bs}
|
return []token{bare("BODYSTRUCTURE"), bs}
|
||||||
|
|
||||||
case "BODY":
|
case "BODY":
|
||||||
|
@ -660,7 +660,7 @@ func (cmd *fetchCmd) xbody(a fetchAtt) (string, token) {
|
||||||
|
|
||||||
if a.section == nil {
|
if a.section == nil {
|
||||||
// Non-extensible form of BODYSTRUCTURE.
|
// Non-extensible form of BODYSTRUCTURE.
|
||||||
return a.field, xbodystructure(part)
|
return a.field, xbodystructure(part, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.peekOrSeen(a.peek)
|
cmd.peekOrSeen(a.peek)
|
||||||
|
@ -865,20 +865,33 @@ func bodyFldEnc(s string) token {
|
||||||
|
|
||||||
// xbodystructure returns a "body".
|
// xbodystructure returns a "body".
|
||||||
// calls itself for multipart messages and message/{rfc822,global}.
|
// calls itself for multipart messages and message/{rfc822,global}.
|
||||||
func xbodystructure(p *message.Part) token {
|
func xbodystructure(p *message.Part, extensible bool) token {
|
||||||
if p.MediaType == "MULTIPART" {
|
if p.MediaType == "MULTIPART" {
|
||||||
// Multipart, ../rfc/9051:6355 ../rfc/9051:6411
|
// Multipart, ../rfc/9051:6355 ../rfc/9051:6411
|
||||||
var bodies concat
|
var bodies concat
|
||||||
for i := range p.Parts {
|
for i := range p.Parts {
|
||||||
bodies = append(bodies, xbodystructure(&p.Parts[i]))
|
bodies = append(bodies, xbodystructure(&p.Parts[i], extensible))
|
||||||
}
|
}
|
||||||
return listspace{bodies, string0(p.MediaSubType)}
|
r := listspace{bodies, string0(p.MediaSubType)}
|
||||||
|
if extensible {
|
||||||
|
if len(p.ContentTypeParams) == 0 {
|
||||||
|
r = append(r, nilt)
|
||||||
|
} else {
|
||||||
|
params := make(listspace, 0, 2*len(p.ContentTypeParams))
|
||||||
|
for k, v := range p.ContentTypeParams {
|
||||||
|
params = append(params, string0(k), string0(v))
|
||||||
|
}
|
||||||
|
r = append(r, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// ../rfc/9051:6355
|
// ../rfc/9051:6355
|
||||||
|
var r listspace
|
||||||
if p.MediaType == "TEXT" {
|
if p.MediaType == "TEXT" {
|
||||||
// ../rfc/9051:6404 ../rfc/9051:6418
|
// ../rfc/9051:6404 ../rfc/9051:6418
|
||||||
return listspace{
|
r = listspace{
|
||||||
dquote("TEXT"), string0(p.MediaSubType), // ../rfc/9051:6739
|
dquote("TEXT"), string0(p.MediaSubType), // ../rfc/9051:6739
|
||||||
// ../rfc/9051:6376
|
// ../rfc/9051:6376
|
||||||
bodyFldParams(p.ContentTypeParams), // ../rfc/9051:6401
|
bodyFldParams(p.ContentTypeParams), // ../rfc/9051:6401
|
||||||
|
@ -891,7 +904,7 @@ func xbodystructure(p *message.Part) token {
|
||||||
} else if p.MediaType == "MESSAGE" && (p.MediaSubType == "RFC822" || p.MediaSubType == "GLOBAL") {
|
} else if p.MediaType == "MESSAGE" && (p.MediaSubType == "RFC822" || p.MediaSubType == "GLOBAL") {
|
||||||
// ../rfc/9051:6415
|
// ../rfc/9051:6415
|
||||||
// note: we don't have to prepare p.Message for reading, because we aren't going to read from it.
|
// note: we don't have to prepare p.Message for reading, because we aren't going to read from it.
|
||||||
return listspace{
|
r = listspace{
|
||||||
dquote("MESSAGE"), dquote(p.MediaSubType), // ../rfc/9051:6732
|
dquote("MESSAGE"), dquote(p.MediaSubType), // ../rfc/9051:6732
|
||||||
// ../rfc/9051:6376
|
// ../rfc/9051:6376
|
||||||
bodyFldParams(p.ContentTypeParams), // ../rfc/9051:6401
|
bodyFldParams(p.ContentTypeParams), // ../rfc/9051:6401
|
||||||
|
@ -900,25 +913,28 @@ func xbodystructure(p *message.Part) token {
|
||||||
bodyFldEnc(p.ContentTransferEncoding),
|
bodyFldEnc(p.ContentTransferEncoding),
|
||||||
number(p.EndOffset - p.BodyOffset),
|
number(p.EndOffset - p.BodyOffset),
|
||||||
xenvelope(p.Message),
|
xenvelope(p.Message),
|
||||||
xbodystructure(p.Message),
|
xbodystructure(p.Message, extensible),
|
||||||
number(p.RawLineCount), // todo: or mp.RawLineCount?
|
number(p.RawLineCount), // todo: or mp.RawLineCount?
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
var media token
|
||||||
|
switch p.MediaType {
|
||||||
|
case "APPLICATION", "AUDIO", "IMAGE", "FONT", "MESSAGE", "MODEL", "VIDEO":
|
||||||
|
media = dquote(p.MediaType)
|
||||||
|
default:
|
||||||
|
media = string0(p.MediaType)
|
||||||
|
}
|
||||||
|
// ../rfc/9051:6404 ../rfc/9051:6407
|
||||||
|
r = listspace{
|
||||||
|
media, string0(p.MediaSubType), // ../rfc/9051:6723
|
||||||
|
// ../rfc/9051:6376
|
||||||
|
bodyFldParams(p.ContentTypeParams), // ../rfc/9051:6401
|
||||||
|
nilOrString(p.ContentID),
|
||||||
|
nilOrString(p.ContentDescription),
|
||||||
|
bodyFldEnc(p.ContentTransferEncoding),
|
||||||
|
number(p.EndOffset - p.BodyOffset),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var media token
|
// todo: if "extensible", we could add the value of the "content-md5" header. we don't have it in our parsed data structure, so we don't add it. likely no one would use it, also not any of the other optional fields. ../rfc/9051:6366
|
||||||
switch p.MediaType {
|
return r
|
||||||
case "APPLICATION", "AUDIO", "IMAGE", "FONT", "MESSAGE", "MODEL", "VIDEO":
|
|
||||||
media = dquote(p.MediaType)
|
|
||||||
default:
|
|
||||||
media = string0(p.MediaType)
|
|
||||||
}
|
|
||||||
// ../rfc/9051:6404 ../rfc/9051:6407
|
|
||||||
return listspace{
|
|
||||||
media, string0(p.MediaSubType), // ../rfc/9051:6723
|
|
||||||
// ../rfc/9051:6376
|
|
||||||
bodyFldParams(p.ContentTypeParams), // ../rfc/9051:6401
|
|
||||||
nilOrString(p.ContentID),
|
|
||||||
nilOrString(p.ContentDescription),
|
|
||||||
bodyFldEnc(p.ContentTransferEncoding),
|
|
||||||
number(p.EndOffset - p.BodyOffset),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,6 +241,7 @@ func TestFetch(t *testing.T) {
|
||||||
imapclient.BodyTypeBasic{MediaType: "IMAGE", MediaSubtype: "JPEG", BodyFields: imapclient.BodyFields{CTE: "BASE64"}},
|
imapclient.BodyTypeBasic{MediaType: "IMAGE", MediaSubtype: "JPEG", BodyFields: imapclient.BodyFields{CTE: "BASE64"}},
|
||||||
},
|
},
|
||||||
MediaSubtype: "PARALLEL",
|
MediaSubtype: "PARALLEL",
|
||||||
|
Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-2"}}},
|
||||||
},
|
},
|
||||||
imapclient.BodyTypeText{MediaType: "TEXT", MediaSubtype: "ENRICHED", BodyFields: imapclient.BodyFields{Octets: 145}, Lines: 5},
|
imapclient.BodyTypeText{MediaType: "TEXT", MediaSubtype: "ENRICHED", BodyFields: imapclient.BodyFields{Octets: 145}, Lines: 5},
|
||||||
imapclient.BodyTypeMsg{
|
imapclient.BodyTypeMsg{
|
||||||
|
@ -260,6 +261,7 @@ func TestFetch(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MediaSubtype: "MIXED",
|
MediaSubtype: "MIXED",
|
||||||
|
Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-1"}}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tc.client.Append("inbox", nil, &received, []byte(nestedMessage))
|
tc.client.Append("inbox", nil, &received, []byte(nestedMessage))
|
||||||
|
|
Loading…
Reference in a new issue