mirror of
https://github.com/vbatts/freezing-octo-hipster.git
synced 2025-07-03 07:48:29 +00:00
go*: one go module for the repo, no more nested
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
parent
a788e9ac95
commit
4ab3be9bc6
632 changed files with 153930 additions and 133148 deletions
520
vendor/github.com/luksen/maildir/maildir.go
generated
vendored
Normal file
520
vendor/github.com/luksen/maildir/maildir.go
generated
vendored
Normal file
|
@ -0,0 +1,520 @@
|
|||
// The maildir package provides an interface to mailboxes in the Maildir format.
|
||||
package maildir
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/mail"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The Separator separates a messages unique key from its flags in the filename.
|
||||
// This should only be changed on operating systems where the colon isn't
|
||||
// allowed in filenames.
|
||||
var Separator rune = ':'
|
||||
|
||||
var id int64 = 10000
|
||||
|
||||
// CreateMode holds the permissions used when creating a directory.
|
||||
const CreateMode = 0700
|
||||
|
||||
// A KeyError occurs when a key matches more or less than one message.
|
||||
type KeyError struct {
|
||||
Key string // the (invalid) key
|
||||
N int // number of matches (!= 1)
|
||||
}
|
||||
|
||||
func (e *KeyError) Error() string {
|
||||
return "maildir: key " + e.Key + " matches " + strconv.Itoa(e.N) + " files."
|
||||
}
|
||||
|
||||
// A FlagError occurs when a non-standard info section is encountered.
|
||||
type FlagError struct {
|
||||
Info string // the encountered info section
|
||||
Experimental bool // info section starts with 1
|
||||
}
|
||||
|
||||
func (e *FlagError) Error() string {
|
||||
if e.Experimental {
|
||||
return "maildir: experimental info section encountered: " + e.Info[2:]
|
||||
}
|
||||
return "maildir: bad info section encountered: " + e.Info
|
||||
}
|
||||
|
||||
// A Dir represents a single directory in a Maildir mailbox.
|
||||
type Dir string
|
||||
|
||||
// Unseen moves messages from new to cur and returns their keys.
|
||||
// This means the messages are now known to the application. To find out whether
|
||||
// a user has seen a message, use Flags().
|
||||
func (d Dir) Unseen() ([]string, error) {
|
||||
f, err := os.Open(filepath.Join(string(d), "new"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var keys []string
|
||||
for _, n := range names {
|
||||
if n[0] != '.' {
|
||||
split := strings.FieldsFunc(n, func(r rune) bool {
|
||||
return r == Separator
|
||||
})
|
||||
key := split[0]
|
||||
info := "2,"
|
||||
// Messages in new shouldn't have an info section but
|
||||
// we act as if, in case some other program didn't
|
||||
// follow the spec.
|
||||
if len(split) > 1 {
|
||||
info = split[1]
|
||||
}
|
||||
keys = append(keys, key)
|
||||
err = os.Rename(filepath.Join(string(d), "new", n),
|
||||
filepath.Join(string(d), "cur", key+string(Separator)+info))
|
||||
}
|
||||
}
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// UnseenCount returns the number of messages in new without looking at them.
|
||||
func (d Dir) UnseenCount() (int, error) {
|
||||
f, err := os.Open(filepath.Join(string(d), "new"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c := 0
|
||||
for _, n := range names {
|
||||
if n[0] != '.' {
|
||||
c += 1
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Keys returns a slice of valid keys to access messages by. This only returns
|
||||
// keys for messages in cur. Use Unseen to access messages in new. All keys,
|
||||
// whether returned here or by Unseen, point to messages in cur.
|
||||
func (d Dir) Keys() ([]string, error) {
|
||||
f, err := os.Open(filepath.Join(string(d), "cur"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var keys []string
|
||||
for _, n := range names {
|
||||
if n[0] != '.' {
|
||||
split := strings.FieldsFunc(n, func(r rune) bool {
|
||||
return r == Separator
|
||||
})
|
||||
keys = append(keys, split[0])
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
finfo, err := os.Stat(filename)
|
||||
return err == nil && finfo.Mode().IsRegular()
|
||||
}
|
||||
|
||||
var suffices []string = []string{
|
||||
"",
|
||||
"S",
|
||||
"D",
|
||||
"F",
|
||||
"P",
|
||||
"R",
|
||||
"T",
|
||||
"DF",
|
||||
"DP",
|
||||
"DR",
|
||||
"DS",
|
||||
"DT",
|
||||
"FP",
|
||||
"FR",
|
||||
"FS",
|
||||
"FT",
|
||||
"PR",
|
||||
"PS",
|
||||
"PT",
|
||||
"RS",
|
||||
"RT",
|
||||
"ST",
|
||||
"DFP",
|
||||
"DFR",
|
||||
"DFS",
|
||||
"DFT",
|
||||
"DPR",
|
||||
"DPS",
|
||||
"DPT",
|
||||
"DRS",
|
||||
"DRT",
|
||||
"DST",
|
||||
"FPR",
|
||||
"FPS",
|
||||
"FPT",
|
||||
"FRS",
|
||||
"FRT",
|
||||
"FST",
|
||||
"PRS",
|
||||
"PRT",
|
||||
"PST",
|
||||
"RST",
|
||||
"DFPR",
|
||||
"DFPS",
|
||||
"DFPT",
|
||||
"DFRS",
|
||||
"DFRT",
|
||||
"DFST",
|
||||
"DPRS",
|
||||
"DPRT",
|
||||
"DPST",
|
||||
"DRST",
|
||||
"FPRS",
|
||||
"FPRT",
|
||||
"FPST",
|
||||
"FRST",
|
||||
"PRST",
|
||||
"DFPRS",
|
||||
"DFPRT",
|
||||
"DFPST",
|
||||
"DFRST",
|
||||
"DPRST",
|
||||
"FPRST",
|
||||
"DFPRST"}
|
||||
|
||||
// quick check for existance of legal (per DJB) file names for a key
|
||||
func (d Dir) quickFilename(key string) string {
|
||||
// "cur" files must have :info suffix
|
||||
filePrefix := filepath.Join(string(d), "cur", key+string(Separator)+"2,")
|
||||
for _, suffix := range suffices {
|
||||
if filename := filePrefix + suffix; fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Filename returns the path to the file corresponding to the key.
|
||||
func (d Dir) Filename(key string) (string, error) {
|
||||
if matchedFile := d.quickFilename(key); matchedFile != "" {
|
||||
return matchedFile, nil
|
||||
}
|
||||
dirPath := filepath.Join(string(d), "cur")
|
||||
f, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(name, key) {
|
||||
return filepath.Join(dirPath, name), nil
|
||||
}
|
||||
}
|
||||
return "", &KeyError{key, 0}
|
||||
}
|
||||
|
||||
// Header returns the corresponding mail header to a key.
|
||||
func (d Dir) Header(key string) (header mail.Header, err error) {
|
||||
filename, err := d.Filename(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
tp := textproto.NewReader(bufio.NewReader(file))
|
||||
hdr, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = mail.Header(hdr)
|
||||
return
|
||||
}
|
||||
|
||||
// Message returns a Message by key.
|
||||
func (d Dir) Message(key string) (*mail.Message, error) {
|
||||
filename, err := d.Filename(key)
|
||||
if err != nil {
|
||||
return &mail.Message{}, err
|
||||
}
|
||||
r, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return &mail.Message{}, err
|
||||
}
|
||||
defer r.Close()
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = io.Copy(buf, r)
|
||||
if err != nil {
|
||||
return &mail.Message{}, err
|
||||
}
|
||||
msg, err := mail.ReadMessage(buf)
|
||||
if err != nil {
|
||||
return msg, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
type runeSlice []rune
|
||||
|
||||
func (s runeSlice) Len() int { return len(s) }
|
||||
func (s runeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s runeSlice) Less(i, j int) bool { return s[i] < s[j] }
|
||||
|
||||
// Flags returns the flags for a message sorted in ascending order.
|
||||
// See the documentation of SetFlags for details.
|
||||
func (d Dir) Flags(key string) (string, error) {
|
||||
filename, err := d.Filename(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
split := strings.FieldsFunc(filename, func(r rune) bool {
|
||||
return r == Separator
|
||||
})
|
||||
switch {
|
||||
case len(split[1]) < 2,
|
||||
split[1][1] != ',':
|
||||
return "", &FlagError{split[1], false}
|
||||
case split[1][0] == '1':
|
||||
return "", &FlagError{split[1], true}
|
||||
case split[1][0] != '2':
|
||||
return "", &FlagError{split[1], false}
|
||||
}
|
||||
rs := runeSlice(split[1][2:])
|
||||
sort.Sort(rs)
|
||||
return string(rs), nil
|
||||
}
|
||||
|
||||
// SetFlags appends an info section to the filename according to the given flags.
|
||||
// This function removes duplicates and sorts the flags, but doesn't check
|
||||
// whether they conform with the Maildir specification.
|
||||
//
|
||||
// The following flags are listed in the specification
|
||||
// (http://cr.yp.to/proto/maildir.html):
|
||||
//
|
||||
// Flag "P" (passed): the user has resent/forwarded/bounced this message to someone else.
|
||||
// Flag "R" (replied): the user has replied to this message.
|
||||
// Flag "S" (seen): the user has viewed this message, though perhaps he didn't read all the way through it.
|
||||
// Flag "T" (trashed): the user has moved this message to the trash; the trash will be emptied by a later user action.
|
||||
// Flag "D" (draft): the user considers this message a draft; toggled at user discretion.
|
||||
// Flag "F" (flagged): user-defined flag; toggled at user discretion.
|
||||
//
|
||||
// Using only these standard flags will improve message retrieval speed.
|
||||
func (d Dir) SetFlags(key string, flags string) error {
|
||||
info := "2,"
|
||||
rs := runeSlice(flags)
|
||||
sort.Sort(rs)
|
||||
for _, r := range rs {
|
||||
if []rune(info)[len(info)-1] != r {
|
||||
info += string(r)
|
||||
}
|
||||
}
|
||||
return d.SetInfo(key, info)
|
||||
}
|
||||
|
||||
// Set the info part of the filename.
|
||||
// Only use this if you plan on using a non-standard info part.
|
||||
func (d Dir) SetInfo(key, info string) error {
|
||||
filename, err := d.Filename(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(filename, filepath.Join(string(d), "cur", key+
|
||||
string(Separator)+info))
|
||||
return err
|
||||
}
|
||||
|
||||
// Key exposes the internal unique key generation. This function is deprecated.
|
||||
func Key() (string, error) {
|
||||
fmt.Println("maildir: Key() is deprecated without replacement. See https://github.com/luksen/maildir/issues/5 for details.")
|
||||
return generateKey()
|
||||
}
|
||||
|
||||
// generateKey generates a new unique key as described in the Maildir
|
||||
// specification. For the third part of the key (delivery identifier) it uses
|
||||
// an internal counter, the process id and a cryptographical random number to
|
||||
// ensure uniqueness among messages delivered in the same second.
|
||||
func generateKey() (string, error) {
|
||||
var key string
|
||||
key += strconv.FormatInt(time.Now().Unix(), 10)
|
||||
key += "."
|
||||
host, err := os.Hostname()
|
||||
if err != err {
|
||||
return "", err
|
||||
}
|
||||
host = strings.Replace(host, "/", "\057", -1)
|
||||
host = strings.Replace(host, string(Separator), "\072", -1)
|
||||
key += host
|
||||
key += "."
|
||||
key += strconv.FormatInt(int64(os.Getpid()), 10)
|
||||
key += strconv.FormatInt(id, 10)
|
||||
atomic.AddInt64(&id, 1)
|
||||
bs := make([]byte, 10)
|
||||
_, err = io.ReadFull(rand.Reader, bs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key += hex.EncodeToString(bs)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Create creates the directory structure for a Maildir.
|
||||
// If the main directory already exists, it tries to create the subdirectories
|
||||
// in there. If an error occurs while creating one of the subdirectories, this
|
||||
// function may leave a partially created directory structure.
|
||||
func (d Dir) Create() error {
|
||||
err := os.Mkdir(string(d), os.ModeDir|CreateMode)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
err = os.Mkdir(filepath.Join(string(d), "tmp"), os.ModeDir|CreateMode)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
err = os.Mkdir(filepath.Join(string(d), "new"), os.ModeDir|CreateMode)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
err = os.Mkdir(filepath.Join(string(d), "cur"), os.ModeDir|CreateMode)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delivery represents an ongoing message delivery to the mailbox.
|
||||
// It implements the WriteCloser interface. On closing the underlying file is
|
||||
// moved/relinked to new.
|
||||
type Delivery struct {
|
||||
file *os.File
|
||||
d Dir
|
||||
key string
|
||||
}
|
||||
|
||||
// NewDelivery creates a new Delivery.
|
||||
func (d Dir) NewDelivery() (*Delivery, error) {
|
||||
key, err := generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
del := &Delivery{}
|
||||
time.AfterFunc(24*time.Hour, func() { del.Abort() })
|
||||
file, err := os.Create(filepath.Join(string(d), "tmp", key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
del.file = file
|
||||
del.d = d
|
||||
del.key = key
|
||||
return del, nil
|
||||
}
|
||||
|
||||
func (d *Delivery) Write(p []byte) (int, error) {
|
||||
return d.file.Write(p)
|
||||
}
|
||||
|
||||
// Close closes the underlying file and moves it to new.
|
||||
func (d *Delivery) Close() error {
|
||||
err := d.file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Link(filepath.Join(string(d.d), "tmp", d.key),
|
||||
filepath.Join(string(d.d), "new", d.key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Remove(filepath.Join(string(d.d), "tmp", d.key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Abort closes the underlying file and removes it completely.
|
||||
func (d *Delivery) Abort() error {
|
||||
err := d.file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Remove(filepath.Join(string(d.d), "tmp", d.key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move moves a message from this Maildir to another.
|
||||
func (d Dir) Move(target Dir, key string) error {
|
||||
path, err := d.Filename(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(path, filepath.Join(string(target), "cur", filepath.Base(path)))
|
||||
}
|
||||
|
||||
// Purge removes the actual file behind this message.
|
||||
func (d Dir) Purge(key string) error {
|
||||
f, err := d.Filename(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(f)
|
||||
}
|
||||
|
||||
// Clean removes old files from tmp and should be run periodically.
|
||||
// This does not use access time but modification time for portability reasons.
|
||||
func (d Dir) Clean() error {
|
||||
f, err := os.Open(filepath.Join(string(d), "tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
for _, n := range names {
|
||||
fi, err := os.Stat(filepath.Join(string(d), "tmp", n))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if now.Sub(fi.ModTime()).Hours() > 36 {
|
||||
err = os.Remove(filepath.Join(string(d), "tmp", n))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue