header.go raw

   1  package dkim
   2  
   3  import (
   4  	"bufio"
   5  	"bytes"
   6  	"errors"
   7  	"fmt"
   8  	"io"
   9  	"net/textproto"
  10  	"sort"
  11  	"strings"
  12  )
  13  
  14  const crlf = "\r\n"
  15  
  16  type header []string
  17  
  18  func readHeader(r *bufio.Reader) (header, error) {
  19  	tr := textproto.NewReader(r)
  20  
  21  	var h header
  22  	for {
  23  		l, err := tr.ReadLine()
  24  		if err != nil {
  25  			return h, fmt.Errorf("failed to read header: %v", err)
  26  		}
  27  
  28  		if len(l) == 0 {
  29  			break
  30  		} else if len(h) > 0 && (l[0] == ' ' || l[0] == '\t') {
  31  			// This is a continuation line
  32  			h[len(h)-1] += l + crlf
  33  		} else {
  34  			h = append(h, l+crlf)
  35  		}
  36  	}
  37  
  38  	return h, nil
  39  }
  40  
  41  func writeHeader(w io.Writer, h header) error {
  42  	for _, kv := range h {
  43  		if _, err := w.Write([]byte(kv)); err != nil {
  44  			return err
  45  		}
  46  	}
  47  	_, err := w.Write([]byte(crlf))
  48  	return err
  49  }
  50  
  51  func foldHeaderField(kv string) string {
  52  	buf := bytes.NewBufferString(kv)
  53  
  54  	line := make([]byte, 75) // 78 - len("\r\n\s")
  55  	first := true
  56  	var fold strings.Builder
  57  	for len, err := buf.Read(line); err != io.EOF; len, err = buf.Read(line) {
  58  		if first {
  59  			first = false
  60  		} else {
  61  			fold.WriteString("\r\n ")
  62  		}
  63  		fold.Write(line[:len])
  64  	}
  65  
  66  	return fold.String() + crlf
  67  }
  68  
  69  func parseHeaderField(s string) (string, string) {
  70  	key, value, _ := strings.Cut(s, ":")
  71  	return strings.TrimSpace(key), strings.TrimSpace(value)
  72  }
  73  
  74  func parseHeaderParams(s string) (map[string]string, error) {
  75  	pairs := strings.Split(s, ";")
  76  	params := make(map[string]string)
  77  	for _, s := range pairs {
  78  		key, value, ok := strings.Cut(s, "=")
  79  		if !ok {
  80  			if strings.TrimSpace(s) == "" {
  81  				continue
  82  			}
  83  			return params, errors.New("dkim: malformed header params")
  84  		}
  85  
  86  		trimmedKey := strings.TrimSpace(key)
  87  		_, present := params[trimmedKey]
  88  		if present {
  89  			return params, errors.New("dkim: duplicate tag name")
  90  		}
  91  		params[trimmedKey] = strings.TrimSpace(value)
  92  	}
  93  	return params, nil
  94  }
  95  
  96  func formatHeaderParams(headerFieldName string, params map[string]string) string {
  97  	keys, bvalue, bfound := sortParams(params)
  98  
  99  	s := headerFieldName + ":"
 100  	var line string
 101  
 102  	for _, k := range keys {
 103  		v := params[k]
 104  		nextLength := 3 + len(line) + len(v) + len(k)
 105  		if nextLength > 75 {
 106  			s += line + crlf
 107  			line = ""
 108  		}
 109  		line = fmt.Sprintf("%v %v=%v;", line, k, v)
 110  	}
 111  
 112  	if line != "" {
 113  		s += line
 114  	}
 115  
 116  	if bfound {
 117  		bfiled := foldHeaderField(" b=" + bvalue)
 118  		s += crlf + bfiled
 119  	}
 120  
 121  	return s
 122  }
 123  
 124  func sortParams(params map[string]string) ([]string, string, bool) {
 125  	keys := make([]string, 0, len(params))
 126  	bfound := false
 127  	var bvalue string
 128  	for k := range params {
 129  		if k == "b" {
 130  			bvalue = params["b"]
 131  			bfound = true
 132  		} else {
 133  			keys = append(keys, k)
 134  		}
 135  	}
 136  	sort.Strings(keys)
 137  	return keys, bvalue, bfound
 138  }
 139  
 140  type headerPicker struct {
 141  	h      header
 142  	picked map[string]int
 143  }
 144  
 145  func newHeaderPicker(h header) *headerPicker {
 146  	return &headerPicker{
 147  		h:      h,
 148  		picked: make(map[string]int),
 149  	}
 150  }
 151  
 152  func (p *headerPicker) Pick(key string) string {
 153  	key = strings.ToLower(key)
 154  
 155  	at := p.picked[key]
 156  	for i := len(p.h) - 1; i >= 0; i-- {
 157  		kv := p.h[i]
 158  		k, _ := parseHeaderField(kv)
 159  
 160  		if !strings.EqualFold(k, key) {
 161  			continue
 162  		}
 163  
 164  		if at == 0 {
 165  			p.picked[key]++
 166  			return kv
 167  		}
 168  		at--
 169  	}
 170  
 171  	return ""
 172  }
 173