From ebbae1658936bd809a85f5d4d128469c5025ad47 Mon Sep 17 00:00:00 2001 From: Ian Kent Date: Wed, 24 Dec 2014 16:52:53 +0000 Subject: [PATCH] Refactor to github.com/mailhog/{MailHog-Server,MailHog-UI} --- MailHog-Server/api/v1.go | 252 ------ MailHog-Server/config/config.go | 83 -- MailHog-Server/main.go | 42 - MailHog-Server/monkey/jim.go | 105 --- MailHog-Server/monkey/monkey.go | 28 - MailHog-Server/smtp/session.go | 161 ---- MailHog-Server/smtp/session_test.go | 141 ---- MailHog-Server/smtp/smtp.go | 43 - MailHog-UI/assets/.gitignore | 1 - MailHog-UI/assets/images/ajax-loader.gif | Bin 673 -> 0 bytes MailHog-UI/assets/images/github.png | Bin 1714 -> 0 bytes MailHog-UI/assets/images/hog.png | Bin 2806 -> 0 bytes MailHog-UI/assets/js/controllers.js | 291 ------- MailHog-UI/assets/js/strutil.js | 980 ----------------------- MailHog-UI/assets/templates/index.html | 244 ------ MailHog-UI/assets/templates/layout.html | 146 ---- MailHog-UI/main.go | 42 - MailHog-UI/web/web.go | 44 - MailHog/main.go | 10 +- Makefile | 15 +- http/server.go | 2 +- 21 files changed, 10 insertions(+), 2620 deletions(-) delete mode 100644 MailHog-Server/api/v1.go delete mode 100644 MailHog-Server/config/config.go delete mode 100644 MailHog-Server/main.go delete mode 100644 MailHog-Server/monkey/jim.go delete mode 100644 MailHog-Server/monkey/monkey.go delete mode 100644 MailHog-Server/smtp/session.go delete mode 100644 MailHog-Server/smtp/session_test.go delete mode 100644 MailHog-Server/smtp/smtp.go delete mode 100644 MailHog-UI/assets/.gitignore delete mode 100644 MailHog-UI/assets/images/ajax-loader.gif delete mode 100644 MailHog-UI/assets/images/github.png delete mode 100644 MailHog-UI/assets/images/hog.png delete mode 100644 MailHog-UI/assets/js/controllers.js delete mode 100644 MailHog-UI/assets/js/strutil.js delete mode 100644 MailHog-UI/assets/templates/index.html delete mode 100644 MailHog-UI/assets/templates/layout.html delete mode 100644 MailHog-UI/main.go delete mode 100644 MailHog-UI/web/web.go diff --git a/MailHog-Server/api/v1.go b/MailHog-Server/api/v1.go deleted file mode 100644 index f3683e2..0000000 --- a/MailHog-Server/api/v1.go +++ /dev/null @@ -1,252 +0,0 @@ -package api - -import ( - "encoding/json" - "net/smtp" - "strconv" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" - "github.com/ian-kent/Go-MailHog/data" - "github.com/ian-kent/Go-MailHog/storage" - "github.com/ian-kent/go-log/log" - gotcha "github.com/ian-kent/gotcha/app" - "github.com/ian-kent/gotcha/http" - - "github.com/ian-kent/goose" -) - -type APIv1 struct { - config *config.Config - app *gotcha.App -} - -var stream *goose.EventStream - -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, - app: app, - } - - stream = goose.NewEventStream() - r := app.Router - - r.Get("/api/v1/messages/?", apiv1.messages) - r.Delete("/api/v1/messages/?", apiv1.delete_all) - r.Get("/api/v1/messages/(?P[^/]+)/?", apiv1.message) - r.Delete("/api/v1/messages/(?P[^/]+)/?", apiv1.delete_one) - r.Get("/api/v1/messages/(?P[^/]+)/download/?", apiv1.download) - r.Get("/api/v1/messages/(?P[^/]+)/mime/part/(\\d+)/download/?", apiv1.download_part) - r.Post("/api/v1/messages/(?P[^/]+)/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") - b := []byte(json) - stream.Notify("data", b) -} - -func (apiv1 *APIv1) eventstream(session *http.Session) { - log.Println("[APIv1] GET /api/v1/events") - - stream.AddReceiver(session.Response.GetWriter()) -} - -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.InMemory: - messages, _ := apiv1.config.Storage.(*storage.InMemory).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.InMemory: - message, _ := apiv1.config.Storage.(*storage.InMemory).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.InMemory: - message, _ := apiv1.config.Storage.(*storage.InMemory).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.InMemory: - message, _ := apiv1.config.Storage.(*storage.InMemory).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.InMemory: - apiv1.config.Storage.(*storage.InMemory).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.InMemory: - msg, _ = apiv1.config.Storage.(*storage.InMemory).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.InMemory: - apiv1.config.Storage.(*storage.InMemory).DeleteOne(id) - default: - session.Response.Status = 500 - } -} diff --git a/MailHog-Server/config/config.go b/MailHog-Server/config/config.go deleted file mode 100644 index cc5f1d0..0000000 --- a/MailHog-Server/config/config.go +++ /dev/null @@ -1,83 +0,0 @@ -package config - -import ( - "flag" - "log" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/monkey" - "github.com/ian-kent/Go-MailHog/data" - "github.com/ian-kent/Go-MailHog/storage" - "github.com/ian-kent/envconf" -) - -func DefaultConfig() *Config { - return &Config{ - SMTPBindAddr: "0.0.0.0:1025", - HTTPBindAddr: "0.0.0.0:8025", - Hostname: "mailhog.example", - MongoUri: "127.0.0.1:27017", - MongoDb: "mailhog", - MongoColl: "messages", - StorageType: "memory", - MessageChan: make(chan *data.Message), - } -} - -type Config struct { - SMTPBindAddr string - HTTPBindAddr string - Hostname string - MongoUri string - MongoDb string - MongoColl string - StorageType string - InviteJim bool - Storage storage.Storage - MessageChan chan *data.Message - Assets func(asset string) ([]byte, error) - Monkey monkey.ChaosMonkey -} - -var cfg = DefaultConfig() -var jim = &monkey.Jim{} - -func Configure() *Config { - switch cfg.StorageType { - case "memory": - log.Println("Using in-memory storage") - cfg.Storage = storage.CreateInMemory() - case "mongodb": - log.Println("Using MongoDB message storage") - s := storage.CreateMongoDB(cfg.MongoUri, cfg.MongoDb, cfg.MongoColl) - if s == nil { - log.Println("MongoDB storage unavailable, reverting to in-memory storage") - cfg.Storage = storage.CreateInMemory() - } else { - log.Println("Connected to MongoDB") - cfg.Storage = s - } - default: - log.Fatalf("Invalid storage type %s", cfg.StorageType) - } - - if cfg.InviteJim { - jim.Configure(func(message string, args ...interface{}) { - log.Printf(message, args...) - }) - cfg.Monkey = jim - } - - return cfg -} - -func RegisterFlags() { - flag.StringVar(&cfg.SMTPBindAddr, "smtpbindaddr", envconf.FromEnvP("MH_SMTP_BIND_ADDR", "0.0.0.0:1025").(string), "SMTP bind interface and port, e.g. 0.0.0.0:1025 or just :1025") - flag.StringVar(&cfg.HTTPBindAddr, "httpbindaddr", envconf.FromEnvP("MH_HTTP_BIND_ADDR", "0.0.0.0:8025").(string), "HTTP bind interface and port, e.g. 0.0.0.0:8025 or just :8025") - flag.StringVar(&cfg.Hostname, "hostname", envconf.FromEnvP("MH_HOSTNAME", "mailhog.example").(string), "Hostname for EHLO/HELO response, e.g. mailhog.example") - flag.StringVar(&cfg.StorageType, "storage", envconf.FromEnvP("MH_STORAGE", "memory").(string), "Message storage: memory (default) or mongodb") - flag.StringVar(&cfg.MongoUri, "mongouri", envconf.FromEnvP("MH_MONGO_URI", "127.0.0.1:27017").(string), "MongoDB URI, e.g. 127.0.0.1:27017") - flag.StringVar(&cfg.MongoDb, "mongodb", envconf.FromEnvP("MH_MONGO_DB", "mailhog").(string), "MongoDB database, e.g. mailhog") - flag.StringVar(&cfg.MongoColl, "mongocoll", envconf.FromEnvP("MH_MONGO_COLLECTION", "messages").(string), "MongoDB collection, e.g. messages") - flag.BoolVar(&cfg.InviteJim, "invite-jim", envconf.FromEnvP("MH_INVITE_JIM", false).(bool), "Decide whether to invite Jim (beware, he causes trouble)") - jim.RegisterFlags() -} diff --git a/MailHog-Server/main.go b/MailHog-Server/main.go deleted file mode 100644 index a7ac00b..0000000 --- a/MailHog-Server/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "flag" - "os" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/api" - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" - "github.com/ian-kent/Go-MailHog/MailHog-Server/smtp" - "github.com/ian-kent/Go-MailHog/MailHog-UI/assets" - "github.com/ian-kent/Go-MailHog/http" - "github.com/ian-kent/go-log/log" - gotcha "github.com/ian-kent/gotcha/app" -) - -var conf *config.Config -var exitCh chan int - -func configure() { - config.RegisterFlags() - flag.Parse() - conf = config.Configure() -} - -func main() { - configure() - - exitCh = make(chan int) - cb := func(app *gotcha.App) { - api.CreateAPIv1(conf, app) - } - go http.Listen(conf, assets.Asset, exitCh, cb) - go smtp.Listen(conf, exitCh) - - for { - select { - case <-exitCh: - log.Printf("Received exit signal") - os.Exit(0) - } - } -} diff --git a/MailHog-Server/monkey/jim.go b/MailHog-Server/monkey/jim.go deleted file mode 100644 index afe6186..0000000 --- a/MailHog-Server/monkey/jim.go +++ /dev/null @@ -1,105 +0,0 @@ -package monkey - -import ( - "flag" - "math/rand" - "net" - "time" - - "github.com/ian-kent/linkio" -) - -// Jim is a chaos monkey -type Jim struct { - disconnectChance float64 - acceptChance float64 - linkSpeedAffect float64 - linkSpeedMin float64 - linkSpeedMax float64 - rejectSenderChance float64 - rejectRecipientChance float64 - rejectAuthChance float64 - logf func(message string, args ...interface{}) -} - -// RegisterFlags implements ChaosMonkey.RegisterFlags -func (j *Jim) RegisterFlags() { - flag.Float64Var(&j.disconnectChance, "jim-disconnect", 0.005, "Chance of disconnect") - flag.Float64Var(&j.acceptChance, "jim-accept", 0.99, "Chance of accept") - flag.Float64Var(&j.linkSpeedAffect, "jim-linkspeed-affect", 0.1, "Chance of affecting link speed") - flag.Float64Var(&j.linkSpeedMin, "jim-linkspeed-min", 1024, "Minimum link speed (in bytes per second)") - flag.Float64Var(&j.linkSpeedMax, "jim-linkspeed-max", 10240, "Maximum link speed (in bytes per second)") - flag.Float64Var(&j.rejectSenderChance, "jim-reject-sender", 0.05, "Chance of rejecting a sender (MAIL FROM)") - flag.Float64Var(&j.rejectRecipientChance, "jim-reject-recipient", 0.05, "Chance of rejecting a recipient (RCPT TO)") - flag.Float64Var(&j.rejectAuthChance, "jim-reject-auth", 0.05, "Chance of rejecting authentication (AUTH)") -} - -// Configure implements ChaosMonkey.Configure -func (j *Jim) Configure(logf func(string, ...interface{})) { - j.logf = logf - rand.Seed(time.Now().Unix()) -} - -// Accept implements ChaosMonkey.Accept -func (j *Jim) Accept(conn net.Conn) bool { - if rand.Float64() > j.acceptChance { - j.logf("Jim: Rejecting connection\n") - return false - } - j.logf("Jim: Allowing connection\n") - return true -} - -// LinkSpeed implements ChaosMonkey.LinkSpeed -func (j *Jim) LinkSpeed() *linkio.Throughput { - rand.Seed(time.Now().Unix()) - if rand.Float64() < j.linkSpeedAffect { - lsDiff := j.linkSpeedMax - j.linkSpeedMin - lsAffect := j.linkSpeedMin + (lsDiff * rand.Float64()) - f := linkio.Throughput(lsAffect) * linkio.BytePerSecond - j.logf("Jim: Restricting throughput to %s\n", f) - return &f - } - j.logf("Jim: Allowing unrestricted throughput") - return nil -} - -// ValidRCPT implements ChaosMonkey.ValidRCPT -func (j *Jim) ValidRCPT(rcpt string) bool { - if rand.Float64() < j.rejectRecipientChance { - j.logf("Jim: Rejecting recipient %s\n", rcpt) - return false - } - j.logf("Jim: Allowing recipient%s\n", rcpt) - return true -} - -// ValidMAIL implements ChaosMonkey.ValidMAIL -func (j *Jim) ValidMAIL(mail string) bool { - if rand.Float64() < j.rejectSenderChance { - j.logf("Jim: Rejecting sender %s\n", mail) - return false - } - j.logf("Jim: Allowing sender %s\n", mail) - return true -} - -// ValidAUTH implements ChaosMonkey.ValidAUTH -func (j *Jim) ValidAUTH(mechanism string, args ...string) bool { - if rand.Float64() < j.rejectAuthChance { - j.logf("Jim: Rejecting authentication %s: %s\n", mechanism, args) - return false - } - j.logf("Jim: Allowing authentication %s: %s\n", mechanism, args) - return true -} - -// Disconnect implements ChaosMonkey.Disconnect -func (j *Jim) Disconnect() bool { - if rand.Float64() < j.disconnectChance { - j.logf("Jim: Being nasty, kicking them off\n") - return true - } - j.logf("Jim: Being nice, letting them stay\n") - return false -} diff --git a/MailHog-Server/monkey/monkey.go b/MailHog-Server/monkey/monkey.go deleted file mode 100644 index 2986eca..0000000 --- a/MailHog-Server/monkey/monkey.go +++ /dev/null @@ -1,28 +0,0 @@ -package monkey - -import ( - "net" - - "github.com/ian-kent/linkio" -) - -// ChaosMonkey should be implemented by chaos monkeys! -type ChaosMonkey interface { - RegisterFlags() - Configure(func(string, ...interface{})) - - // Accept is called for each incoming connection. Returning false closes the connection. - Accept(conn net.Conn) bool - // LinkSpeed sets the maximum connection throughput (in one direction) - LinkSpeed() *linkio.Throughput - - // ValidRCPT is called for the RCPT command. Returning false signals an invalid recipient. - ValidRCPT(rcpt string) bool - // ValidMAIL is called for the MAIL command. Returning false signals an invalid sender. - ValidMAIL(mail string) bool - // ValidAUTH is called after authentication. Returning false signals invalid authentication. - ValidAUTH(mechanism string, args ...string) bool - - // Disconnect is called after every read. Returning true will close the connection. - Disconnect() bool -} diff --git a/MailHog-Server/smtp/session.go b/MailHog-Server/smtp/session.go deleted file mode 100644 index 89dea67..0000000 --- a/MailHog-Server/smtp/session.go +++ /dev/null @@ -1,161 +0,0 @@ -package smtp - -// http://www.rfc-editor.org/rfc/rfc5321.txt - -import ( - "io" - "log" - "strings" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/monkey" - "github.com/ian-kent/Go-MailHog/data" - "github.com/ian-kent/Go-MailHog/storage" - "github.com/ian-kent/linkio" - "github.com/mailhog/smtp" -) - -// Session represents a SMTP session using net.TCPConn -type Session struct { - conn io.ReadWriteCloser - proto *smtp.Protocol - storage storage.Storage - messageChan chan *data.Message - remoteAddress string - isTLS bool - line string - link *linkio.Link - - reader io.Reader - writer io.Writer - monkey monkey.ChaosMonkey -} - -// Accept starts a new SMTP session using io.ReadWriteCloser -func Accept(remoteAddress string, conn io.ReadWriteCloser, storage storage.Storage, messageChan chan *data.Message, hostname string, monkey monkey.ChaosMonkey) { - defer conn.Close() - - proto := smtp.NewProtocol() - proto.Hostname = hostname - var link *linkio.Link - reader := io.Reader(conn) - writer := io.Writer(conn) - if monkey != nil { - linkSpeed := monkey.LinkSpeed() - if linkSpeed != nil { - link = linkio.NewLink(*linkSpeed * linkio.BytePerSecond) - reader = link.NewLinkReader(io.Reader(conn)) - writer = link.NewLinkWriter(io.Writer(conn)) - } - } - - session := &Session{conn, proto, storage, messageChan, remoteAddress, false, "", link, reader, writer, monkey} - proto.LogHandler = session.logf - proto.MessageReceivedHandler = session.acceptMessage - proto.ValidateSenderHandler = session.validateSender - proto.ValidateRecipientHandler = session.validateRecipient - proto.ValidateAuthenticationHandler = session.validateAuthentication - - session.logf("Starting session") - session.Write(proto.Start()) - for session.Read() == true { - if monkey != nil && monkey.Disconnect != nil && monkey.Disconnect() { - session.conn.Close() - break - } - } - session.logf("Session ended") -} - -func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *smtp.Reply, ok bool) { - if c.monkey != nil { - ok := c.monkey.ValidAUTH(mechanism, args...) - if !ok { - // FIXME better error? - return smtp.ReplyUnrecognisedCommand(), false - } - } - return nil, true -} - -func (c *Session) validateRecipient(to string) bool { - if c.monkey != nil { - ok := c.monkey.ValidRCPT(to) - if !ok { - return false - } - } - return true -} - -func (c *Session) validateSender(from string) bool { - if c.monkey != nil { - ok := c.monkey.ValidMAIL(from) - if !ok { - return false - } - } - return true -} - -func (c *Session) acceptMessage(msg *data.Message) (id string, err error) { - c.logf("Storing message %s", msg.ID) - id, err = c.storage.Store(msg) - c.messageChan <- msg - return -} - -func (c *Session) logf(message string, args ...interface{}) { - message = strings.Join([]string{"[SMTP %s]", message}, " ") - args = append([]interface{}{c.remoteAddress}, args...) - log.Printf(message, args...) -} - -// Read reads from the underlying net.TCPConn -func (c *Session) Read() bool { - buf := make([]byte, 1024) - n, err := c.reader.Read(buf) - - if n == 0 { - c.logf("Connection closed by remote host\n") - io.Closer(c.conn).Close() // not sure this is necessary? - return false - } - - if err != nil { - c.logf("Error reading from socket: %s\n", err) - return false - } - - text := string(buf[0:n]) - logText := strings.Replace(text, "\n", "\\n", -1) - logText = strings.Replace(logText, "\r", "\\r", -1) - c.logf("Received %d bytes: '%s'\n", n, logText) - - c.line += text - - for strings.Contains(c.line, "\n") { - line, reply := c.proto.Parse(c.line) - c.line = line - - if reply != nil { - c.Write(reply) - if reply.Status == 221 { - io.Closer(c.conn).Close() - return false - } - } - } - - return true -} - -// Write writes a reply to the underlying net.TCPConn -func (c *Session) Write(reply *smtp.Reply) { - lines := reply.Lines() - for _, l := range lines { - logText := strings.Replace(l, "\n", "\\n", -1) - logText = strings.Replace(logText, "\r", "\\r", -1) - c.logf("Sent %d bytes: '%s'", len(l), logText) - c.writer.Write([]byte(l)) - } -} diff --git a/MailHog-Server/smtp/session_test.go b/MailHog-Server/smtp/session_test.go deleted file mode 100644 index c6240c0..0000000 --- a/MailHog-Server/smtp/session_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package smtp - -import ( - "errors" - "sync" - "testing" - - . "github.com/smartystreets/goconvey/convey" - - "github.com/ian-kent/Go-MailHog/data" - "github.com/ian-kent/Go-MailHog/storage" -) - -type fakeRw struct { - _read func(p []byte) (n int, err error) - _write func(p []byte) (n int, err error) - _close func() error -} - -func (rw *fakeRw) Read(p []byte) (n int, err error) { - if rw._read != nil { - return rw._read(p) - } - return 0, nil -} -func (rw *fakeRw) Close() error { - if rw._close != nil { - return rw._close() - } - return nil -} -func (rw *fakeRw) Write(p []byte) (n int, err error) { - if rw._write != nil { - return rw._write(p) - } - return len(p), nil -} - -func TestAccept(t *testing.T) { - Convey("Accept should handle a connection", t, func() { - frw := &fakeRw{} - mChan := make(chan *data.Message) - Accept("1.1.1.1:11111", frw, storage.CreateInMemory(), mChan, "localhost", nil) - }) -} - -func TestSocketError(t *testing.T) { - Convey("Socket errors should return from Accept", t, func() { - frw := &fakeRw{ - _read: func(p []byte) (n int, err error) { - return -1, errors.New("OINK") - }, - } - mChan := make(chan *data.Message) - Accept("1.1.1.1:11111", frw, storage.CreateInMemory(), mChan, "localhost", nil) - }) -} - -func TestAcceptMessage(t *testing.T) { - Convey("acceptMessage should be called", t, func() { - mbuf := "EHLO localhost\nMAIL FROM:\nRCPT TO:\nDATA\nHi.\r\n.\r\nQUIT\n" - var rbuf []byte - frw := &fakeRw{ - _read: func(p []byte) (n int, err error) { - if len(p) >= len(mbuf) { - ba := []byte(mbuf) - mbuf = "" - for i, b := range ba { - p[i] = b - } - return len(ba), nil - } - - ba := []byte(mbuf[0:len(p)]) - mbuf = mbuf[len(p):] - for i, b := range ba { - p[i] = b - } - return len(ba), nil - }, - _write: func(p []byte) (n int, err error) { - rbuf = append(rbuf, p...) - return len(p), nil - }, - _close: func() error { - return nil - }, - } - mChan := make(chan *data.Message) - var wg sync.WaitGroup - wg.Add(1) - handlerCalled := false - go func() { - handlerCalled = true - <-mChan - //FIXME breaks some tests (in drone.io) - //m := <-mChan - //So(m, ShouldNotBeNil) - wg.Done() - }() - Accept("1.1.1.1:11111", frw, storage.CreateInMemory(), mChan, "localhost", nil) - wg.Wait() - So(handlerCalled, ShouldBeTrue) - }) -} - -func TestValidateAuthentication(t *testing.T) { - Convey("validateAuthentication is always successful", t, func() { - c := &Session{} - - err, ok := c.validateAuthentication("OINK") - So(err, ShouldBeNil) - So(ok, ShouldBeTrue) - - err, ok = c.validateAuthentication("OINK", "arg1") - So(err, ShouldBeNil) - So(ok, ShouldBeTrue) - - err, ok = c.validateAuthentication("OINK", "arg1", "arg2") - So(err, ShouldBeNil) - So(ok, ShouldBeTrue) - }) -} - -func TestValidateRecipient(t *testing.T) { - Convey("validateRecipient is always successful", t, func() { - c := &Session{} - - So(c.validateRecipient("OINK"), ShouldBeTrue) - So(c.validateRecipient("foo@bar.mailhog"), ShouldBeTrue) - }) -} - -func TestValidateSender(t *testing.T) { - Convey("validateSender is always successful", t, func() { - c := &Session{} - - So(c.validateSender("OINK"), ShouldBeTrue) - So(c.validateSender("foo@bar.mailhog"), ShouldBeTrue) - }) -} diff --git a/MailHog-Server/smtp/smtp.go b/MailHog-Server/smtp/smtp.go deleted file mode 100644 index ff9df01..0000000 --- a/MailHog-Server/smtp/smtp.go +++ /dev/null @@ -1,43 +0,0 @@ -package smtp - -import ( - "io" - "log" - "net" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" -) - -func Listen(cfg *config.Config, exitCh chan int) *net.TCPListener { - log.Printf("[SMTP] Binding to address: %s\n", cfg.SMTPBindAddr) - ln, err := net.Listen("tcp", cfg.SMTPBindAddr) - if err != nil { - log.Fatalf("[SMTP] Error listening on socket: %s\n", err) - } - defer ln.Close() - - for { - conn, err := ln.Accept() - if err != nil { - log.Printf("[SMTP] Error accepting connection: %s\n", err) - continue - } - - if cfg.Monkey != nil { - ok := cfg.Monkey.Accept(conn) - if !ok { - conn.Close() - continue - } - } - - go Accept( - conn.(*net.TCPConn).RemoteAddr().String(), - io.ReadWriteCloser(conn), - cfg.Storage, - cfg.MessageChan, - cfg.Hostname, - cfg.Monkey, - ) - } -} diff --git a/MailHog-UI/assets/.gitignore b/MailHog-UI/assets/.gitignore deleted file mode 100644 index 60ce659..0000000 --- a/MailHog-UI/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -assets.go diff --git a/MailHog-UI/assets/images/ajax-loader.gif b/MailHog-UI/assets/images/ajax-loader.gif deleted file mode 100644 index d0bce1542342e912da81a2c260562df172f30d73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nnmm28Kh24mmkF0U1e2Nli^nlO|14{Lk&@8WQa67~pE8 zXTZz|lvDgC+Z`3#dv5h=E26FfcG1 zbL_hF&)}42ws10s6^G;;cE1^EoUR)U5A70}d2pLv!jVIT7j&Z~EblI3x0K*v_sV|m z0kj3v921Z^em#l`(k(o@H$3ZdDRc@9NidXDNbqrumReCGv$gd8+e8WW28HVqkJ_9i zH>s*<31KtHjANIPvi2#*6BEu%3Dak5O_t&NBI)H?V$TxT}#l{vOTn5naXTfF^&~Hhq+NX@#Ccc>y7T?;vjI&jdhsDsPJyAw*m0Qz>i}K7# zL9w50Ng{fT}A5JUe8lRK1h7_Y2;BWJDd=c6f&i?Wv5(5q?6|P zQw{>maxZP<537OA37Uk}7@%_$4o$EWe_Zl>&#id|lE-BpDC#+Fn|msJ%_2h{Hg1vP z#N8WAzfWasG}yq|xqE)DrWaOofX=z|?*pgc%{ig5vl!pqDlC|q&~Z0$&Rvsft&VO- z4MZj+%-+Vx%W}v;V76hyp=;+R;x+~t^Q%*xuFTQAF2})fSfTHDAs>sO!OBw`)&)o$ c0!CNZt))x~rAZP^^P&YOFfdqy5)K#u0POD40{{R3 diff --git a/MailHog-UI/assets/images/github.png b/MailHog-UI/assets/images/github.png deleted file mode 100644 index 8b25551a97921681334176ee143b41510a117d86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1714 zcmaJ?X;2eq7*4oFu!ne{XxAht2qc?8LXr|_LPCfTpaBK7K$c{I0Ld=NLIOeuC;@2) zZ$K%a)k+m-s0>xHmKxL%0V&0TRzzznhgyqrIC$F)0{WwLXLrBvd*^wc_uSc%h%m9E z{W5z3f#4_!7RvAyFh6!S_*<8qJ%KOIm?#E|L=rJQq=gB5C6WLG5;c?r%V0>EmEH#X z5eSwPRa6WXBMs#$5H%GtW2go-in9p>zW@UYDNNWc^XOXZQ? z1QjEV00I#$3^1wQUJ8&-2UsjB-G|9y(LDhMNN3PM{APL4eYi{(m*ERcUnJa{R+-3^ z34^A6;U^v`8N*O6ji%S@sd{fJqD`XFIUJ5zgTe5^5nj414F(y!G&=H(f)Lgzv?>%+ zAsWD}2qhpH7>|TU`X&W6IxDNuO_vET7|j5oG&&VDr!)hUO8+0KR?nh!m<)a!?|%yG zqOwq!CWCcIhE{<$E|F|@g>nP6FoYr6C<8>D?ID9%&5J(4oSbR1I^byW*g@__U z4QsF&uJSEcFeleM3~ChjEQGbHOjsGDMbyAl(p=Ttv9RaVo8~I#js@@Y9C^_2U})yn zzSHU%6FxuY?d;&65MyR({^lU*3$z$ZllDb(o&<7d;A_`h2U+3~BJ2Hv`{W}KEU801#cv_B|9Cm!ynR{S`AMsSn z;7E=B;mb!wx$L;S>yGXG^6=&WlQn9$s?&L%Y1D8TI^MlKB1DqsEng$>f4=xYWBoPI z_S1p!sJ#d2?YI4kPA{k}Eby?F=f-J9zIc`YDl^pzjVm~9ebE?Hn?t0Nx+la|D0MB; z9)2xv1G>a1|A9kQ>~DV<=X3-4yC&n!m8-3K#P z{X@0zRuQsy$+N ziSCoLJU{Z$nQy4A4Y5UJ07$5FA~qL2%Q+cLaqDU?Lz3?=BC5;Nk6BbTmmceEaM>-Z zi>O&-dSE=%ex;vcvCOk{*JQ5^_4M z4lW7%l9IqY(z7pV(?I@@8=KPFO82)O{VDI18-*d-k$YmI^XiuPs_LuFw<^ZcD}yP5 c*NrbeloN*74g`U%%F6r~k%+>C^#XapzmV0H-2eap diff --git a/MailHog-UI/assets/images/hog.png b/MailHog-UI/assets/images/hog.png deleted file mode 100644 index c846729fbbba52c9e64b2576945bc50dcdfd43fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2806 zcma)8`9IT-;~$Zuj5S9T*`f)_8*leyyoMDc*4rJ*8Jp#(oVlir99ym!G0XAJ5viOJ zDl|vTHF7magm;V%s*k_m`}qFwJbrlo^8Drbcv9@JmSUoEq971R3}Xe@ALxezYPZ~fstYQ>9 zS3WNHo&3}}0-c4EH4w)jRUxJg{Kw{}vjvob^4izmO~=wxts-exz~p~SPXU^Ptz4C* z-9%_So)51M8T!`R)4Bm4+kGbOx|yEI2rX8VH?BxLs%bqN^Sgn377?Ps7(#oh#xk-s z-sS8TU+E1lv;6uk>Q@8-93cEKKtdMgcAD8blOIwrbrC^iqyrV#s42148K0!3uI8dq zPN$BvBg*QSNXg8jl5SQ1LEy}FRm)C_u>loao#KB>G(kiJ!cH?OJ5SPv2>Ua@4XWA> zU}x4{xre!JXz`Hi^}ptTK}kmQz~$lpR&QrWpfH4RwVFk9oIDqm-NZD= zb>srlg~DIe<$D@OPpXA0GQ@LMy;|O5`4ILHc+ps*47{wM2K`tD*Y5Ol=HAj?5fvKF z1>g#yibc1$r8J?kqcZunAD0guSKHmywKRHZHb|k9S86@(&JHCM6+Ccii2hRvKHjk! zl=a?TNZmdc=6NC>JW1L5;Q%K;bO}56SHQ}x>~17S`Un)6j>C-$UhX%S_)u)QeE-gG z8-?0&*6;7jn*nLxtb!v@Q{ZG==#!`zbzPoA>Yv+sx8I^N24qoIuv3ynkvp^#&T?9$ zJ3{C(_eQTTYOWV@@Ep2b${|#L=Egu+6-~5sVS11mPM*X0Ea z^fLTjO;BgM0Q7EhPW1rPu!sV2=aMsIH<4Jmpi2H@F&go|> zMLDw7^Z~;iwUOOer)vuTLen0Jd`&?J%;i(LpXY-QRx61^n31?m3GR zLn@JG?C!xY!8|cp{Jtsl$FU8nN%r{}TGP!aHcX(ShC(0qL^lAQFJ3tC`*%*0{f*c} zf~@S05TEAQrSq3l$V9N&e1lHunP^C_)Tg91v;=r|dGyT^&?u3Vd^EzNTZwUQ^(eo$ zM-m(DzibAaUOi$aMKdt*?}Idu-?F6ySWYAz5oZ8Y@~p(nkg)B$Cga*Kvu7cX0(ypv zoVT9ZE~NjCtq29woXc6DSmNWv;n4c1&ALG%0o7*8WyLH5tc~OeOiZe^lyvtlDg0R7+|wF;Va#yCfAb(q+EQkwXiL=veo9R1vFPo> zTK=G>-j}EwipL21u6k}b2wbLEk(63&lb@E9@~%x?fzk0e&!*J%qwKZ;>&a8izso-u zQB$lU7sUB+46fu}X5!)ils(wWf zmX`nxAl>~skxKa4Lpo_NUKgP++8qIIIcD2~6grx2L(Br*m}53)52Wy8;y|6op5K+9a85WGx3?yr`Q=HJJKQ=BW3M zqVV7I9I{1BN?HL-HYQiQAR0nrbMKIqFE1kG_U{Xd!%&=C$P4pscMO!ZrD>bn%}kUZQX20e z!Z_VYQMm4sTk@KGcot(K31BG)`&-lK=H7SEoUD>{J^_j}gp33)!1Drw^r^c_a&kIi z;=Ur6WDkpDvy7c>+1+W}rAzga5w33p4xPkxP&lWbsQ3vA8Ff<^2t6efMPJBhI0Lcg z^CT1N-xo)waRUoA7ao?3o-yaT;7wpSQZq^0e=fIia&C zePK<1$a;h1glAnVMg&k{2q12-CK5C)B?DXG98(}IB6waKa=9BW*Pi+1#lxMUZ+3)7 zhcn3ew4cJk{7YG9(#Zctj^_e)e!AZ?lN%q_nP@`Rt2mlrwkv2c81}IJqZgf1BK8y` z`um*ywgFJrUOXF-c#d2#jL)G|47lXB#WzyVA%4nMCY@z8b&U(lSXjjj$_C+eFIcF0 zrBnSCMuW4TrP#8wU3-ajs@CD_t|xU7d^cj}0qlv?c91aSN?g<;rGX>mLWJ)<Y kLHHEm|CjAB`XizV+UO 0) { - if(part.MIME.Parts[p].Headers["Content-Type"][0].match(mime + ";?.*")) { - return part.MIME.Parts[p]; - } else if (part.MIME.Parts[p].Headers["Content-Type"][0].match(/multipart\/.*/)) { - var f = $scope.findMatchingMIME(part.MIME.Parts[p], mime); - if(f != null) { - return f; - } - } - } - } - } - } - return null; - } - $scope.hasHTML = function(message) { - // TODO cache this - var l = $scope.findMatchingMIME(message, "text/html"); - if(l != null && l !== "undefined") { - return true - } - return false; - } - $scope.getMessageHTML = function(message) { - var l = $scope.findMatchingMIME(message, "text/html"); - if(l != null && l !== "undefined") { - return $scope.tryDecode(l); - } - return ""; - } - - $scope.tryDecode = function(l){ - if(l.Headers && l.Headers["Content-Type"] && l.Headers["Content-Transfer-Encoding"]){ - return $scope.tryDecodeContent({Content:l},l.Body.replace(/=[\r\n]+/gm,"")); - }else{ - return l.Body; - } - }; - $scope.date = function(timestamp) { - return (new Date(timestamp)).toString(); - }; - - $scope.deleteAll = function() { - $('#confirm-delete-all').modal('show'); - } - - $scope.releaseOne = function(message) { - $scope.releasing = message; - $('#release-one').modal('show'); - } - $scope.confirmReleaseMessage = function() { - $('#release-one').modal('hide'); - var message = $scope.releasing; - $scope.releasing = null; - - var e = $scope.startEvent("Releasing message", message.ID, "glyphicon-share"); - - $http.post('/api/v1/messages/' + message.ID + '/release', { - email: $('#release-message-email').val(), - host: $('#release-message-smtp-host').val(), - port: $('#release-message-smtp-port').val(), - }).success(function() { - e.done(); - }).error(function(err) { - e.fail(); - e.error = err; - }); - } - - $scope.getSource = function(message) { - var source = ""; - $.each(message.Content.Headers, function(k, v) { - source += k + ": " + v + "\n"; - }); - source += "\n"; - source += message.Content.Body; - return source; - } - - $scope.deleteAllConfirm = function() { - $('#confirm-delete-all').modal('hide'); - var e = $scope.startEvent("Deleting all messages", null, "glyphicon-remove-circle"); - $http.delete('/api/v1/messages').success(function() { - $scope.refresh(); - $scope.preview = null; - e.done() - }); - } - - $scope.deleteOne = function(message) { - var e = $scope.startEvent("Deleting message", message.ID, "glyphicon-remove"); - $http.delete('/api/v1/messages/' + message.ID).success(function() { - if($scope.preview._id == message._id) $scope.preview = null; - $scope.refresh(); - e.done(); - }); - } -}); diff --git a/MailHog-UI/assets/js/strutil.js b/MailHog-UI/assets/js/strutil.js deleted file mode 100644 index 3ce629e..0000000 --- a/MailHog-UI/assets/js/strutil.js +++ /dev/null @@ -1,980 +0,0 @@ -// -*- coding: utf-8 -*- - -// GO-MAILHOG: This file borrowed from http://0xcc.net/jsescape/strutil.js - -// Utility functions for strings. -// -// Copyright (C) 2007 Satoru Takabayashi -// All rights reserved. This is free software with ABSOLUTELY NO WARRANTY. -// You can redistribute it and/or modify it under the terms of -// the GNU General Public License version 2. - -// NOTES: -// -// Surrogate pairs: -// -// 1st 0xD800 - 0xDBFF (high surrogate) -// 2nd 0xDC00 - 0xDFFF (low surrogate) -// -// UTF-8 sequences: -// -// 0xxxxxxx -// 110xxxxx 10xxxxxx -// 1110xxxx 10xxxxxx 10xxxxxx -// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - -var EQUAL_SIGN = 0x3D; -var QUESTION_MARK = 0x3F; - -// "あい" => [ 0x3042, 0x3044 ] -function convertStringToUnicodeCodePoints(str) { - var surrogate_1st = 0; - var unicode_codes = []; - for (var i = 0; i < str.length; ++i) { - var utf16_code = str.charCodeAt(i); - if (surrogate_1st != 0) { - if (utf16_code >= 0xDC00 && utf16_code <= 0xDFFF) { - var surrogate_2nd = utf16_code; - var unicode_code = (surrogate_1st - 0xD800) * (1 << 10) + (1 << 16) + - (surrogate_2nd - 0xDC00); - unicode_codes.push(unicode_code); - } else { - // Malformed surrogate pair ignored. - } - surrogate_1st = 0; - } else if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) { - surrogate_1st = utf16_code; - } else { - unicode_codes.push(utf16_code); - } - } - return unicode_codes; -} - -// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => [ 0x3042, 0x3044 ] -function convertUtf8BytesToUnicodeCodePoints(utf8_bytes) { - var unicode_codes = []; - var unicode_code = 0; - var num_followed = 0; - for (var i = 0; i < utf8_bytes.length; ++i) { - var utf8_byte = utf8_bytes[i]; - if (utf8_byte >= 0x100) { - // Malformed utf8 byte ignored. - } else if ((utf8_byte & 0xC0) == 0x80) { - if (num_followed > 0) { - unicode_code = (unicode_code << 6) | (utf8_byte & 0x3f); - num_followed -= 1; - } else { - // Malformed UTF-8 sequence ignored. - } - } else { - if (num_followed == 0) { - unicode_codes.push(unicode_code); - } else { - // Malformed UTF-8 sequence ignored. - } - if (utf8_byte < 0x80){ // 1-byte - unicode_code = utf8_byte; - num_followed = 0; - } else if ((utf8_byte & 0xE0) == 0xC0) { // 2-byte - unicode_code = utf8_byte & 0x1f; - num_followed = 1; - } else if ((utf8_byte & 0xF0) == 0xE0) { // 3-byte - unicode_code = utf8_byte & 0x0f; - num_followed = 2; - } else if ((utf8_byte & 0xF8) == 0xF0) { // 4-byte - unicode_code = utf8_byte & 0x07; - num_followed = 3; - } else { - // Malformed UTF-8 sequence ignored. - } - } - } - if (num_followed == 0) { - unicode_codes.push(unicode_code); - } else { - // Malformed UTF-8 sequence ignored. - } - unicode_codes.shift(); // Trim the first element. - return unicode_codes; -} - -// Helper function. -function convertEscapedCodesToCodes(str, prefix, base, num_bits) { - var parts = str.split(prefix); - parts.shift(); // Trim the first element. - var codes = []; - var max = Math.pow(2, num_bits); - for (var i = 0; i < parts.length; ++i) { - var code = parseInt(parts[i], base); - if (code >= 0 && code < max) { - codes.push(code); - } else { - // Malformed code ignored. - } - } - return codes; -} - -// r'\u3042\u3044' => [ 0x3042, 0x3044 ] -// Note that the r '...' notation is borrowed from Python. -function convertEscapedUtf16CodesToUtf16Codes(str) { - return convertEscapedCodesToCodes(str, "\\u", 16, 16); -} - -// r'\U00003042\U00003044' => [ 0x3042, 0x3044 ] -function convertEscapedUtf32CodesToUnicodeCodePoints(str) { - return convertEscapedCodesToCodes(str, "\\U", 16, 32); -} - -// r'\xE3\x81\x82\xE3\x81\x84' => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] -// r'\343\201\202\343\201\204' => [ 0343, 0201, 0202, 0343, 0201, 0204 ] -function convertEscapedBytesToBytes(str, base) { - var prefix = (base == 16 ? "\\x" : "\\"); - return convertEscapedCodesToCodes(str, prefix, base, 8); -} - -// "&#12354;&#12356;" => [ 0x3042, 0x3044 ] -// "&#x3042;&#x3044;" => [ 0x3042, 0x3044 ] -function convertNumRefToUnicodeCodePoints(str, base) { - var num_refs = str.split(";"); - num_refs.pop(); // Trim the last element. - var unicode_codes = []; - for (var i = 0; i < num_refs.length; ++i) { - var decimal_str = num_refs[i].replace(/^&#x?/, ""); - var unicode_code = parseInt(decimal_str, base); - unicode_codes.push(unicode_code); - } - return unicode_codes; -} - -// [ 0x3042, 0x3044 ] => [ 0x3042, 0x3044 ] -// [ 0xD840, 0xDC0B ] => [ 0x2000B ] // A surrogate pair. -function convertUnicodeCodePointsToUtf16Codes(unicode_codes) { - var utf16_codes = []; - for (var i = 0; i < unicode_codes.length; ++i) { - var unicode_code = unicode_codes[i]; - if (unicode_code < (1 << 16)) { - utf16_codes.push(unicode_code); - } else { - var first = ((unicode_code - (1 << 16)) / (1 << 10)) + 0xD800; - var second = (unicode_code % (1 << 10)) + 0xDC00; - utf16_codes.push(first) - utf16_codes.push(second) - } - } - return utf16_codes; -} - -// 0x3042 => [ 0xE3, 0x81, 0x82 ] -function convertUnicodeCodePointToUtf8Bytes(unicode_code, base) { - var utf8_bytes = []; - if (unicode_code < 0x80) { // 1-byte - utf8_bytes.push(unicode_code); - } else if (unicode_code < (1 << 11)) { // 2-byte - utf8_bytes.push((unicode_code >>> 6) | 0xC0); - utf8_bytes.push((unicode_code & 0x3F) | 0x80); - } else if (unicode_code < (1 << 16)) { // 3-byte - utf8_bytes.push((unicode_code >>> 12) | 0xE0); - utf8_bytes.push(((unicode_code >> 6) & 0x3f) | 0x80); - utf8_bytes.push((unicode_code & 0x3F) | 0x80); - } else if (unicode_code < (1 << 21)) { // 4-byte - utf8_bytes.push((unicode_code >>> 18) | 0xF0); - utf8_bytes.push(((unicode_code >> 12) & 0x3F) | 0x80); - utf8_bytes.push(((unicode_code >> 6) & 0x3F) | 0x80); - utf8_bytes.push((unicode_code & 0x3F) | 0x80); - } - return utf8_bytes; -} - -// [ 0x3042, 0x3044 ] => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] -function convertUnicodeCodePointsToUtf8Bytes(unicode_codes) { - var utf8_bytes = []; - for (var i = 0; i < unicode_codes.length; ++i) { - var bytes = convertUnicodeCodePointToUtf8Bytes(unicode_codes[i]); - utf8_bytes = utf8_bytes.concat(bytes); - } - return utf8_bytes; -} - -// 0xff => "ff" -// 0xff => "377" -function formatNumber(number, base, num_digits) { - var str = number.toString(base).toUpperCase(); - for (var i = str.length; i < num_digits; ++i) { - str = "0" + str; - } - return str; -} - -var BASE64 = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -function encodeBase64Helper(data) { - var encoded = []; - if (data.length == 1) { - encoded.push(BASE64.charAt(data[0] >> 2)); - encoded.push(BASE64.charAt(((data[0] & 3) << 4))); - encoded.push('='); - encoded.push('='); - } else if (data.length == 2) { - encoded.push(BASE64.charAt(data[0] >> 2)); - encoded.push(BASE64.charAt(((data[0] & 3) << 4) | - (data[1] >> 4))); - encoded.push(BASE64.charAt(((data[1] & 0xF) << 2))); - encoded.push('='); - } else if (data.length == 3) { - encoded.push(BASE64.charAt(data[0] >> 2)); - encoded.push(BASE64.charAt(((data[0] & 3) << 4) | - (data[1] >> 4))); - encoded.push(BASE64.charAt(((data[1] & 0xF) << 2) | - (data[2] >> 6))); - encoded.push(BASE64.charAt(data[2] & 0x3f)); - } - return encoded.join(''); -} - -// "44GC44GE" => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] -function decodeBase64(encoded) { - var decoded_bytes = []; - var data_bytes = []; - for (var i = 0; i < encoded.length; i += 4) { - data_bytes.length = 0; - for (var j = i; j < i + 4; ++j) { - var letter = encoded.charAt(j); - if (letter == "=" || letter == "") { - break; - } - var data_byte = BASE64.indexOf(letter); - if (data_byte >= 64) { // Malformed base64 data. - break; - } - data_bytes.push(data_byte); - } - if (data_bytes.length == 1) { - // Malformed base64 data. - } else if (data_bytes.length == 2) { // 12-bit. - decoded_bytes.push((data_bytes[0] << 2) | (data_bytes[1] >> 4)); - } else if (data_bytes.length == 3) { // 18-bit. - decoded_bytes.push((data_bytes[0] << 2) | (data_bytes[1] >> 4)); - decoded_bytes.push(((data_bytes[1] & 0xF) << 4) | (data_bytes[2] >> 2)); - } else if (data_bytes.length == 4) { // 24-bit. - decoded_bytes.push((data_bytes[0] << 2) | (data_bytes[1] >> 4)); - decoded_bytes.push(((data_bytes[1] & 0xF) << 4) | (data_bytes[2] >> 2)); - decoded_bytes.push(((data_bytes[2] & 0x3) << 6) | (data_bytes[3])); - } - } - return decoded_bytes; -} - -// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => "44GC44GE" -function encodeBase64(data_bytes) { - var encoded = ''; - for (var i = 0; i < data_bytes.length; i += 3) { - var at_most_three_bytes = data_bytes.slice(i, i + 3); - encoded += encodeBase64Helper(at_most_three_bytes); - } - return encoded; -} - -function decodeQuotedPrintableHelper(str, prefix) { - var decoded_bytes = []; - for (var i = 0; i < str.length;) { - if (str.charAt(i) == prefix) { - decoded_bytes.push(parseInt(str.substr(i + 1, 2), 16)); - i += 3; - } else { - decoded_bytes.push(str.charCodeAt(i)); - ++i; - } - } - return decoded_bytes; -} - -// "=E3=81=82=E3=81=84" => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] -function decodeQuotedPrintable(str) { - str = str.replace(/_/g, " ") // RFC 2047. - return decodeQuotedPrintableHelper(str, "="); -} - -// "%E3%81%82%E3%81%84" => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] -function decodeUrl(str) { - return decodeQuotedPrintableHelper(str, "%"); -} - -function encodeQuotedPrintableHelper(data_bytes, prefix, should_escape) { - var encoded = ''; - var prefix_code = prefix.charCodeAt(0); - for (var i = 0; i < data_bytes.length; ++i) { - var data_byte = data_bytes[i]; - if (should_escape(data_byte)) { - encoded += prefix + formatNumber(data_bytes[i], 16, 2); - } else { - encoded += String.fromCharCode(data_byte); - } - } - return encoded; -} - -// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => "=E3=81=82=E3=81=84" -function encodeQuotedPrintable(data_bytes) { - var should_escape = function(b) { - return b < 32 || b > 126 || b == EQUAL_SIGN || b == QUESTION_MARK; - }; - return encodeQuotedPrintableHelper(data_bytes, '=', should_escape); -} - -var URL_SAFE = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-"; - -// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => "%E3%81%82%E3%81%84" -function encodeUrl(data_bytes) { - var should_escape = function(b) { - return URL_SAFE.indexOf(String.fromCharCode(b)) == -1; - }; - return encodeQuotedPrintableHelper(data_bytes, '%', should_escape); -} - -// [ 0x3042, 0x3044 ] => "あい" -function convertUtf16CodesToString(utf16_codes) { - var unescaped = ''; - for (var i = 0; i < utf16_codes.length; ++i) { - unescaped += String.fromCharCode(utf16_codes[i]); - } - return unescaped; -} - -// [ 0x3042, 0x3044 ] => "あい" -function convertUnicodeCodePointsToString(unicode_codes) { - var utf16_codes = convertUnicodeCodePointsToUtf16Codes(unicode_codes); - return convertUtf16CodesToString(utf16_codes); -} - -function maybeInitMaps(encoded_maps, to_unicode_map, from_unicode_map) { - if (to_unicode_map.is_initialized) { - return; - } - var data_types = [ 'ROUNDTRIP', 'INPUT_ONLY', 'OUTPUT_ONLY' ]; - for (var i = 0; i < data_types.length; ++i) { - var data_type = data_types[i]; - var encoded_data = encoded_maps[data_type]; - var data_bytes = decodeBase64(encoded_data); - for (var j = 0; j < data_bytes.length; j += 4) { - var local_code = (data_bytes[j] << 8) | data_bytes[j + 1]; - var unicode_code = (data_bytes[j + 2] << 8) | data_bytes[j + 3]; - if (i == 0 || i == 1) { // ROUNDTRIP or INPUT_ONLY - to_unicode_map[local_code] = unicode_code; - } - if (i == 0 || i == 2) { // ROUNDTRIP or OUTPUT_ONLY - from_unicode_map[unicode_code] = local_code; - } - } - } - to_unicode_map.is_initialized = true; -} - -var SJIS_TO_UNICODE = {} -var UNICODE_TO_SJIS = {} -// Requires: sjis_map.js should be loaded. -function maybeInitSjisMaps() { - maybeInitMaps(SJIS_MAP_ENCODED, SJIS_TO_UNICODE, UNICODE_TO_SJIS); -} - -var ISO88591_TO_UNICODE = {} -var UNICODE_TO_ISO88591 = {} -// Requires: iso88591_map.js should be loaded. -function maybeInitIso88591Maps() { - maybeInitMaps(ISO88591_MAP_ENCODED, ISO88591_TO_UNICODE, - UNICODE_TO_ISO88591); -} - -function lookupMapWithDefault(map, key, default_value) { - var value = map[key]; - if (!value) { - value = default_value; - } - return value; -} - -// [ 0x3042, 0x3044 ] => [ 0x82, 0xA0, 0x82, 0xA2 ] -function convertUnicodeCodePointsToSjisBytes(unicode_codes) { - maybeInitSjisMaps(); - var sjis_bytes = []; - for (var i = 0; i < unicode_codes.length; ++i) { - var unicode_code = unicode_codes[i]; - var sjis_code = lookupMapWithDefault(UNICODE_TO_SJIS, - unicode_code, QUESTION_MARK); - if (sjis_code <= 0xFF) { // 1-byte character. - sjis_bytes.push(sjis_code); - } else { - sjis_bytes.push(sjis_code >> 8); - sjis_bytes.push(sjis_code & 0xFF); - } - } - return sjis_bytes; -} - -// [ 0x3042, 0x3044 ] => [ 0xA4, 0xA2, 0xA4, 0xA4 ] -function convertUnicodeCodePointsToEucJpBytes(unicode_codes) { - maybeInitSjisMaps(); - var eucjp_bytes = []; - for (var i = 0; i < unicode_codes.length; ++i) { - var unicode_code = unicode_codes[i]; - var sjis_code = lookupMapWithDefault(UNICODE_TO_SJIS, unicode_code, - QUESTION_MARK); - if (sjis_code > 0xFF) { // Double byte character. - var jis_code = convertSjisCodeToJisX208Code(sjis_code); - var eucjp_code = jis_code | 0x8080; - eucjp_bytes.push(eucjp_code >> 8); - eucjp_bytes.push(eucjp_code & 0xFF); - } else if (sjis_code >= 0x80) { // 8-bit character. - eucjp_bytes.push(0x8E); - eucjp_bytes.push(sjis_code); - } else { // 7-bit character. - eucjp_bytes.push(sjis_code); - } - } - return eucjp_bytes; -} - - -function convertUnicodeCodePointsToIso88591Bytes(unicode_codes) { - maybeInitIso88591Maps(); - var latin_bytes = []; - for (var i = 0; i < unicode_codes.length; ++i) { - var unicode_code = unicode_codes[i]; - var latin_code = lookupMapWithDefault(UNICODE_TO_ISO88591, - unicode_code, QUESTION_MARK); - latin_bytes.push(latin_code); - } - return latin_bytes; -} - -// [ 0x82, 0xA0, 0x82, 0xA2 ] => [ 0x3042, 0x3044 ] -function convertSjisBytesToUnicodeCodePoints(sjis_bytes) { - maybeInitSjisMaps(); - var unicode_codes = []; - for (var i = 0; i < sjis_bytes.length;) { - var sjis_code = -1; - var sjis_byte = sjis_bytes[i]; - if ((sjis_byte >= 0x81 && sjis_byte <= 0x9F) || - (sjis_byte >= 0xE0 && sjis_byte <= 0xFC)) { - ++i; - var sjis_byte2 = sjis_bytes[i]; - if ((sjis_byte2 >= 0x40 && sjis_byte2 <= 0x7E) || - (sjis_byte2 >= 0x80 && sjis_byte2 <= 0xFC)) { - sjis_code = (sjis_byte << 8) | sjis_byte2; - ++i; - } - } else { - sjis_code = sjis_byte; - ++i; - } - - var unicode_code = lookupMapWithDefault(SJIS_TO_UNICODE, - sjis_code, QUESTION_MARK); - unicode_codes.push(unicode_code); - } - return unicode_codes; -} - -function convertIso88591BytesToUnicodeCodePoints(latin_bytes) { - maybeInitIso88591Maps(); - var unicode_codes = []; - for (var i = 0; i < latin_bytes.length; ++i) { - var latin_code = latin_bytes[i]; - var unicode_code = lookupMapWithDefault(ISO88591_TO_UNICODE, - latin_code, QUESTION_MARK); - unicode_codes.push(unicode_code); - } - return unicode_codes; -} - -// 0x2422 => 0x82a0 -function convertJisX208CodeToSjisCode(jis_code) { - var j1 = jis_code >> 8; - var j2 = jis_code & 0xFF; - // http://people.debian.org/~kubota/unicode-symbols-map2.html.ja - var s1 = ((j1 - 1) >> 1) + ((j1 <= 0x5E) ? 0x71 : 0xB1); - var s2 = j2 + ((j1 & 1) ? ((j2 < 0x60) ? 0x1F : 0x20) : 0x7E); - return (s1 << 8) | s2; -} - -// 0x82a0 => 0x2422 -function convertSjisCodeToJisX208Code(sjis_code) { - var s1 = sjis_code >> 8; - var s2 = sjis_code & 0xFF; - // http://people.debian.org/~kubota/unicode-symbols-map2.html.ja - var j1 = (s1 << 1) - (s1 <= 0x9f ? 0xe0 : 0x160) - (s2 < 0x9f ? 1 : 0); - var j2 = s2 - 0x1f - (s2 >= 0x7f ? 1 : 0) - (s2 >= 0x9f ? 0x5e : 0); - return (j1 << 8) | j2; -} - -// [ 0x24, 0x22, 0x24, 0x24 ] => [ 0x82, 0xA0, 0x82, 0xA2 ] -function convertJisX208BytesToSjisBytes(jis_bytes) { - var sjis_bytes = []; - for (var i = 0; i < jis_bytes.length; i += 2) { - var jis_code = (jis_bytes[i] << 8) | jis_bytes[i + 1]; - var sjis_code = convertJisX208CodeToSjisCode(jis_code); - sjis_bytes.push(sjis_code >> 8); - sjis_bytes.push(sjis_code & 0xFF); - } - return sjis_bytes; -} - -// [ 0x82, 0xA0, 0x82, 0xA2 ] => [ 0x24, 0x22, 0x24, 0x24 ] -function convertSjisBytesToJisX208Bytes(sjis_bytes) { - var jis_bytes = []; - for (var i = 0; i < sjis_bytes.length; i += 2) { - var sjis_code = (sjis_bytes[i] << 8) | sjis_bytes[i + 1]; - var jis_code = convertSjisCodeToJisX208Code(sjis_code); - jis_bytes.push(jis_code >> 8); - jis_bytes.push(jis_code & 0xFF); - } - return jis_bytes; -} - -// Constants used in convertJisBytesToUnicodeCodePoints(). -var ASCII = 0; -var JISX201 = 1; -var JISX208 = 2; - -// Map used in convertIso2022JpBytesToUnicodeCodePoints(). -var ESCAPE_SEQUENCE_TO_MODE = { - "(B": ASCII, - "(J": JISX201, - "$B": JISX208, - "$@": JISX208 -}; - -// Map used in convertUnicodeCodePointsToIso2022JpBytes(). -var MODE_TO_ESCAPE_SEQUENCE = {} -MODE_TO_ESCAPE_SEQUENCE[ASCII] = "(B"; -MODE_TO_ESCAPE_SEQUENCE[JISX201] = "(J"; -MODE_TO_ESCAPE_SEQUENCE[JISX208] = "$B"; - -// [ 0x1B, 0x24, 0x42, 0x24, 0x22, 0x1B, 0x28, 0x42, ] => [ 0x3042 ] -function convertIso2022JpBytesToUnicodeCodePoints(iso2022jp_bytes) { - maybeInitSjisMaps(); - var flush = function(mode, data_bytes, output) { - var unicode_codes = []; - if (mode == ASCII) { - unicode_codes = data_bytes; - } else if (mode == JISX201) { // Might have half-width Katakana? - unicode_codes = convertSjisBytesToUnicodeCodePoints(data_bytes); - } else if (mode == JISX208) { - var sjis_bytes = convertJisX208BytesToSjisBytes(data_bytes); - unicode_codes = convertSjisBytesToUnicodeCodePoints(sjis_bytes); - } else { // Unknown mode - } - for (var i = 0; i < unicode_codes.length; ++i) { - output.push(unicode_codes[i]); - } - data_bytes.length = 0; // Clear. - } - - var unicode_codes = []; - var mode = ASCII; - var current_data_bytes = []; - for (var i = 0; i < iso2022jp_bytes.length;) { - if (iso2022jp_bytes[i] == 0x1B) { // Mode is changed. - flush(mode, current_data_bytes, unicode_codes); - ++i; - var code = String.fromCharCode(iso2022jp_bytes[i], - iso2022jp_bytes[i + 1]); - mode = ESCAPE_SEQUENCE_TO_MODE[code]; - if (!mode) { // Unknown mode. - mode = ASCII; - } - i += 2; - } else { - current_data_bytes.push(iso2022jp_bytes[i]); - ++i; - } - } - flush(mode, current_data_bytes, unicode_codes); - return unicode_codes; -} - -// [ 0xA4, 0xA2, 0xA4, 0xA4 ] => [ 0x3042, 0x3044 ] -function convertEucJpBytesToUnicodeCodePoints(eucjp_bytes) { - maybeInitSjisMaps(); - var unicode_codes = []; - for (var i = 0; i < eucjp_bytes.length;) { - if (eucjp_bytes[i] >= 0x80 && (i + 1) < eucjp_bytes.length && - eucjp_bytes[i + 1] >= 0x80) { - var eucjp_code = (eucjp_bytes[i] << 8) | eucjp_bytes[i + 1]; - var jis_code = eucjp_code & 0x7F7F; - var sjis_code = convertJisX208CodeToSjisCode(jis_code); - var unicode_code = lookupMapWithDefault(SJIS_TO_UNICODE, - sjis_code, QUESTION_MARK); - unicode_codes.push(unicode_code); - i += 2; - } else { - if (eucjp_bytes[i] < 0x80) { - unicode_codes.push(eucjp_bytes[i]); - } else { - // Ignore singleton 8-bit byte. - } - ++i; - } - } - return unicode_codes; -} - -// [ 0x3042 ] => [ 0x1B, 0x24, 0x42, 0x24, 0x22, 0x1B, 0x28, 0x42, ] -function convertUnicodeCodePointsToIso2022JpBytes(unicode_codes) { - maybeInitSjisMaps(); - var mode = ASCII; - var maybeChangeMode = function(new_mode) { - if (mode != new_mode) { - mode = new_mode; - var esc_as_string = MODE_TO_ESCAPE_SEQUENCE[mode]; - var esc_as_code_points = convertStringToUnicodeCodePoints(esc_as_string); - iso2022jp_bytes.push(0x1B); // ESC code. - iso2022jp_bytes = iso2022jp_bytes.concat(esc_as_code_points); - } - } - var iso2022jp_bytes = []; - for (var i = 0; i < unicode_codes.length; ++i) { - var unicode_code = unicode_codes[i]; - var sjis_code = lookupMapWithDefault(UNICODE_TO_SJIS, unicode_code, - QUESTION_MARK); - if (sjis_code > 0xFF) { // Double byte character. - var jis_code = convertSjisCodeToJisX208Code(sjis_code); - maybeChangeMode(JISX208); - iso2022jp_bytes.push(jis_code >> 8); - iso2022jp_bytes.push(jis_code & 0xFF); - } else if (sjis_code >= 0x80) { // 8-bit character. - maybeChangeMode(JISX201); - iso2022jp_bytes.push(sjis_code); - } else { // 7-bit character. - maybeChangeMode(ASCII); - iso2022jp_bytes.push(sjis_code); - } - } - maybeChangeMode(ASCII); - return iso2022jp_bytes; -} - -var MIME_FULL_MATCH = /^=\?([^?]+)\?([BQ])\?([^?]+)\?=$/; -var MIME_PARTIAL_MATCH = /^=\?([^?]+)\?([BQ])\?([^?]+)\?=/; - -// "=?UTF-8?B?44GC?=" => true -// "foobar" => false -function isMimeEncodedString(str) { - return str.match(MIME_FULL_MATCH) != null; -} - -// "=?UTF-8?B?44GC?=" => ["UTF-8", [0xE3, 0x81, 0x82]] -// "=?UTF-8?Q?=E3=81=82?=" => ["UTF-8", [0xE3, 0x81, 0x82]] -// "INVALID" => [] -function decodeMime(str) { - var m = str.match(MIME_FULL_MATCH); - if (m) { - var char_encoding = m[1]; - // We don't need the language information preceded by '*'. - char_encoding = char_encoding.replace(/\*.*$/, "") - var mime_encoding = m[2]; - var mime_str = m[3]; - var decoded_bytes; - if (mime_encoding == "B") { - decoded_bytes = decodeBase64(mime_str); - } else if (mime_encoding == "Q") { - decoded_bytes = decodeQuotedPrintable(mime_str); - } - if (char_encoding != "" && decoded_bytes) { - return [char_encoding, decoded_bytes] - } - } - return []; -} - -var OUTPUT_CONVERTERS = { - 'ISO2022JP': convertUnicodeCodePointsToIso2022JpBytes, - 'ISO88591': convertUnicodeCodePointsToIso88591Bytes, - 'SHIFTJIS': convertUnicodeCodePointsToSjisBytes, - 'EUCJP': convertUnicodeCodePointsToEucJpBytes, - 'UTF8': convertUnicodeCodePointsToUtf8Bytes -} - -var INPUT_CONVERTERS = { - 'ISO2022JP': convertIso2022JpBytesToUnicodeCodePoints, - 'ISO88591': convertIso88591BytesToUnicodeCodePoints, - 'SHIFTJIS': convertSjisBytesToUnicodeCodePoints, - 'EUCJP': convertEucJpBytesToUnicodeCodePoints, - 'UTF8': convertUtf8BytesToUnicodeCodePoints -} - -function convertUnicodeCodePointsToBytes(unicode_codes, encoding) { - var normalized_encoding = normalizeEncodingName(encoding); - var convert_function = OUTPUT_CONVERTERS[normalized_encoding]; - if (convert_function) { - return convert_function(unicode_codes); - } - return []; -} - -function convertBytesToUnicodeCodePoints(data_bytes, encoding) { - var normalized_encoding = normalizeEncodingName(encoding); - var convert_function = INPUT_CONVERTERS[normalized_encoding]; - if (convert_function) { - return convert_function(data_bytes); - } - return []; -} - -// 'あい' => r'\u3042\u3044' -function escapeToUtf16(str) { - var escaped = '' - for (var i = 0; i < str.length; ++i) { - var hex = str.charCodeAt(i).toString(16).toUpperCase(); - escaped += "\\u" + "0000".substr(hex.length) + hex; - } - return escaped; -} - -// 'あい' => r'\U00003042\U00003044' -function escapeToUtf32(str) { - var escaped = '' - var unicode_codes = convertStringToUnicodeCodePoints(str); - for (var i = 0; i "あい" -// "あい" => "あい" -function escapeToNumRef(str, base) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var escaped = '' - var prefix = base == 10 ? '' : 'x'; - for (var i = 0; i < unicode_codes.length; ++i) { - var code = unicode_codes[i].toString(base).toUpperCase(); - var num_ref = "&#" + prefix + code + ";" - escaped += num_ref; - } - return escaped; -} - -// "あい" => "l8je" -function escapeToPunyCode(str) { - var unicode_codes = convertStringToPunyCodes(str); - return convertUnicodeCodePointsToString(unicode_codes); -} - -// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => '\xE3\x81\x82\xE3\x81\x84' -// [ 0343, 0201, 0202, 0343, 0201, 0204 ] => '\343\201\202\343\201\204' -function convertBytesToEscapedString(data_bytes, base) { - var escaped = ''; - for (var i = 0; i < data_bytes.length; ++i) { - var prefix = (base == 16 ? "\\x" : "\\"); - var num_digits = base == 16 ? 2 : 3; - var escaped_byte = prefix + formatNumber(data_bytes[i], base, num_digits) - escaped += escaped_byte; - } - return escaped; -} - -// "あい" => [0x6C, 0x38, 0x6A, 0x65] // "l8je" -// Requires: punycode.js should be loaded. -function convertStringToPunyCodes(str) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var puny_codes = []; - var result = ""; - if (PunyCode.encode(unicode_codes, puny_codes)) { - return puny_codes; - } - return unicode_codes; -} - -// [ 0x6C, 0x38, 0x6A, 0x65 ] => "あい" -// Requires: punycode.js should be loaded. -function convertPunyCodesToString(puny_codes) { - var unicode_codes = []; - if (PunyCode.decode(puny_codes, unicode_codes)) { - return convertUnicodeCodePointsToString(unicode_codes); - } - return convertUnicodeCodePointsToString(puny_codes); -} - -// "あい" => r'\xE3\x81\x82\xE3\x81\x84' // UTF-8 -// "あい" => r'\343\201\202\343\201\204' // UTF-8 -function escapeToEscapedBytes(str, base, encoding) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes, encoding); - return convertBytesToEscapedString(data_bytes, base); -} - -// "あい" => "44GC44GE" // UTF-8 -function escapeToBase64(str, encoding) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes, encoding); - return encodeBase64(data_bytes); -} - -// "あい" => "=E3=81=82=E3=81=84" // UTF-8 -function escapeToQuotedPrintable(str, encoding) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes, encoding); - return encodeQuotedPrintable(data_bytes); -} - -// "あい" => "%E3%81%82%E3%81%84" -function escapeToUrl(str, encoding) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes, encoding); - return encodeUrl(data_bytes); -} - -// "あい" => "=?UTF-8?B?44GC44GE?=" -// "あい" => "=?UTF-8?Q?=E3=81=82=E3=81=84?=" -function escapeToMime(str, mime_encoding, char_encoding) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes, - char_encoding); - if (str == "") { - return ""; - } - var escaped = "=?" + char_encoding + "?"; - if (mime_encoding == 'base64') { - escaped += "B?"; - escaped += encodeBase64(data_bytes); - } else { - escaped += "Q?"; - escaped += encodeQuotedPrintable(data_bytes); - } - escaped += '?='; - return escaped; -} - -// r'\u3042\u3044 => "あい" -function unescapeFromUtf16(str) { - var utf16_codes = convertEscapedUtf16CodesToUtf16Codes(str); - return convertUtf16CodesToString(utf16_codes); -} - -// r'\U00003042\U00003044 => "あい" -function unescapeFromUtf32(str) { - var unicode_codes = convertEscapedUtf32CodesToUnicodeCodePoints(str); - var utf16_codes = convertUnicodeCodePointsToUtf16Codes(unicode_codes); - return convertUtf16CodesToString(utf16_codes); -} - -// r'\xE3\x81\x82\xE3\x81\x84' => "あい" -// r'\343\201\202\343\201\204' => "あい" -function unescapeFromEscapedBytes(str, base, encoding) { - var data_bytes = convertEscapedBytesToBytes(str, base); - var unicode_codes = convertBytesToUnicodeCodePoints(data_bytes, encoding); - return convertUnicodeCodePointsToString(unicode_codes); -} - - -// "あい" => "あい" -// "あい" => "あい" -function unescapeFromNumRef(str, base) { - var unicode_codes = convertNumRefToUnicodeCodePoints(str, base); - return convertUnicodeCodePointsToString(unicode_codes); -} - -// "l8je" => "あい" -function unescapeFromPunyCode(str) { - var unicode_codes = convertStringToUnicodeCodePoints(str); - return convertPunyCodesToString(unicode_codes); -} - -// "44GC44GE" => "あい" -function unescapeFromBase64(str, encoding) { - var decoded_bytes = decodeBase64(str); - var unicode_codes = convertBytesToUnicodeCodePoints(decoded_bytes, encoding); - return convertUnicodeCodePointsToString(unicode_codes); -} - -// "=E3=81=82=E3=81=84" => "あい" -function unescapeFromQuotedPrintable(str, encoding) { - var decoded_bytes = decodeQuotedPrintable(str); - var unicode_bytes = convertBytesToUnicodeCodePoints(decoded_bytes, encoding); - return convertUnicodeCodePointsToString(unicode_bytes); -} - -// "%E3%81%82%E3%81%84" => "あい" -function unescapeFromUrl(str, encoding) { - var decoded_bytes = decodeUrl(str); - var unicode_bytes = convertBytesToUnicodeCodePoints(decoded_bytes, encoding); - return convertUnicodeCodePointsToString(unicode_bytes); -} - -// " " => true -// " \n" => true -function isEmptyOrSequenceOfWhiteSpaces(str) { - for (var i = 0; i < str.length; ++i) { - var code = str.charCodeAt(i); - if (!(code == 0x09 || // TAB - code == 0x0A || // LF - code == 0x0D || // CR - code == 0x20)) { // SPACE - return false; - } - } - return true; -} - -// "=?UTF-8?B?*?= =?UTF-8?B?*?=" => ["=?UTF-8?B?*?=", "=?UTF-8?B?*?="] -// "=?UTF-8?B?*?=FOO" => ["=?UTF-8?B?*?=", "FOO"] -function splitMimeString(str) { - var parts = []; - var current = ""; - while (str != "") { - var m = str.match(MIME_PARTIAL_MATCH) - if (m) { - if (!isEmptyOrSequenceOfWhiteSpaces(current)) { - parts.push(current); - } - current = ""; - parts.push(m[0]); - str = str.substr(m[0].length); - } else { - current += str.charAt(0); - str = str.substr(1); - } - } - if (!isEmptyOrSequenceOfWhiteSpaces(current)) { - parts.push(current); - } - return parts; -} - -// "UTF-8" => "UTF8" -// "Shift_JIS" => "SHIFTJIS" -function normalizeEncodingName(encoding) { - return encoding.toUpperCase().replace(/[_-]/g, ""); -} - -// "=?UTF-8?B?44GC44GE?=" => "あい" -// "=?Shift_JIS?B?gqCCog==?=" => "あい" -// "=?ISO-2022-JP?B?GyRCJCIkJBsoQg==?=" => "あい" -// "=?UTF-8?Q?=E3=81=82=E3=81=84?=" => "あい" -// "=?Shift_JIS?Q?=82=A0=82=A2?=" => "あい" -// "=?ISO-2022-JP?Q?=1B$B$"$$=1B(B?=" => "あい" -function unescapeFromMime(str) { - var parts = splitMimeString(str); - var unescaped = ""; - for (var i = 0; i < parts.length; ++i) { - if (isMimeEncodedString(parts[i])) { - var pair = decodeMime(parts[i]); - if (pair.length == 0) { // Malformed MIME string. Skip it. - continue; - } - var encoding = normalizeEncodingName(pair[0]); - var data_bytes = pair[1]; - var unicode_codes = convertBytesToUnicodeCodePoints(data_bytes, - encoding); - unescaped += convertUnicodeCodePointsToString(unicode_codes); - } else { - unescaped += parts[i]; - } - } - return unescaped; -} diff --git a/MailHog-UI/assets/templates/index.html b/MailHog-UI/assets/templates/index.html deleted file mode 100644 index 332c3b6..0000000 --- a/MailHog-UI/assets/templates/index.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - -
-
- - - - - - - - - - - - - - - - - - - -
FromToSubjectReceivedActions
- {{ message.From.Mailbox }}@{{ message.From.Domain }} - - - {{ to.Mailbox }}@{{ to.Domain }} - - - {{ tryDecodeMime(message.Content.Headers["Subject"][0]) }} - - {{ date(message.Created) }} - - - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
From{{ tryDecodeMime(preview.Content.Headers["From"][0]) }}
Subject{{ tryDecodeMime(preview.Content.Headers["Subject"][0]) }}
To - - {{ tryDecodeMime(preview.Content.Headers["To"].join(', ')) }} -
- {{ tryDecodeMime(header) }} - - -
{{ v }}
-
-
- -
-
-
{{ getMessagePlain(preview) }}
-
{{ getSource(preview) }}
-
-
- - - Download - - {{ part.Headers["Content-Type"][0] || "Unknown type" }} ({{ part.Size }} bytes) -
-
-
-
-
diff --git a/MailHog-UI/assets/templates/layout.html b/MailHog-UI/assets/templates/layout.html deleted file mode 100644 index 2c24135..0000000 --- a/MailHog-UI/assets/templates/layout.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - MailHog - - - - - - - - - - - - - - [: .Content :] - - diff --git a/MailHog-UI/main.go b/MailHog-UI/main.go deleted file mode 100644 index de7b396..0000000 --- a/MailHog-UI/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "flag" - "os" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" - "github.com/ian-kent/Go-MailHog/MailHog-UI/assets" - "github.com/ian-kent/Go-MailHog/MailHog-UI/web" - "github.com/ian-kent/Go-MailHog/http" - "github.com/ian-kent/go-log/log" - gotcha "github.com/ian-kent/gotcha/app" -) - -var conf *config.Config -var exitCh chan int - -func configure() { - config.RegisterFlags() - flag.Parse() - conf = config.Configure() -} - -func main() { - configure() - - // FIXME need to make API URL configurable - - exitCh = make(chan int) - cb := func(app *gotcha.App) { - web.CreateWeb(conf, app) - } - go http.Listen(conf, assets.Asset, exitCh, cb) - - for { - select { - case <-exitCh: - log.Printf("Received exit signal") - os.Exit(0) - } - } -} diff --git a/MailHog-UI/web/web.go b/MailHog-UI/web/web.go deleted file mode 100644 index dad7af0..0000000 --- a/MailHog-UI/web/web.go +++ /dev/null @@ -1,44 +0,0 @@ -package web - -import ( - "html/template" - - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" - gotcha "github.com/ian-kent/gotcha/app" - "github.com/ian-kent/gotcha/events" - "github.com/ian-kent/gotcha/http" -) - -type Web struct { - config *config.Config - app *gotcha.App -} - -func CreateWeb(cfg *config.Config, app *gotcha.App) *Web { - app.On(events.BeforeHandler, func(session *http.Session, next func()) { - session.Stash["config"] = cfg - next() - }) - - r := app.Router - - r.Get("/images/(?P.*)", r.Static("assets/images/{{file}}")) - r.Get("/js/(?P.*)", r.Static("assets/js/{{file}}")) - r.Get("/", Index) - - app.Config.LeftDelim = "[:" - app.Config.RightDelim = ":]" - - return &Web{ - config: cfg, - app: app, - } -} - -func Index(session *http.Session) { - html, _ := session.RenderTemplate("index.html") - - session.Stash["Page"] = "Browse" - session.Stash["Content"] = template.HTML(html) - session.Render("layout.html") -} diff --git a/MailHog/main.go b/MailHog/main.go index cf3cd79..d407c76 100644 --- a/MailHog/main.go +++ b/MailHog/main.go @@ -4,14 +4,14 @@ import ( "flag" "os" - "github.com/ian-kent/Go-MailHog/MailHog-Server/api" - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" - "github.com/ian-kent/Go-MailHog/MailHog-Server/smtp" - "github.com/ian-kent/Go-MailHog/MailHog-UI/assets" - "github.com/ian-kent/Go-MailHog/MailHog-UI/web" "github.com/ian-kent/Go-MailHog/http" "github.com/ian-kent/go-log/log" gotcha "github.com/ian-kent/gotcha/app" + "github.com/mailhog/MailHog-Server/api" + "github.com/mailhog/MailHog-Server/config" + "github.com/mailhog/MailHog-Server/smtp" + "github.com/mailhog/MailHog-UI/assets" + "github.com/mailhog/MailHog-UI/web" ) var conf *config.Config diff --git a/Makefile b/Makefile index 455a8c2..23fedc8 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,10 @@ DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...) -all: deps bindata fmt combined +all: deps fmt combined combined: go install ./MailHog -server: - go install ./MailHog-Server - -ui: - go install ./MailHog-UI - -bindata: - go-bindata -o MailHog-UI/assets/assets.go -pkg assets -prefix MailHog-UI/ MailHog-UI/assets/... - release: release-deps gox -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" ./MailHog @@ -21,6 +12,8 @@ fmt: go fmt ./... deps: + go get github.com/mailhog/MailHog-Server + go get github.com/mailhog/MailHog-UI go get github.com/ian-kent/gotcha/gotcha go get github.com/ian-kent/go-log/log go get github.com/ian-kent/envconf @@ -38,4 +31,4 @@ test-deps: release-deps: go get github.com/mitchellh/gox -.PNONY: all combined server ui bindata release fmt test-deps release-deps +.PNONY: all combined release fmt deps test-deps release-deps diff --git a/http/server.go b/http/server.go index 47e3881..e1606b4 100644 --- a/http/server.go +++ b/http/server.go @@ -1,9 +1,9 @@ package http import ( - "github.com/ian-kent/Go-MailHog/MailHog-Server/config" "github.com/ian-kent/go-log/log" gotcha "github.com/ian-kent/gotcha/app" + "github.com/mailhog/MailHog-Server/config" ) func Listen(cfg *config.Config, Asset func(string) ([]byte, error), exitCh chan int, registerCallback func(*gotcha.App)) {