mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
with strict message parsing, don't allow lines longer than 1000 bytes
This commit is contained in:
parent
34c2dcd49d
commit
983002b074
2 changed files with 34 additions and 15 deletions
|
@ -109,7 +109,6 @@ The code is heavily cross-referenced with the RFCs for readability/maintainabili
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- Improve message parsing, more lenient for imported messages
|
|
||||||
- Rewrite account and admin javascript to typescript
|
- Rewrite account and admin javascript to typescript
|
||||||
- Prepare data storage for JMAP
|
- Prepare data storage for JMAP
|
||||||
- IMAP THREAD extension
|
- IMAP THREAD extension
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package message
|
package message
|
||||||
|
|
||||||
// todo: we should be more forgiving when parsing, at least as an option for imported messages, possibly incoming as well, but not for submitted/outgoing messages.
|
|
||||||
// todo: allow more invalid content-type values, we now stop parsing on: empty media type (eg "content-type: ; name=..."), empty value for property (eg "charset=", missing quotes for characters that should be quoted (eg boundary containing "=" but without quotes), duplicate properties (two charsets), empty pairs (eg "text/html;;").
|
// todo: allow more invalid content-type values, we now stop parsing on: empty media type (eg "content-type: ; name=..."), empty value for property (eg "charset=", missing quotes for characters that should be quoted (eg boundary containing "=" but without quotes), duplicate properties (two charsets), empty pairs (eg "text/html;;").
|
||||||
// todo: what should our max line length be? rfc says 1000. messages exceed that. we should enforce 1000 for outgoing messages.
|
|
||||||
// todo: should we be forgiving when closing boundary in multipart message is missing? seems like spam messages do this...
|
// todo: should we be forgiving when closing boundary in multipart message is missing? seems like spam messages do this...
|
||||||
// todo: should we allow base64 messages where a line starts with a space? and possibly more whitespace. is happening in messages. coreutils base64 accepts it, encoding/base64 does not.
|
// todo: should we allow base64 messages where a line starts with a space? and possibly more whitespace. is happening in messages. coreutils base64 accepts it, encoding/base64 does not.
|
||||||
// todo: handle comments in headers?
|
// todo: handle comments in headers?
|
||||||
|
@ -662,7 +660,7 @@ func (p *Part) RawReader() io.Reader {
|
||||||
}
|
}
|
||||||
p.RawLineCount = 0
|
p.RawLineCount = 0
|
||||||
if p.parent == nil {
|
if p.parent == nil {
|
||||||
return &offsetReader{p, p.BodyOffset, p.strict, true, false}
|
return &offsetReader{p, p.BodyOffset, p.strict, true, false, 0}
|
||||||
}
|
}
|
||||||
return &boundReader{p: p, b: &bufAt{strict: p.strict, r: p.r, offset: p.BodyOffset}, prevlf: true}
|
return &boundReader{p: p, b: &bufAt{strict: p.strict, r: p.r, offset: p.BodyOffset}, prevlf: true}
|
||||||
}
|
}
|
||||||
|
@ -703,9 +701,20 @@ type bufAt struct {
|
||||||
scratch []byte
|
scratch []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: lower max line length? at least have a mode where we refuse anything beyong 1000 bytes. ../rfc/5321:3512
|
// Messages should not have lines longer than 78+2 bytes, and must not have
|
||||||
|
// lines longer than 998+2 bytes. But in practice they have longer lines. We
|
||||||
|
// have a higher limit, but for when parsing with strict we check for the 1000
|
||||||
|
// bytes limit.
|
||||||
|
// ../rfc/5321:3512
|
||||||
const maxLineLength = 8 * 1024
|
const maxLineLength = 8 * 1024
|
||||||
|
|
||||||
|
func (b *bufAt) maxLineLength() int {
|
||||||
|
if b.strict || moxvar.Pedantic {
|
||||||
|
return 1000
|
||||||
|
}
|
||||||
|
return maxLineLength
|
||||||
|
}
|
||||||
|
|
||||||
// ensure makes sure b.nbuf is up to maxLineLength, unless eof is encountered.
|
// ensure makes sure b.nbuf is up to maxLineLength, unless eof is encountered.
|
||||||
func (b *bufAt) ensure() error {
|
func (b *bufAt) ensure() error {
|
||||||
for _, c := range b.buf[:b.nbuf] {
|
for _, c := range b.buf[:b.nbuf] {
|
||||||
|
@ -714,12 +723,12 @@ func (b *bufAt) ensure() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if b.scratch == nil {
|
if b.scratch == nil {
|
||||||
b.scratch = make([]byte, maxLineLength)
|
b.scratch = make([]byte, b.maxLineLength())
|
||||||
}
|
}
|
||||||
if b.buf == nil {
|
if b.buf == nil {
|
||||||
b.buf = make([]byte, maxLineLength)
|
b.buf = make([]byte, b.maxLineLength())
|
||||||
}
|
}
|
||||||
for b.nbuf < maxLineLength {
|
for b.nbuf < b.maxLineLength() {
|
||||||
n, err := b.r.ReadAt(b.buf[b.nbuf:], b.offset+int64(b.nbuf))
|
n, err := b.r.ReadAt(b.buf[b.nbuf:], b.offset+int64(b.nbuf))
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
b.nbuf += n
|
b.nbuf += n
|
||||||
|
@ -772,7 +781,7 @@ func (b *bufAt) line(consume, requirecrlf bool) (buf []byte, crlf bool, err erro
|
||||||
}
|
}
|
||||||
return b.scratch, true, nil
|
return b.scratch, true, nil
|
||||||
}
|
}
|
||||||
if b.nbuf >= maxLineLength {
|
if b.nbuf >= b.maxLineLength() {
|
||||||
return nil, false, errLineTooLong
|
return nil, false, errLineTooLong
|
||||||
}
|
}
|
||||||
if requirecrlf {
|
if requirecrlf {
|
||||||
|
@ -801,17 +810,22 @@ func (b *bufAt) PeekByte() (byte, error) {
|
||||||
// offsetReader reads from p.r starting from offset, and RawLineCount on p.
|
// offsetReader reads from p.r starting from offset, and RawLineCount on p.
|
||||||
// offsetReader validates lines end with \r\n.
|
// offsetReader validates lines end with \r\n.
|
||||||
type offsetReader struct {
|
type offsetReader struct {
|
||||||
p *Part
|
p *Part
|
||||||
offset int64
|
offset int64
|
||||||
strict bool
|
strict bool
|
||||||
prevlf bool
|
prevlf bool
|
||||||
prevcr bool
|
prevcr bool
|
||||||
|
linelength int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *offsetReader) Read(buf []byte) (int, error) {
|
func (r *offsetReader) Read(buf []byte) (int, error) {
|
||||||
n, err := r.p.r.ReadAt(buf, r.offset)
|
n, err := r.p.r.ReadAt(buf, r.offset)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
r.offset += int64(n)
|
r.offset += int64(n)
|
||||||
|
max := maxLineLength
|
||||||
|
if r.strict || moxvar.Pedantic {
|
||||||
|
max = 1000
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range buf[:n] {
|
for _, c := range buf[:n] {
|
||||||
if r.prevlf {
|
if r.prevlf {
|
||||||
|
@ -826,6 +840,12 @@ func (r *offsetReader) Read(buf []byte) (int, error) {
|
||||||
}
|
}
|
||||||
r.prevlf = c == '\n'
|
r.prevlf = c == '\n'
|
||||||
r.prevcr = c == '\r'
|
r.prevcr = c == '\r'
|
||||||
|
r.linelength++
|
||||||
|
if c == '\n' {
|
||||||
|
r.linelength = 0
|
||||||
|
} else if r.linelength > max && err == nil {
|
||||||
|
err = errLineTooLong
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
@ -924,7 +944,7 @@ func (b *boundReader) Read(buf []byte) (count int, rerr error) {
|
||||||
line = line[n:]
|
line = line[n:]
|
||||||
if len(line) > 0 {
|
if len(line) > 0 {
|
||||||
if b.buf == nil {
|
if b.buf == nil {
|
||||||
b.buf = make([]byte, maxLineLength)
|
b.buf = make([]byte, b.b.maxLineLength())
|
||||||
}
|
}
|
||||||
copy(b.buf, line)
|
copy(b.buf, line)
|
||||||
b.nbuf = len(line)
|
b.nbuf = len(line)
|
||||||
|
|
Loading…
Reference in a new issue