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() }