mirror of
https://github.com/ergochat/ergo.git
synced 2025-05-03 13:17:36 +02:00
168 lines
2.9 KiB
Go
168 lines
2.9 KiB
Go
package dkim
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/textproto"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const crlf = "\r\n"
|
|
|
|
type header []string
|
|
|
|
func readHeader(r *bufio.Reader) (header, error) {
|
|
tr := textproto.NewReader(r)
|
|
|
|
var h header
|
|
for {
|
|
l, err := tr.ReadLine()
|
|
if err != nil {
|
|
return h, fmt.Errorf("failed to read header: %v", err)
|
|
}
|
|
|
|
if len(l) == 0 {
|
|
break
|
|
} else if len(h) > 0 && (l[0] == ' ' || l[0] == '\t') {
|
|
// This is a continuation line
|
|
h[len(h)-1] += l + crlf
|
|
} else {
|
|
h = append(h, l+crlf)
|
|
}
|
|
}
|
|
|
|
return h, nil
|
|
}
|
|
|
|
func writeHeader(w io.Writer, h header) error {
|
|
for _, kv := range h {
|
|
if _, err := w.Write([]byte(kv)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err := w.Write([]byte(crlf))
|
|
return err
|
|
}
|
|
|
|
func foldHeaderField(kv string) string {
|
|
buf := bytes.NewBufferString(kv)
|
|
|
|
line := make([]byte, 75) // 78 - len("\r\n\s")
|
|
first := true
|
|
var fold strings.Builder
|
|
for len, err := buf.Read(line); err != io.EOF; len, err = buf.Read(line) {
|
|
if first {
|
|
first = false
|
|
} else {
|
|
fold.WriteString("\r\n ")
|
|
}
|
|
fold.Write(line[:len])
|
|
}
|
|
|
|
return fold.String() + crlf
|
|
}
|
|
|
|
func parseHeaderField(s string) (string, string) {
|
|
key, value, _ := strings.Cut(s, ":")
|
|
return strings.TrimSpace(key), strings.TrimSpace(value)
|
|
}
|
|
|
|
func parseHeaderParams(s string) (map[string]string, error) {
|
|
pairs := strings.Split(s, ";")
|
|
params := make(map[string]string)
|
|
for _, s := range pairs {
|
|
key, value, ok := strings.Cut(s, "=")
|
|
if !ok {
|
|
if strings.TrimSpace(s) == "" {
|
|
continue
|
|
}
|
|
return params, errors.New("dkim: malformed header params")
|
|
}
|
|
|
|
params[strings.TrimSpace(key)] = strings.TrimSpace(value)
|
|
}
|
|
return params, nil
|
|
}
|
|
|
|
func formatHeaderParams(headerFieldName string, params map[string]string) string {
|
|
keys, bvalue, bfound := sortParams(params)
|
|
|
|
s := headerFieldName + ":"
|
|
var line string
|
|
|
|
for _, k := range keys {
|
|
v := params[k]
|
|
nextLength := 3 + len(line) + len(v) + len(k)
|
|
if nextLength > 75 {
|
|
s += line + crlf
|
|
line = ""
|
|
}
|
|
line = fmt.Sprintf("%v %v=%v;", line, k, v)
|
|
}
|
|
|
|
if line != "" {
|
|
s += line
|
|
}
|
|
|
|
if bfound {
|
|
bfiled := foldHeaderField(" b=" + bvalue)
|
|
s += crlf + bfiled
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func sortParams(params map[string]string) ([]string, string, bool) {
|
|
keys := make([]string, 0, len(params))
|
|
bfound := false
|
|
var bvalue string
|
|
for k := range params {
|
|
if k == "b" {
|
|
bvalue = params["b"]
|
|
bfound = true
|
|
} else {
|
|
keys = append(keys, k)
|
|
}
|
|
}
|
|
sort.Strings(keys)
|
|
return keys, bvalue, bfound
|
|
}
|
|
|
|
type headerPicker struct {
|
|
h header
|
|
picked map[string]int
|
|
}
|
|
|
|
func newHeaderPicker(h header) *headerPicker {
|
|
return &headerPicker{
|
|
h: h,
|
|
picked: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
func (p *headerPicker) Pick(key string) string {
|
|
key = strings.ToLower(key)
|
|
|
|
at := p.picked[key]
|
|
for i := len(p.h) - 1; i >= 0; i-- {
|
|
kv := p.h[i]
|
|
k, _ := parseHeaderField(kv)
|
|
|
|
if !strings.EqualFold(k, key) {
|
|
continue
|
|
}
|
|
|
|
if at == 0 {
|
|
p.picked[key]++
|
|
return kv
|
|
}
|
|
at--
|
|
}
|
|
|
|
return ""
|
|
}
|