132 lines
2.4 KiB
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()
|
|
}
|