MailHog/mailhog/http/api/v1.go

281 lines
8.1 KiB
Go

package api
import (
"log"
"encoding/json"
"fmt"
"net/smtp"
"strconv"
"strings"
"github.com/ian-kent/Go-MailHog/mailhog/data"
"github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/storage"
gotcha "github.com/ian-kent/gotcha/app"
"github.com/ian-kent/gotcha/http"
)
type APIv1 struct {
config *config.Config
eventlisteners []*EventListener
app *gotcha.App
}
type EventListener struct {
session *http.Session
ch chan []byte
}
type ReleaseConfig struct {
Email string
Host string
Port string
}
func CreateAPIv1(conf *config.Config, app *gotcha.App) *APIv1 {
log.Println("Creating API v1")
apiv1 := &APIv1{
config: conf,
eventlisteners: make([]*EventListener, 0),
app: app,
}
r := app.Router
r.Get("/api/v1/messages/?", apiv1.messages)
r.Delete("/api/v1/messages/?", apiv1.delete_all)
r.Get("/api/v1/messages/(?P<id>[0-9a-f]+)/?", apiv1.message)
r.Delete("/api/v1/messages/(?P<id>[0-9a-f]+)/?", apiv1.delete_one)
r.Get("/api/v1/messages/(?P<id>[0-9a-f]+)/download/?", apiv1.download)
r.Get("/api/v1/messages/(?P<id>[0-9a-f]+)/mime/part/(\\d+)/download/?", apiv1.download_part)
r.Post("/api/v1/messages/(?P<id>[0-9a-f]+)/release/?", apiv1.release_one)
r.Get("/api/v1/events/?", apiv1.eventstream)
go func() {
for {
select {
case msg := <- apiv1.config.MessageChan:
log.Println("Got message in APIv1 event stream")
bytes, _ := json.MarshalIndent(msg, "", " ")
json := string(bytes)
log.Printf("Sending content: %s\n", json)
apiv1.broadcast(json)
}
}
}()
return apiv1
}
func (apiv1 *APIv1) broadcast(json string) {
log.Println("[APIv1] BROADCAST /api/v1/events")
for _, l := range apiv1.eventlisteners {
log.Printf("Sending to connection: %s\n", l.session.Request.RemoteAddr)
lines := strings.Split(json, "\n")
data := ""
for _, l := range lines {
data += "data: " + l + "\n"
}
data += "\n"
size := fmt.Sprintf("%X", len(data) + 1)
l.ch <- []byte(size + "\r\n")
lines = strings.Split(data, "\n")
for _, ln := range lines {
l.ch <- []byte(ln + "\n")
}
l.ch <- []byte("\r\n")
}
}
func (apiv1 *APIv1) eventstream(session *http.Session) {
log.Println("[APIv1] GET /api/v1/events")
apiv1.eventlisteners = append(apiv1.eventlisteners, &EventListener{
session,
session.Response.Chunked(),
})
session.Response.Headers.Add("Content-Type", "text/event-stream")
session.Response.Headers.Add("Cache-Control", "no-cache")
session.Response.Headers.Add("Connection", "keep-alive")
session.Response.Write([]byte("\n\n"))
session.Response.Send()
}
func (apiv1 *APIv1) messages(session *http.Session) {
log.Println("[APIv1] GET /api/v1/messages")
// TODO start, limit
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
messages, _ := apiv1.config.Storage.(*storage.MongoDB).List(0, 1000)
bytes, _ := json.Marshal(messages)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
case *storage.Memory:
messages, _ := apiv1.config.Storage.(*storage.Memory).List(0, 1000)
bytes, _ := json.Marshal(messages)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
default:
session.Response.Status = 500
}
}
func (apiv1 *APIv1) message(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
bytes, _ := json.Marshal(message)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
case *storage.Memory:
message, _ := apiv1.config.Storage.(*storage.Memory).Load(id)
bytes, _ := json.Marshal(message)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
default:
session.Response.Status = 500
}
}
func (apiv1 *APIv1) download(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)
session.Response.Headers.Add("Content-Type", "message/rfc822")
session.Response.Headers.Add("Content-Disposition", "attachment; filename=\"" + id + ".eml\"")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
for h, l := range message.Content.Headers {
for _, v := range l {
session.Response.Write([]byte(h + ": " + v + "\r\n"))
}
}
session.Response.Write([]byte("\r\n" + message.Content.Body))
case *storage.Memory:
message, _ := apiv1.config.Storage.(*storage.Memory).Load(id)
for h, l := range message.Content.Headers {
for _, v := range l {
session.Response.Write([]byte(h + ": " + v + "\r\n"))
}
}
session.Response.Write([]byte("\r\n" + message.Content.Body))
default:
session.Response.Status = 500
}
}
func (apiv1 *APIv1) download_part(session *http.Session) {
id := session.Stash["id"].(string)
part, _ := strconv.Atoi(session.Stash["part"].(string))
log.Printf("[APIv1] GET /api/v1/messages/%s/mime/part/%d/download\n", id, part)
// TODO extension from content-type?
session.Response.Headers.Add("Content-Disposition", "attachment; filename=\"" + id + "-part-" + strconv.Itoa(part) + "\"")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
for h, l := range message.MIME.Parts[part].Headers {
for _, v := range l {
session.Response.Headers.Add(h, v)
}
}
session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
case *storage.Memory:
message, _ := apiv1.config.Storage.(*storage.Memory).Load(id)
for h, l := range message.MIME.Parts[part].Headers {
for _, v := range l {
session.Response.Headers.Add(h, v)
}
}
session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
default:
session.Response.Status = 500
}
}
func (apiv1 *APIv1) delete_all(session *http.Session) {
log.Println("[APIv1] POST /api/v1/messages")
session.Response.Headers.Add("Content-Type", "text/json")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
apiv1.config.Storage.(*storage.MongoDB).DeleteAll()
case *storage.Memory:
apiv1.config.Storage.(*storage.Memory).DeleteAll()
default:
session.Response.Status = 500
return
}
}
func (apiv1 *APIv1) release_one(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] POST /api/v1/messages/%s/release\n", id)
session.Response.Headers.Add("Content-Type", "text/json")
var msg = &data.Message{}
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
msg, _ = apiv1.config.Storage.(*storage.MongoDB).Load(id)
case *storage.Memory:
msg, _ = apiv1.config.Storage.(*storage.Memory).Load(id)
default:
session.Response.Status = 500
return
}
decoder := json.NewDecoder(session.Request.Body())
var cfg ReleaseConfig
err := decoder.Decode(&cfg)
if err != nil {
log.Printf("Error decoding request body: %s", err)
session.Response.Status = 500
session.Response.Write([]byte("Error decoding request body"))
return
}
log.Printf("Releasing to %s (via %s:%s)", cfg.Email, cfg.Host, cfg.Port)
log.Printf("Got message: %s", msg.Id)
bytes := make([]byte, 0)
for h, l := range msg.Content.Headers {
for _, v := range l {
bytes = append(bytes, []byte(h + ": " + v + "\r\n")...)
}
}
bytes = append(bytes, []byte("\r\n" + msg.Content.Body)...)
err = smtp.SendMail(cfg.Host + ":" + cfg.Port, nil, "nobody@" + apiv1.config.Hostname, []string{cfg.Email}, bytes)
if err != nil {
log.Printf("Failed to release message: %s", err)
session.Response.Status = 500
return
}
log.Printf("Message released successfully")
}
func (apiv1 *APIv1) delete_one(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] POST /api/v1/messages/%s/delete\n", id)
session.Response.Headers.Add("Content-Type", "text/json")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
apiv1.config.Storage.(*storage.MongoDB).DeleteOne(id)
case *storage.Memory:
apiv1.config.Storage.(*storage.Memory).DeleteOne(id)
default:
session.Response.Status = 500
}
}