mirror of
https://github.com/ergochat/ergo.git
synced 2025-05-03 13:17:36 +02:00
200 lines
3.8 KiB
Go
200 lines
3.8 KiB
Go
package dkim
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// Canonicalization is a canonicalization algorithm.
|
|
type Canonicalization string
|
|
|
|
const (
|
|
CanonicalizationSimple Canonicalization = "simple"
|
|
CanonicalizationRelaxed = "relaxed"
|
|
)
|
|
|
|
type canonicalizer interface {
|
|
CanonicalizeHeader(s string) string
|
|
CanonicalizeBody(w io.Writer) io.WriteCloser
|
|
}
|
|
|
|
var canonicalizers = map[Canonicalization]canonicalizer{
|
|
CanonicalizationSimple: new(simpleCanonicalizer),
|
|
CanonicalizationRelaxed: new(relaxedCanonicalizer),
|
|
}
|
|
|
|
// crlfFixer fixes any lone LF without a preceding CR.
|
|
type crlfFixer struct {
|
|
cr bool
|
|
}
|
|
|
|
func (cf *crlfFixer) Fix(b []byte) []byte {
|
|
res := make([]byte, 0, len(b))
|
|
for _, ch := range b {
|
|
prevCR := cf.cr
|
|
cf.cr = false
|
|
switch ch {
|
|
case '\r':
|
|
cf.cr = true
|
|
case '\n':
|
|
if !prevCR {
|
|
res = append(res, '\r')
|
|
}
|
|
}
|
|
res = append(res, ch)
|
|
}
|
|
return res
|
|
}
|
|
|
|
type simpleCanonicalizer struct{}
|
|
|
|
func (c *simpleCanonicalizer) CanonicalizeHeader(s string) string {
|
|
return s
|
|
}
|
|
|
|
type simpleBodyCanonicalizer struct {
|
|
w io.Writer
|
|
crlfBuf []byte
|
|
crlfFixer crlfFixer
|
|
}
|
|
|
|
func (c *simpleBodyCanonicalizer) Write(b []byte) (int, error) {
|
|
written := len(b)
|
|
b = append(c.crlfBuf, b...)
|
|
|
|
b = c.crlfFixer.Fix(b)
|
|
|
|
end := len(b)
|
|
// If it ends with \r, maybe the next write will begin with \n
|
|
if end > 0 && b[end-1] == '\r' {
|
|
end--
|
|
}
|
|
// Keep all \r\n sequences
|
|
for end >= 2 {
|
|
prev := b[end-2]
|
|
cur := b[end-1]
|
|
if prev != '\r' || cur != '\n' {
|
|
break
|
|
}
|
|
end -= 2
|
|
}
|
|
|
|
c.crlfBuf = b[end:]
|
|
|
|
var err error
|
|
if end > 0 {
|
|
_, err = c.w.Write(b[:end])
|
|
}
|
|
return written, err
|
|
}
|
|
|
|
func (c *simpleBodyCanonicalizer) Close() error {
|
|
// Flush crlfBuf if it ends with a single \r (without a matching \n)
|
|
if len(c.crlfBuf) > 0 && c.crlfBuf[len(c.crlfBuf)-1] == '\r' {
|
|
if _, err := c.w.Write(c.crlfBuf); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
c.crlfBuf = nil
|
|
|
|
if _, err := c.w.Write([]byte(crlf)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *simpleCanonicalizer) CanonicalizeBody(w io.Writer) io.WriteCloser {
|
|
return &simpleBodyCanonicalizer{w: w}
|
|
}
|
|
|
|
type relaxedCanonicalizer struct{}
|
|
|
|
func (c *relaxedCanonicalizer) CanonicalizeHeader(s string) string {
|
|
k, v, ok := strings.Cut(s, ":")
|
|
if !ok {
|
|
return strings.TrimSpace(strings.ToLower(s)) + ":" + crlf
|
|
}
|
|
|
|
k = strings.TrimSpace(strings.ToLower(k))
|
|
v = strings.Join(strings.FieldsFunc(v, func(r rune) bool {
|
|
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
|
}), " ")
|
|
return k + ":" + v + crlf
|
|
}
|
|
|
|
type relaxedBodyCanonicalizer struct {
|
|
w io.Writer
|
|
crlfBuf []byte
|
|
wsp bool
|
|
written bool
|
|
crlfFixer crlfFixer
|
|
}
|
|
|
|
func (c *relaxedBodyCanonicalizer) Write(b []byte) (int, error) {
|
|
written := len(b)
|
|
|
|
b = c.crlfFixer.Fix(b)
|
|
|
|
canonical := make([]byte, 0, len(b))
|
|
for _, ch := range b {
|
|
if ch == ' ' || ch == '\t' {
|
|
c.wsp = true
|
|
} else if ch == '\r' || ch == '\n' {
|
|
c.wsp = false
|
|
c.crlfBuf = append(c.crlfBuf, ch)
|
|
} else {
|
|
if len(c.crlfBuf) > 0 {
|
|
canonical = append(canonical, c.crlfBuf...)
|
|
c.crlfBuf = c.crlfBuf[:0]
|
|
}
|
|
if c.wsp {
|
|
canonical = append(canonical, ' ')
|
|
c.wsp = false
|
|
}
|
|
|
|
canonical = append(canonical, ch)
|
|
}
|
|
}
|
|
|
|
if !c.written && len(canonical) > 0 {
|
|
c.written = true
|
|
}
|
|
|
|
_, err := c.w.Write(canonical)
|
|
return written, err
|
|
}
|
|
|
|
func (c *relaxedBodyCanonicalizer) Close() error {
|
|
if c.written {
|
|
if _, err := c.w.Write([]byte(crlf)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *relaxedCanonicalizer) CanonicalizeBody(w io.Writer) io.WriteCloser {
|
|
return &relaxedBodyCanonicalizer{w: w}
|
|
}
|
|
|
|
type limitedWriter struct {
|
|
W io.Writer
|
|
N int64
|
|
}
|
|
|
|
func (w *limitedWriter) Write(b []byte) (int, error) {
|
|
if w.N <= 0 {
|
|
return len(b), nil
|
|
}
|
|
|
|
skipped := 0
|
|
if int64(len(b)) > w.N {
|
|
b = b[:w.N]
|
|
skipped = int(int64(len(b)) - w.N)
|
|
}
|
|
|
|
n, err := w.W.Write(b)
|
|
w.N -= int64(n)
|
|
return n + skipped, err
|
|
}
|