Files
crockery/internal/imap/uidlist/header.go

132 lines
2.4 KiB
Go

package uidlist
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
const (
HeaderTemplate = "%d V%d N%d G%s"
)
type Header struct {
Version uint64
UIDValidity uint64
NextUID uint64
GUID string
Extra map[string][]string
}
func setUint64(s string, out *uint64) error {
num, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return err
}
*out = uint64(num)
return nil
}
// GenerateHeader produces a valid verison 3 dovecot-uidlist header line, using
// the time for UIDValidity and a random string for the GUID.
func GenerateHeader() (Header, error) {
// A GUID is a random 128-bit number
guidBytes := make([]byte, 16)
_, err := rand.Read(guidBytes)
if err != nil {
return Header{}, err
}
// ...hex-encoded
guidString := hex.EncodeToString(guidBytes)
return Header{
Version: uint64(3),
UIDValidity: uint64(time.Now().Unix()),
NextUID: uint64(1),
GUID: guidString,
Extra: make(map[string][]string),
}, nil
}
// ParseHeader breaks a dovecot-uidlist header line into its constituent parts
func ParseHeader(line string) (Header, error) {
var seenUIDValidity bool
var seenNextUID bool
var seenGUID bool
out := Header{}
parts := strings.Split(strings.TrimSpace(line), " ")
if len(parts) < 4 {
return out, errors.New("Short header")
}
if err := setUint64(parts[0], &out.Version); err != nil {
return out, err
}
out.Extra = make(map[string][]string)
for _, part := range parts[1:] {
part = strings.TrimSpace(part)
if len(part) == 0 {
continue
}
key := part[0:1]
value := part[1:]
switch key {
case "V":
if seenUIDValidity {
return out, errors.New("UIDVALIDITY specified twice")
}
seenUIDValidity = true
if err := setUint64(value, &out.UIDValidity); err != nil {
return out, err
}
case "N":
if seenNextUID {
return out, errors.New("NEXTUID specified twice")
}
seenNextUID = true
if err := setUint64(value, &out.NextUID); err != nil {
return out, err
}
case "G":
if seenGUID {
return out, errors.New("GUID specified twice")
}
seenGUID = true
out.GUID = value
default:
out.Extra[key] = append(out.Extra[key], value)
}
}
return out, nil
}
func (h Header) String() string {
var sb strings.Builder
fmt.Fprintf(
&sb,
HeaderTemplate,
h.Version, h.UIDValidity, h.NextUID, h.GUID,
)
for k, v := range h.Extra {
fmt.Fprintf(&sb, " %s%s", k, v)
}
return sb.String()
}