update storage

This commit is contained in:
Ian Kent 2017-04-16 23:27:18 +01:00
parent d4927bc7e6
commit 50802a0753
No known key found for this signature in database
GPG key ID: CE5AC689AF520A48
6 changed files with 4 additions and 515 deletions

View file

@ -1,184 +0,0 @@
package storage
import (
"errors"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/mailhog/data"
)
// Maildir is a maildir storage backend
type Maildir struct {
Path string
}
// CreateMaildir creates a new maildir storage backend
func CreateMaildir(path string) *Maildir {
if len(path) == 0 {
dir, err := ioutil.TempDir("", "mailhog")
if err != nil {
panic(err)
}
path = dir
}
if _, err := os.Stat(path); err != nil {
err := os.MkdirAll(path, 0770)
if err != nil {
panic(err)
}
}
log.Println("Maildir path is", path)
return &Maildir{
Path: path,
}
}
// Store stores a message and returns its storage ID
func (maildir *Maildir) Store(m *data.Message) (string, error) {
b, err := ioutil.ReadAll(m.Raw.Bytes())
if err != nil {
return "", err
}
err = ioutil.WriteFile(filepath.Join(maildir.Path, string(m.ID)), b, 0660)
return string(m.ID), err
}
// Count returns the number of stored messages
func (maildir *Maildir) Count() int {
// FIXME may be wrong, ../. ?
// and handle error?
dir, err := os.Open(maildir.Path)
if err != nil {
panic(err)
}
defer dir.Close()
n, _ := dir.Readdirnames(0)
return len(n)
}
// Search finds messages matching the query
func (maildir *Maildir) Search(kind, query string, start, limit int) (*data.Messages, int, error) {
query = strings.ToLower(query)
var filteredMessages = make([]data.Message, 0)
var matched int
err := filepath.Walk(maildir.Path, func(path string, info os.FileInfo, err error) error {
if limit > 0 && len(filteredMessages) >= limit {
return errors.New("reached limit")
}
if info.IsDir() {
return nil
}
msg, err := maildir.Load(info.Name())
if err != nil {
log.Println(err)
return nil
}
switch kind {
case "to":
for _, t := range msg.To {
if strings.Contains(strings.ToLower(t.Mailbox+"@"+t.Domain), query) {
if start > matched {
matched++
break
}
filteredMessages = append(filteredMessages, *msg)
break
}
}
case "from":
if strings.Contains(strings.ToLower(msg.From.Mailbox+"@"+msg.From.Domain), query) {
if start > matched {
matched++
break
}
filteredMessages = append(filteredMessages, *msg)
}
case "containing":
if strings.Contains(strings.ToLower(msg.Raw.Data), query) {
if start > matched {
matched++
break
}
filteredMessages = append(filteredMessages, *msg)
}
}
return nil
})
if err != nil {
log.Println(err)
}
msgs := data.Messages(filteredMessages)
return &msgs, len(filteredMessages), nil
}
// List lists stored messages by index
func (maildir *Maildir) List(start, limit int) (*data.Messages, error) {
log.Println("Listing messages in", maildir.Path)
messages := make([]data.Message, 0)
dir, err := os.Open(maildir.Path)
if err != nil {
return nil, err
}
defer dir.Close()
n, err := dir.Readdir(0)
if err != nil {
return nil, err
}
for _, fileinfo := range n {
b, err := ioutil.ReadFile(filepath.Join(maildir.Path, fileinfo.Name()))
if err != nil {
return nil, err
}
msg := data.FromBytes(b)
// FIXME domain
m := *msg.Parse("mailhog.example")
m.ID = data.MessageID(fileinfo.Name())
m.Created = fileinfo.ModTime()
messages = append(messages, m)
}
log.Printf("Found %d messages", len(messages))
msgs := data.Messages(messages)
return &msgs, nil
}
// DeleteOne deletes an individual message by storage ID
func (maildir *Maildir) DeleteOne(id string) error {
return os.Remove(filepath.Join(maildir.Path, id))
}
// DeleteAll deletes all in memory messages
func (maildir *Maildir) DeleteAll() error {
err := os.RemoveAll(maildir.Path)
if err != nil {
return err
}
return os.Mkdir(maildir.Path, 0770)
}
// Load returns an individual message by storage ID
func (maildir *Maildir) Load(id string) (*data.Message, error) {
b, err := ioutil.ReadFile(filepath.Join(maildir.Path, id))
if err != nil {
return nil, err
}
// FIXME domain
m := data.FromBytes(b).Parse("mailhog.example")
m.ID = data.MessageID(id)
return m, nil
}

View file

@ -1,191 +0,0 @@
package storage
import (
"strings"
"sync"
"github.com/mailhog/data"
)
// InMemory is an in memory storage backend
type InMemory struct {
MessageIDIndex map[string]int
Messages []*data.Message
mu sync.Mutex
}
// CreateInMemory creates a new in memory storage backend
func CreateInMemory() *InMemory {
return &InMemory{
MessageIDIndex: make(map[string]int),
Messages: make([]*data.Message, 0),
}
}
// Store stores a message and returns its storage ID
func (memory *InMemory) Store(m *data.Message) (string, error) {
memory.mu.Lock()
defer memory.mu.Unlock()
memory.Messages = append(memory.Messages, m)
memory.MessageIDIndex[string(m.ID)] = len(memory.Messages) - 1
return string(m.ID), nil
}
// Count returns the number of stored messages
func (memory *InMemory) Count() int {
return len(memory.Messages)
}
// Search finds messages matching the query
func (memory *InMemory) Search(kind, query string, start, limit int) (*data.Messages, int, error) {
// FIXME needs optimising, or replacing with a proper db!
query = strings.ToLower(query)
var filteredMessages = make([]*data.Message, 0)
for _, m := range memory.Messages {
doAppend := false
switch kind {
case "to":
for _, to := range m.To {
if strings.Contains(strings.ToLower(to.Mailbox+"@"+to.Domain), query) {
doAppend = true
break
}
}
if !doAppend {
if hdr, ok := m.Content.Headers["To"]; ok {
for _, to := range hdr {
if strings.Contains(strings.ToLower(to), query) {
doAppend = true
break
}
}
}
}
case "from":
if strings.Contains(strings.ToLower(m.From.Mailbox+"@"+m.From.Domain), query) {
doAppend = true
}
if !doAppend {
if hdr, ok := m.Content.Headers["From"]; ok {
for _, from := range hdr {
if strings.Contains(strings.ToLower(from), query) {
doAppend = true
break
}
}
}
}
case "containing":
if strings.Contains(strings.ToLower(m.Content.Body), query) {
doAppend = true
}
if !doAppend {
for _, hdr := range m.Content.Headers {
for _, v := range hdr {
if strings.Contains(strings.ToLower(v), query) {
doAppend = true
}
}
}
}
}
if doAppend {
filteredMessages = append(filteredMessages, m)
}
}
var messages = make([]data.Message, 0)
if len(filteredMessages) == 0 || start > len(filteredMessages) {
msgs := data.Messages(messages)
return &msgs, 0, nil
}
if start+limit > len(filteredMessages) {
limit = len(filteredMessages) - start
}
start = len(filteredMessages) - start - 1
end := start - limit
if start < 0 {
start = 0
}
if end < -1 {
end = -1
}
for i := start; i > end; i-- {
//for _, m := range memory.MessageIndex[start:end] {
messages = append(messages, *filteredMessages[i])
}
msgs := data.Messages(messages)
return &msgs, len(filteredMessages), nil
}
// List lists stored messages by index
func (memory *InMemory) List(start int, limit int) (*data.Messages, error) {
var messages = make([]data.Message, 0)
if len(memory.Messages) == 0 || start > len(memory.Messages) {
msgs := data.Messages(messages)
return &msgs, nil
}
if start+limit > len(memory.Messages) {
limit = len(memory.Messages) - start
}
start = len(memory.Messages) - start - 1
end := start - limit
if start < 0 {
start = 0
}
if end < -1 {
end = -1
}
for i := start; i > end; i-- {
//for _, m := range memory.MessageIndex[start:end] {
messages = append(messages, *memory.Messages[i])
}
msgs := data.Messages(messages)
return &msgs, nil
}
// DeleteOne deletes an individual message by storage ID
func (memory *InMemory) DeleteOne(id string) error {
memory.mu.Lock()
defer memory.mu.Unlock()
index := memory.MessageIDIndex[id]
delete(memory.MessageIDIndex, id)
for k, v := range memory.MessageIDIndex {
if v > index {
memory.MessageIDIndex[k] = v - 1
}
}
memory.Messages = append(memory.Messages[:index], memory.Messages[index+1:]...)
return nil
}
// DeleteAll deletes all in memory messages
func (memory *InMemory) DeleteAll() error {
memory.mu.Lock()
defer memory.mu.Unlock()
memory.Messages = make([]*data.Message, 0)
memory.MessageIDIndex = make(map[string]int)
return nil
}
// Load returns an individual message by storage ID
func (memory *InMemory) Load(id string) (*data.Message, error) {
if idx, ok := memory.MessageIDIndex[id]; ok {
return memory.Messages[idx], nil
}
return nil, nil
}

View file

@ -1,122 +0,0 @@
package storage
import (
"log"
"github.com/mailhog/data"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// MongoDB represents MongoDB backed storage backend
type MongoDB struct {
Session *mgo.Session
Collection *mgo.Collection
}
// CreateMongoDB creates a MongoDB backed storage backend
func CreateMongoDB(uri, db, coll string) *MongoDB {
log.Printf("Connecting to MongoDB: %s\n", uri)
session, err := mgo.Dial(uri)
if err != nil {
log.Printf("Error connecting to MongoDB: %s", err)
return nil
}
err = session.DB(db).C(coll).EnsureIndexKey("created")
if err != nil {
log.Printf("Failed creating index: %s", err)
return nil
}
return &MongoDB{
Session: session,
Collection: session.DB(db).C(coll),
}
}
// Store stores a message in MongoDB and returns its storage ID
func (mongo *MongoDB) Store(m *data.Message) (string, error) {
err := mongo.Collection.Insert(m)
if err != nil {
log.Printf("Error inserting message: %s", err)
return "", err
}
return string(m.ID), nil
}
// Count returns the number of stored messages
func (mongo *MongoDB) Count() int {
c, _ := mongo.Collection.Count()
return c
}
// Search finds messages matching the query
func (mongo *MongoDB) Search(kind, query string, start, limit int) (*data.Messages, int, error) {
messages := &data.Messages{}
var count = 0
var field = "raw.data"
switch kind {
case "to":
field = "raw.to"
case "from":
field = "raw.from"
}
err := mongo.Collection.Find(bson.M{field: bson.RegEx{Pattern: query, Options: "i"}}).Skip(start).Limit(limit).Sort("-created").Select(bson.M{
"id": 1,
"_id": 1,
"from": 1,
"to": 1,
"content.headers": 1,
"content.size": 1,
"created": 1,
"raw": 1,
}).All(messages)
if err != nil {
log.Printf("Error loading messages: %s", err)
return nil, 0, err
}
count, _ = mongo.Collection.Find(bson.M{field: bson.RegEx{Pattern: query, Options: "i"}}).Count()
return messages, count, nil
}
// List returns a list of messages by index
func (mongo *MongoDB) List(start int, limit int) (*data.Messages, error) {
messages := &data.Messages{}
err := mongo.Collection.Find(bson.M{}).Skip(start).Limit(limit).Sort("-created").Select(bson.M{
"id": 1,
"_id": 1,
"from": 1,
"to": 1,
"content.headers": 1,
"content.size": 1,
"created": 1,
"raw": 1,
}).All(messages)
if err != nil {
log.Printf("Error loading messages: %s", err)
return nil, err
}
return messages, nil
}
// DeleteOne deletes an individual message by storage ID
func (mongo *MongoDB) DeleteOne(id string) error {
_, err := mongo.Collection.RemoveAll(bson.M{"id": id})
return err
}
// DeleteAll deletes all messages stored in MongoDB
func (mongo *MongoDB) DeleteAll() error {
_, err := mongo.Collection.RemoveAll(bson.M{})
return err
}
// Load loads an individual message by storage ID
func (mongo *MongoDB) Load(id string) (*data.Message, error) {
result := &data.Message{}
err := mongo.Collection.Find(bson.M{"id": id}).One(&result)
if err != nil {
log.Printf("Error loading message: %s", err)
return nil, err
}
return result, nil
}

View file

@ -1,14 +0,0 @@
package storage
import "github.com/mailhog/data"
// Storage represents a storage backend
type Storage interface {
Store(m *data.Message) (string, error)
List(start, limit int) (*data.Messages, error)
Search(kind, query string, start, limit int) (*data.Messages, int, error)
Count() int
DeleteOne(id string) error
DeleteAll() error
Load(id string) (*data.Message, error)
}

View file

@ -133,7 +133,7 @@ func (c *Client) Step(in []byte) bool {
func (c *Client) step1(in []byte) error {
if len(c.clientNonce) == 0 {
const nonceLen = 6
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
buf := make([]byte, nonceLen + b64.EncodedLen(nonceLen))
if _, err := rand.Read(buf[:nonceLen]); err != nil {
return fmt.Errorf("cannot read random SCRAM-SHA-1 nonce from operating system: %v", err)
}

6
vendor/vendor.json vendored
View file

@ -147,10 +147,10 @@
"revisionTime": "2016-11-19T23:01:07Z"
},
{
"checksumSHA1": "Kd53/4cZ1KNfatXBgCg4VWzowis=",
"checksumSHA1": "bnVk1LzeVwAk47vHdu1jU6KZQZU=",
"path": "github.com/mailhog/storage",
"revision": "970ad63c574405c1d31aade44081611047343c77",
"revisionTime": "2017-04-16T18:53:22Z"
"revision": "295f257171af29ea8ee6411a9a0ee3cc04338482",
"revisionTime": "2017-04-16T22:26:00Z"
},
{
"checksumSHA1": "mbhJnsNwGAwkTQH5c2hVRO9YmxA=",