mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 08:23:48 +03:00
add support for parsing the imap "bodystructure" extensible form
not generating it yet from imapserver because we don't have content-md5 available. we could send "nil" instead of any actual content-md5 header (and probably no contemporary messages include a content-md5 header), but it would not be correct. if no known clients have problems in practice with absent extensible data, it's better to just leave the bodystructure as is, with extensible data. for issue #217 by danieleggert
This commit is contained in:
parent
81c179bb4c
commit
5d97bf198a
2 changed files with 239 additions and 63 deletions
|
@ -54,6 +54,10 @@ func (c *Conn) readrune() (rune, error) {
|
||||||
return x, err
|
return x, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) space() bool {
|
||||||
|
return c.take(' ')
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Conn) xspace() {
|
func (c *Conn) xspace() {
|
||||||
c.xtake(" ")
|
c.xtake(" ")
|
||||||
}
|
}
|
||||||
|
@ -70,6 +74,10 @@ func (c *Conn) peek(exp byte) bool {
|
||||||
return err == nil && strings.EqualFold(string(rune(b)), string(rune(exp)))
|
return err == nil && strings.EqualFold(string(rune(b)), string(rune(exp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) peekstring() bool {
|
||||||
|
return c.peek('"') || c.peek('{')
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Conn) take(exp byte) bool {
|
func (c *Conn) take(exp byte) bool {
|
||||||
if c.peek(exp) {
|
if c.peek(exp) {
|
||||||
_, _ = c.readbyte()
|
_, _ = c.readbyte()
|
||||||
|
@ -141,7 +149,7 @@ func (c *Conn) xrespCode() (string, CodeArg) {
|
||||||
|
|
||||||
if _, ok := knownCodes[W]; !ok {
|
if _, ok := knownCodes[W]; !ok {
|
||||||
var args []string
|
var args []string
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
arg := ""
|
arg := ""
|
||||||
for !c.peek(' ') && !c.peek(']') {
|
for !c.peek(' ') && !c.peek(']') {
|
||||||
arg += string(rune(c.xbyte()))
|
arg += string(rune(c.xbyte()))
|
||||||
|
@ -155,10 +163,10 @@ func (c *Conn) xrespCode() (string, CodeArg) {
|
||||||
switch W {
|
switch W {
|
||||||
case "BADCHARSET":
|
case "BADCHARSET":
|
||||||
var l []string // Must be nil initially.
|
var l []string // Must be nil initially.
|
||||||
if c.take(' ') {
|
if c.space() {
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
l = []string{c.xcharset()}
|
l = []string{c.xcharset()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
l = append(l, c.xcharset())
|
l = append(l, c.xcharset())
|
||||||
}
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
|
@ -167,7 +175,7 @@ func (c *Conn) xrespCode() (string, CodeArg) {
|
||||||
case "CAPABILITY":
|
case "CAPABILITY":
|
||||||
c.xtake(" ")
|
c.xtake(" ")
|
||||||
caps := []string{c.xatom()}
|
caps := []string{c.xatom()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
caps = append(caps, c.xatom())
|
caps = append(caps, c.xatom())
|
||||||
}
|
}
|
||||||
c.CapAvailable = map[Capability]struct{}{}
|
c.CapAvailable = map[Capability]struct{}{}
|
||||||
|
@ -178,10 +186,10 @@ func (c *Conn) xrespCode() (string, CodeArg) {
|
||||||
|
|
||||||
case "PERMANENTFLAGS":
|
case "PERMANENTFLAGS":
|
||||||
l := []string{} // Must be non-nil.
|
l := []string{} // Must be non-nil.
|
||||||
if c.take(' ') {
|
if c.space() {
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
l = []string{c.xflagPerm()}
|
l = []string{c.xflagPerm()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
l = append(l, c.xflagPerm())
|
l = append(l, c.xflagPerm())
|
||||||
}
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
|
@ -248,6 +256,14 @@ func (c *Conn) xdigits() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) peekdigit() bool {
|
||||||
|
if b, err := c.readbyte(); err == nil {
|
||||||
|
c.unreadbyte()
|
||||||
|
return b >= '0' && b <= '9'
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Conn) xint32() int32 {
|
func (c *Conn) xint32() int32 {
|
||||||
s := c.xdigits()
|
s := c.xdigits()
|
||||||
num, err := strconv.ParseInt(s, 10, 32)
|
num, err := strconv.ParseInt(s, 10, 32)
|
||||||
|
@ -321,7 +337,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
case "CAPABILITY":
|
case "CAPABILITY":
|
||||||
// ../rfc/9051:6427
|
// ../rfc/9051:6427
|
||||||
var caps []string
|
var caps []string
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
caps = append(caps, c.xnonspace())
|
caps = append(caps, c.xnonspace())
|
||||||
}
|
}
|
||||||
c.CapAvailable = map[Capability]struct{}{}
|
c.CapAvailable = map[Capability]struct{}{}
|
||||||
|
@ -335,7 +351,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
case "ENABLED":
|
case "ENABLED":
|
||||||
// ../rfc/9051:6520
|
// ../rfc/9051:6520
|
||||||
var caps []string
|
var caps []string
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
caps = append(caps, c.xnonspace())
|
caps = append(caps, c.xnonspace())
|
||||||
}
|
}
|
||||||
for _, cap := range caps {
|
for _, cap := range caps {
|
||||||
|
@ -427,7 +443,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
// ../rfc/9051:6809
|
// ../rfc/9051:6809
|
||||||
c.xneedDisabled("untagged SEARCH response", CapIMAP4rev2)
|
c.xneedDisabled("untagged SEARCH response", CapIMAP4rev2)
|
||||||
var nums []uint32
|
var nums []uint32
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
// ../rfc/7162:2557
|
// ../rfc/7162:2557
|
||||||
if c.take('(') {
|
if c.take('(') {
|
||||||
c.xtake("MODSEQ")
|
c.xtake("MODSEQ")
|
||||||
|
@ -473,7 +489,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
params[k] = v
|
params[k] = v
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
}
|
}
|
||||||
c.xcrlf()
|
c.xcrlf()
|
||||||
return UntaggedID(params)
|
return UntaggedID(params)
|
||||||
|
@ -497,7 +513,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
c.xspace()
|
c.xspace()
|
||||||
c.xastring()
|
c.xastring()
|
||||||
var roots []string
|
var roots []string
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
root := c.xastring()
|
root := c.xastring()
|
||||||
roots = append(roots, root)
|
roots = append(roots, root)
|
||||||
}
|
}
|
||||||
|
@ -523,7 +539,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
seen := map[QuotaResourceName]bool{}
|
seen := map[QuotaResourceName]bool{}
|
||||||
l := []QuotaResource{xresource()}
|
l := []QuotaResource{xresource()}
|
||||||
seen[l[0].Name] = true
|
seen[l[0].Name] = true
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
res := xresource()
|
res := xresource()
|
||||||
if seen[res.Name] {
|
if seen[res.Name] {
|
||||||
c.xerrorf("duplicate resource name %q", res.Name)
|
c.xerrorf("duplicate resource name %q", res.Name)
|
||||||
|
@ -583,7 +599,7 @@ func (c *Conn) xuntagged() Untagged {
|
||||||
func (c *Conn) xfetch(num uint32) UntaggedFetch {
|
func (c *Conn) xfetch(num uint32) UntaggedFetch {
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
attrs := []FetchAttr{c.xmsgatt1()}
|
attrs := []FetchAttr{c.xmsgatt1()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
attrs = append(attrs, c.xmsgatt1())
|
attrs = append(attrs, c.xmsgatt1())
|
||||||
}
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
|
@ -611,7 +627,7 @@ func (c *Conn) xmsgatt1() FetchAttr {
|
||||||
var flags []string
|
var flags []string
|
||||||
if !c.take(')') {
|
if !c.take(')') {
|
||||||
flags = []string{c.xflag()}
|
flags = []string{c.xflag()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
flags = append(flags, c.xflag())
|
flags = append(flags, c.xflag())
|
||||||
}
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
|
@ -646,8 +662,8 @@ func (c *Conn) xmsgatt1() FetchAttr {
|
||||||
return FetchRFC822Text(s)
|
return FetchRFC822Text(s)
|
||||||
|
|
||||||
case "BODY":
|
case "BODY":
|
||||||
if c.take(' ') {
|
if c.space() {
|
||||||
return FetchBodystructure{F, c.xbodystructure()}
|
return FetchBodystructure{F, c.xbodystructure(false)}
|
||||||
}
|
}
|
||||||
c.record = true
|
c.record = true
|
||||||
section := c.xsection()
|
section := c.xsection()
|
||||||
|
@ -663,7 +679,7 @@ func (c *Conn) xmsgatt1() FetchAttr {
|
||||||
|
|
||||||
case "BODYSTRUCTURE":
|
case "BODYSTRUCTURE":
|
||||||
c.xspace()
|
c.xspace()
|
||||||
return FetchBodystructure{F, c.xbodystructure()}
|
return FetchBodystructure{F, c.xbodystructure(true)}
|
||||||
|
|
||||||
case "BINARY":
|
case "BINARY":
|
||||||
c.record = true
|
c.record = true
|
||||||
|
@ -703,7 +719,7 @@ func (c *Conn) xnilString() string {
|
||||||
} else if c.peek('{') {
|
} else if c.peek('{') {
|
||||||
return string(c.xliteral())
|
return string(c.xliteral())
|
||||||
} else {
|
} else {
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -831,50 +847,69 @@ func (c *Conn) xnilStringLiteral8() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ../rfc/9051:6355
|
// ../rfc/9051:6355
|
||||||
func (c *Conn) xbodystructure() any {
|
func (c *Conn) xbodystructure(extensibleForm bool) any {
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
if c.peek('(') {
|
if c.peek('(') {
|
||||||
// ../rfc/9051:6411
|
// ../rfc/9051:6411
|
||||||
parts := []any{c.xbodystructure()}
|
parts := []any{c.xbodystructure(extensibleForm)}
|
||||||
for c.peek('(') {
|
for c.peek('(') {
|
||||||
parts = append(parts, c.xbodystructure())
|
parts = append(parts, c.xbodystructure(extensibleForm))
|
||||||
}
|
}
|
||||||
c.xspace()
|
c.xspace()
|
||||||
mediaSubtype := c.xstring()
|
mediaSubtype := c.xstring()
|
||||||
// todo: parse optional body-ext-mpart
|
var ext *BodyExtensionMpart
|
||||||
|
if extensibleForm && c.space() {
|
||||||
|
ext = c.xbodyExtMpart()
|
||||||
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
return BodyTypeMpart{parts, mediaSubtype}
|
return BodyTypeMpart{parts, mediaSubtype, ext}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: verify the media(sub)type is valid for returned data.
|
||||||
|
|
||||||
|
var ext *BodyExtension1Part
|
||||||
mediaType := c.xstring()
|
mediaType := c.xstring()
|
||||||
c.xspace()
|
c.xspace()
|
||||||
mediaSubtype := c.xstring()
|
mediaSubtype := c.xstring()
|
||||||
c.xspace()
|
c.xspace()
|
||||||
bodyFields := c.xbodyFields()
|
bodyFields := c.xbodyFields()
|
||||||
if c.take(' ') {
|
if !c.space() {
|
||||||
if c.peek('(') {
|
// Basic type without extension.
|
||||||
// ../rfc/9051:6415
|
|
||||||
envelope := c.xenvelope()
|
|
||||||
c.xspace()
|
|
||||||
bodyStructure := c.xbodystructure()
|
|
||||||
c.xspace()
|
|
||||||
lines := c.xint64()
|
|
||||||
c.xtake(")")
|
|
||||||
return BodyTypeMsg{mediaType, mediaSubtype, bodyFields, envelope, bodyStructure, lines}
|
|
||||||
}
|
|
||||||
// ../rfc/9051:6418
|
|
||||||
lines := c.xint64()
|
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
return BodyTypeText{mediaType, mediaSubtype, bodyFields, lines}
|
return BodyTypeBasic{mediaType, mediaSubtype, bodyFields, nil}
|
||||||
|
}
|
||||||
|
if c.peek('(') {
|
||||||
|
// ../rfc/9051:6415
|
||||||
|
envelope := c.xenvelope()
|
||||||
|
c.xspace()
|
||||||
|
bodyStructure := c.xbodystructure(extensibleForm)
|
||||||
|
c.xspace()
|
||||||
|
lines := c.xint64()
|
||||||
|
if extensibleForm && c.space() {
|
||||||
|
ext = c.xbodyExt1Part()
|
||||||
|
}
|
||||||
|
c.xtake(")")
|
||||||
|
return BodyTypeMsg{mediaType, mediaSubtype, bodyFields, envelope, bodyStructure, lines, ext}
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(mediaType, "text") {
|
||||||
|
if !extensibleForm {
|
||||||
|
c.xerrorf("body result, basic type, with disallowed extensible form")
|
||||||
|
}
|
||||||
|
ext = c.xbodyExt1Part()
|
||||||
|
// ../rfc/9051:6407
|
||||||
|
c.xtake(")")
|
||||||
|
return BodyTypeBasic{mediaType, mediaSubtype, bodyFields, ext}
|
||||||
|
}
|
||||||
|
// ../rfc/9051:6418
|
||||||
|
lines := c.xint64()
|
||||||
|
if extensibleForm && c.space() {
|
||||||
|
ext = c.xbodyExt1Part()
|
||||||
}
|
}
|
||||||
// ../rfc/9051:6407
|
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
return BodyTypeBasic{mediaType, mediaSubtype, bodyFields}
|
return BodyTypeText{mediaType, mediaSubtype, bodyFields, lines, ext}
|
||||||
|
|
||||||
// todo: verify the media(sub)type is valid for returned data.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ../rfc/9051:6376
|
// ../rfc/9051:6376 ../rfc/3501:4604
|
||||||
func (c *Conn) xbodyFields() BodyFields {
|
func (c *Conn) xbodyFields() BodyFields {
|
||||||
params := c.xbodyFldParam()
|
params := c.xbodyFldParam()
|
||||||
c.xspace()
|
c.xspace()
|
||||||
|
@ -888,14 +923,58 @@ func (c *Conn) xbodyFields() BodyFields {
|
||||||
return BodyFields{params, contentID, contentDescr, cte, octets}
|
return BodyFields{params, contentID, contentDescr, cte, octets}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ../rfc/9051:6401
|
// ../rfc/9051:6371 ../rfc/3501:4599
|
||||||
|
func (c *Conn) xbodyExtMpart() (ext *BodyExtensionMpart) {
|
||||||
|
ext = &BodyExtensionMpart{}
|
||||||
|
ext.Params = c.xbodyFldParam()
|
||||||
|
if !c.space() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ext.Disposition, ext.DispositionParams = c.xbodyFldDsp()
|
||||||
|
if !c.space() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ext.Language = c.xbodyFldLang()
|
||||||
|
if !c.space() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ext.Location = c.xbodyFldLoc()
|
||||||
|
for c.space() {
|
||||||
|
ext.More = append(ext.More, c.xbodyExtension())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../rfc/9051:6366 ../rfc/3501:4584
|
||||||
|
func (c *Conn) xbodyExt1Part() (ext *BodyExtension1Part) {
|
||||||
|
ext = &BodyExtension1Part{}
|
||||||
|
ext.MD5 = c.xnilString()
|
||||||
|
if !c.space() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ext.Disposition, ext.DispositionParams = c.xbodyFldDsp()
|
||||||
|
if !c.space() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ext.Language = c.xbodyFldLang()
|
||||||
|
if !c.space() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ext.Location = c.xbodyFldLoc()
|
||||||
|
for c.space() {
|
||||||
|
ext.More = append(ext.More, c.xbodyExtension())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../rfc/9051:6401 ../rfc/3501:4626
|
||||||
func (c *Conn) xbodyFldParam() [][2]string {
|
func (c *Conn) xbodyFldParam() [][2]string {
|
||||||
if c.take('(') {
|
if c.take('(') {
|
||||||
k := c.xstring()
|
k := c.xstring()
|
||||||
c.xspace()
|
c.xspace()
|
||||||
v := c.xstring()
|
v := c.xstring()
|
||||||
l := [][2]string{{k, v}}
|
l := [][2]string{{k, v}}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
k = c.xstring()
|
k = c.xstring()
|
||||||
c.xspace()
|
c.xspace()
|
||||||
v = c.xstring()
|
v = c.xstring()
|
||||||
|
@ -904,10 +983,67 @@ func (c *Conn) xbodyFldParam() [][2]string {
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ../rfc/9051:6381 ../rfc/3501:4609
|
||||||
|
func (c *Conn) xbodyFldDsp() (string, [][2]string) {
|
||||||
|
if !c.take('(') {
|
||||||
|
c.xtake("nil")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
disposition := c.xstring()
|
||||||
|
c.xspace()
|
||||||
|
param := c.xbodyFldParam()
|
||||||
|
c.xtake(")")
|
||||||
|
return disposition, param
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../rfc/9051:6391 ../rfc/3501:4616
|
||||||
|
func (c *Conn) xbodyFldLang() (lang []string) {
|
||||||
|
if c.take('(') {
|
||||||
|
lang = []string{c.xstring()}
|
||||||
|
for c.space() {
|
||||||
|
lang = append(lang, c.xstring())
|
||||||
|
}
|
||||||
|
c.xtake(")")
|
||||||
|
return lang
|
||||||
|
}
|
||||||
|
if c.peekstring() {
|
||||||
|
return []string{c.xstring()}
|
||||||
|
}
|
||||||
|
c.xtake("nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../rfc/9051:6393 ../rfc/3501:4618
|
||||||
|
func (c *Conn) xbodyFldLoc() string {
|
||||||
|
return c.xnilString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../rfc/9051:6357 ../rfc/3501:4575
|
||||||
|
func (c *Conn) xbodyExtension() (ext BodyExtension) {
|
||||||
|
if c.take('(') {
|
||||||
|
for {
|
||||||
|
ext.More = append(ext.More, c.xbodyExtension())
|
||||||
|
if !c.space() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.xtake(")")
|
||||||
|
} else if c.peekdigit() {
|
||||||
|
num := c.xint64()
|
||||||
|
ext.Number = &num
|
||||||
|
} else if c.peekstring() {
|
||||||
|
str := c.xstring()
|
||||||
|
ext.String = &str
|
||||||
|
} else {
|
||||||
|
c.xtake("nil")
|
||||||
|
}
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
|
||||||
// ../rfc/9051:6522
|
// ../rfc/9051:6522
|
||||||
func (c *Conn) xenvelope() Envelope {
|
func (c *Conn) xenvelope() Envelope {
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
|
@ -937,7 +1073,7 @@ func (c *Conn) xenvelope() Envelope {
|
||||||
// ../rfc/9051:6526
|
// ../rfc/9051:6526
|
||||||
func (c *Conn) xaddresses() []Address {
|
func (c *Conn) xaddresses() []Address {
|
||||||
if !c.take('(') {
|
if !c.take('(') {
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
l := []Address{c.xaddress()}
|
l := []Address{c.xaddress()}
|
||||||
|
@ -967,7 +1103,7 @@ func (c *Conn) xflagList() []string {
|
||||||
var l []string
|
var l []string
|
||||||
if !c.take(')') {
|
if !c.take(')') {
|
||||||
l = []string{c.xflag()}
|
l = []string{c.xflag()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
l = append(l, c.xflag())
|
l = append(l, c.xflag())
|
||||||
}
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
|
@ -981,7 +1117,7 @@ func (c *Conn) xmailboxList() UntaggedList {
|
||||||
var flags []string
|
var flags []string
|
||||||
if !c.peek(')') {
|
if !c.peek(')') {
|
||||||
flags = append(flags, c.xflag())
|
flags = append(flags, c.xflag())
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
flags = append(flags, c.xflag())
|
flags = append(flags, c.xflag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -996,16 +1132,16 @@ func (c *Conn) xmailboxList() UntaggedList {
|
||||||
}
|
}
|
||||||
b = byte(quoted[0])
|
b = byte(quoted[0])
|
||||||
} else if !c.peek(' ') {
|
} else if !c.peek(' ') {
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
}
|
}
|
||||||
c.xspace()
|
c.xspace()
|
||||||
mailbox := c.xastring()
|
mailbox := c.xastring()
|
||||||
ul := UntaggedList{flags, b, mailbox, nil, ""}
|
ul := UntaggedList{flags, b, mailbox, nil, ""}
|
||||||
if c.take(' ') {
|
if c.space() {
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
if !c.peek(')') {
|
if !c.peek(')') {
|
||||||
c.xmboxListExtendedItem(&ul)
|
c.xmboxListExtendedItem(&ul)
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
c.xmboxListExtendedItem(&ul)
|
c.xmboxListExtendedItem(&ul)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1108,7 +1244,7 @@ func (c *Conn) xtaggedExtComp() TaggedExtComp {
|
||||||
return TaggedExtComp{String: s}
|
return TaggedExtComp{String: s}
|
||||||
}
|
}
|
||||||
l := []TaggedExtComp{{String: s}}
|
l := []TaggedExtComp{{String: s}}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
l = append(l, c.xtaggedExtComp())
|
l = append(l, c.xtaggedExtComp())
|
||||||
}
|
}
|
||||||
return TaggedExtComp{Comps: l}
|
return TaggedExtComp{Comps: l}
|
||||||
|
@ -1117,7 +1253,7 @@ func (c *Conn) xtaggedExtComp() TaggedExtComp {
|
||||||
// ../rfc/9051:6765
|
// ../rfc/9051:6765
|
||||||
func (c *Conn) xnamespace() []NamespaceDescr {
|
func (c *Conn) xnamespace() []NamespaceDescr {
|
||||||
if !c.take('(') {
|
if !c.take('(') {
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1141,7 +1277,7 @@ func (c *Conn) xnamespaceDescr() NamespaceDescr {
|
||||||
}
|
}
|
||||||
b = byte(s[0])
|
b = byte(s[0])
|
||||||
} else {
|
} else {
|
||||||
c.xtake("NIL")
|
c.xtake("nil")
|
||||||
}
|
}
|
||||||
var exts []NamespaceExtension
|
var exts []NamespaceExtension
|
||||||
for !c.take(')') {
|
for !c.take(')') {
|
||||||
|
@ -1150,7 +1286,7 @@ func (c *Conn) xnamespaceDescr() NamespaceDescr {
|
||||||
c.xspace()
|
c.xspace()
|
||||||
c.xtake("(")
|
c.xtake("(")
|
||||||
values := []string{c.xstring()}
|
values := []string{c.xstring()}
|
||||||
for c.take(' ') {
|
for c.space() {
|
||||||
values = append(values, c.xstring())
|
values = append(values, c.xstring())
|
||||||
}
|
}
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
|
@ -1172,7 +1308,7 @@ func (c *Conn) xneedDisabled(msg string, caps ...Capability) {
|
||||||
// Already consumed: "ESEARCH"
|
// Already consumed: "ESEARCH"
|
||||||
func (c *Conn) xesearchResponse() (r UntaggedEsearch) {
|
func (c *Conn) xesearchResponse() (r UntaggedEsearch) {
|
||||||
|
|
||||||
if !c.take(' ') {
|
if !c.space() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.take('(') {
|
if c.take('(') {
|
||||||
|
@ -1182,14 +1318,14 @@ func (c *Conn) xesearchResponse() (r UntaggedEsearch) {
|
||||||
r.Correlator = c.xastring()
|
r.Correlator = c.xastring()
|
||||||
c.xtake(")")
|
c.xtake(")")
|
||||||
}
|
}
|
||||||
if !c.take(' ') {
|
if !c.space() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w := c.xnonspace()
|
w := c.xnonspace()
|
||||||
W := strings.ToUpper(w)
|
W := strings.ToUpper(w)
|
||||||
if W == "UID" {
|
if W == "UID" {
|
||||||
r.UID = true
|
r.UID = true
|
||||||
if !c.take(' ') {
|
if !c.space() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w = c.xnonspace()
|
w = c.xnonspace()
|
||||||
|
@ -1250,7 +1386,7 @@ func (c *Conn) xesearchResponse() (r UntaggedEsearch) {
|
||||||
r.Exts = append(r.Exts, ext)
|
r.Exts = append(r.Exts, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.take(' ') {
|
if !c.space() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
w = c.xnonspace() // todo: this is too loose
|
w = c.xnonspace() // todo: this is too loose
|
||||||
|
|
|
@ -479,21 +479,26 @@ type BodyFields struct {
|
||||||
Octets int32
|
Octets int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodyTypeMpart represents the body structure a multipart message, with subparts and the multipart media subtype. Used in a FETCH response.
|
// BodyTypeMpart represents the body structure a multipart message, with
|
||||||
|
// subparts and the multipart media subtype. Used in a FETCH response.
|
||||||
type BodyTypeMpart struct {
|
type BodyTypeMpart struct {
|
||||||
// ../rfc/9051:6411
|
// ../rfc/9051:6411
|
||||||
Bodies []any // BodyTypeBasic, BodyTypeMsg, BodyTypeText
|
Bodies []any // BodyTypeBasic, BodyTypeMsg, BodyTypeText
|
||||||
MediaSubtype string
|
MediaSubtype string
|
||||||
|
Ext *BodyExtensionMpart
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodyTypeBasic represents basic information about a part, used in a FETCH response.
|
// BodyTypeBasic represents basic information about a part, used in a FETCH
|
||||||
|
// response.
|
||||||
type BodyTypeBasic struct {
|
type BodyTypeBasic struct {
|
||||||
// ../rfc/9051:6407
|
// ../rfc/9051:6407
|
||||||
MediaType, MediaSubtype string
|
MediaType, MediaSubtype string
|
||||||
BodyFields BodyFields
|
BodyFields BodyFields
|
||||||
|
Ext *BodyExtension1Part
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodyTypeMsg represents an email message as a body structure, used in a FETCH response.
|
// BodyTypeMsg represents an email message as a body structure, used in a FETCH
|
||||||
|
// response.
|
||||||
type BodyTypeMsg struct {
|
type BodyTypeMsg struct {
|
||||||
// ../rfc/9051:6415
|
// ../rfc/9051:6415
|
||||||
MediaType, MediaSubtype string
|
MediaType, MediaSubtype string
|
||||||
|
@ -501,14 +506,49 @@ type BodyTypeMsg struct {
|
||||||
Envelope Envelope
|
Envelope Envelope
|
||||||
Bodystructure any // One of the BodyType*
|
Bodystructure any // One of the BodyType*
|
||||||
Lines int64
|
Lines int64
|
||||||
|
Ext *BodyExtension1Part
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodyTypeText represents a text part as a body structure, used in a FETCH response.
|
// BodyTypeText represents a text part as a body structure, used in a FETCH
|
||||||
|
// response.
|
||||||
type BodyTypeText struct {
|
type BodyTypeText struct {
|
||||||
// ../rfc/9051:6418
|
// ../rfc/9051:6418
|
||||||
MediaType, MediaSubtype string
|
MediaType, MediaSubtype string
|
||||||
BodyFields BodyFields
|
BodyFields BodyFields
|
||||||
Lines int64
|
Lines int64
|
||||||
|
Ext *BodyExtension1Part
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyExtension1Part has the extensible form fields of a BODYSTRUCTURE for
|
||||||
|
// multiparts.
|
||||||
|
type BodyExtensionMpart struct {
|
||||||
|
// ../rfc/9051:5986 ../rfc/3501:4161 ../rfc/9051:6371 ../rfc/3501:4599
|
||||||
|
Params [][2]string
|
||||||
|
Disposition string
|
||||||
|
DispositionParams [][2]string
|
||||||
|
Language []string
|
||||||
|
Location string
|
||||||
|
More []BodyExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyExtension1Part has the extensible form fields of a BODYSTRUCTURE for
|
||||||
|
// non-multiparts.
|
||||||
|
type BodyExtension1Part struct {
|
||||||
|
// ../rfc/9051:6023 ../rfc/3501:4191 ../rfc/9051:6366 ../rfc/3501:4584
|
||||||
|
MD5 string
|
||||||
|
Disposition string
|
||||||
|
DispositionParams [][2]string
|
||||||
|
Language []string
|
||||||
|
Location string
|
||||||
|
More []BodyExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyExtension has the additional extension fields for future expansion of
|
||||||
|
// extensions.
|
||||||
|
type BodyExtension struct {
|
||||||
|
String *string
|
||||||
|
Number *int64
|
||||||
|
More []BodyExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
// "BINARY" fetch response.
|
// "BINARY" fetch response.
|
||||||
|
|
Loading…
Reference in a new issue