This is the main dovel repository, it has the Go code to run dovel SMTP server.
Author: blmayer (bleemayer@gmail.com)
Date: Sat Apr 22 22:06:17 2023 -0300
Parent: 9c123fb
Refactored backend and handlers - Also did a small style on pages
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/cmd/dovel/backend.go b/cmd/dovel/backend.go
index 1a07252..cd42039 100644
--- a/cmd/dovel/backend.go
+++ b/cmd/dovel/backend.go
@@ -6,7 +6,6 @@ import (
"io"
"strings"
- "blmayer.dev/x/dovel/config"
"blmayer.dev/x/dovel/interfaces"
"github.com/OfimaticSRL/parsemail"
"github.com/emersion/go-smtp"
@@ -15,7 +14,7 @@ import (
// A Session is returned after EHLO.
type Session struct {
User string
- handlers map[string]config.Handler
+ handlers map[string]interfaces.Mailer
}
func (s Session) AuthPlain(username, password string) error {
@@ -57,7 +56,7 @@ func (s Session) Data(r io.Reader) error {
return fmt.Errorf("no handler for domain %s", userDomain)
}
mail.To = []string{to}
- if err := handler(mail); err != nil {
+ if err := handler.Save(mail); err != nil {
println("handler error", err.Error())
return fmt.Errorf("handler error %s", err.Error())
}
@@ -74,7 +73,7 @@ func (s Session) Logout() error {
}
type backend struct {
- Handlers map[string]config.Handler
+ Handlers map[string]interfaces.Mailer
PrivateKey crypto.Signer
}
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/cmd/dovel/main.go b/cmd/dovel/main.go
index 9c4edc1..6f37484 100644
--- a/cmd/dovel/main.go
+++ b/cmd/dovel/main.go
@@ -6,9 +6,11 @@ import (
"net/http"
"os"
"path"
+ "html/template"
"time"
"blmayer.dev/x/dovel/config"
+ "blmayer.dev/x/dovel/interfaces"
"blmayer.dev/x/dovel/interfaces/file"
"blmayer.dev/x/dovel/interfaces/gwi"
@@ -34,7 +36,7 @@ var (
},
}
cfg config.Config
- b = backend{Handlers: map[string]config.Handler{}}
+ b = backend{Handlers: map[string]interfaces.Mailer{}}
)
func main() {
@@ -60,7 +62,7 @@ func main() {
panic(err)
}
- b.Handlers[hand.Domain] = g.Save
+ b.Handlers[hand.Domain] = g
case "file":
v, err := NewPlainTextVault(path.Join(hand.Root, "users.json"))
if err != nil {
@@ -72,13 +74,22 @@ func main() {
panic(err)
}
+ web := webHandler{
+ assetsPath: hand.Templates,
+ vault: v,
+ mailer: mail,
+ }
if hand.Templates != "" {
- http.HandleFunc(hand.Domain+"/", mail.IndexHandler())
- http.HandleFunc(hand.Domain+"/assets/", mail.AssetsHandler())
- http.HandleFunc(hand.Domain+"/out", SendHandler(mail, v))
+ web.templates, err = template.ParseGlob(hand.Templates + "/*.html")
+ if err != nil {
+ panic(err)
+ }
+ http.HandleFunc(hand.Domain+"/", web.indexHandler())
+ http.HandleFunc(hand.Domain+"/assets/", web.assetsHandler())
+ http.HandleFunc(hand.Domain+"/out", web.sendHandler())
}
- b.Handlers[hand.Domain] = mail.Save
+ b.Handlers[hand.Domain] = mail
}
println("added handler " + hand.Domain + " as " + hand.Handler)
}
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/cmd/dovel/web.go b/cmd/dovel/web.go
index cdc925f..d7bb4b7 100644
--- a/cmd/dovel/web.go
+++ b/cmd/dovel/web.go
@@ -1,18 +1,63 @@
package main
import (
+ "html/template"
"io"
"net/http"
+ "net/url"
+ "path"
"time"
"blmayer.dev/x/dovel/interfaces"
)
-func SendHandler(mailer interfaces.Mailer, vault interfaces.Vault) http.HandlerFunc {
+type webHandler struct {
+ assetsPath string
+ templates *template.Template
+ vault interfaces.Vault
+ mailer interfaces.Mailer
+}
+
+func (h webHandler) indexHandler() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ user, pass, _ := r.BasicAuth()
+ if user == "" || !h.vault.Validate(user, pass) {
+ w.Header().Add("WWW-Authenticate", "Basic")
+ http.Error(w, "wrong auth", http.StatusUnauthorized)
+ return
+ }
+
+ if r.URL.Path == "/" {
+ r.URL.Path = "/index.html"
+ }
+
+ err := h.templates.ExecuteTemplate(
+ w, r.URL.Path[1:],
+ struct {
+ Query url.Values
+ Mailer interfaces.Mailer
+ }{
+ r.URL.Query(),
+ h.mailer,
+ },
+ )
+ if err != nil {
+ println("execute", err.Error())
+ }
+ }
+}
+
+func (h webHandler) assetsHandler() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, path.Join(h.assetsPath, r.URL.Path[1:]))
+ }
+}
+
+func (h webHandler) sendHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
println("file handling send email")
user, pass, _ := r.BasicAuth()
- if user == "" || !vault.Validate(user, pass) {
+ if user == "" || !h.vault.Validate(user, pass) {
w.Header().Add("WWW-Authenticate", "Basic")
http.Error(w, "wrong auth", http.StatusUnauthorized)
return
@@ -54,8 +99,8 @@ func SendHandler(mailer interfaces.Mailer, vault interfaces.Vault) http.HandlerF
// TODO: Add content-type
}
}
-
- err := mailer.Send(email)
+
+ err := h.mailer.Send(email)
if err != nil {
println("send error: " + err.Error())
http.Error(w, "send error"+err.Error(), http.StatusInternalServerError)
@@ -64,4 +109,3 @@ func SendHandler(mailer interfaces.Mailer, vault interfaces.Vault) http.HandlerF
http.Redirect(w, r, "/", http.StatusFound)
}
}
-
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/config/config.go b/config/config.go
index f3c21ab..0e23958 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,13 +1,5 @@
package config
-import (
- "blmayer.dev/x/dovel/interfaces"
-)
-
-// Handler is the way to add custom logic to the email handlers, after an
-// email is received and saved all handlers are run
-type Handler func(email interfaces.Email) error
-
// Config is the configuration structure used to set up dovel
// server and web interface. This should be a JSON file located
// in $HOME/.dovel-config.json
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/interfaces/file/file.go b/interfaces/file/file.go
index a987c0f..728b8f4 100644
--- a/interfaces/file/file.go
+++ b/interfaces/file/file.go
@@ -10,12 +10,10 @@ import (
"encoding/base64"
"encoding/pem"
"fmt"
- "html/template"
"io"
"io/ioutil"
"mime/multipart"
"net"
- "net/http"
"net/smtp"
"os"
"path"
@@ -37,9 +35,7 @@ import (
// filter and separate emails, only emails sent to one of your domains are
// saved, each according to its configuration.
type FileHandler struct {
- templates *template.Template
root string
- assetsPath string
domain string
privateKey crypto.Signer
vault interfaces.Vault
@@ -55,12 +51,6 @@ func NewFileHandler(c config.InboxConfig, v interfaces.Vault, fs map[string]any)
fs["mail"] = f.Mail
var err error
- if c.Templates != "" {
- f.assetsPath = c.Templates
- f.templates, err = template.New(c.Root).Funcs(fs).
- ParseGlob(c.Templates + "/*.html")
- }
-
if c.DKIMKeyPath != "" {
key, err := ioutil.ReadFile(c.DKIMKeyPath)
if err != nil {
@@ -81,34 +71,6 @@ func NewFileHandler(c config.InboxConfig, v interfaces.Vault, fs map[string]any)
return f, err
}
-func (f FileHandler) IndexHandler() http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- user, pass, _ := r.BasicAuth()
- if user == "" || !f.vault.Validate(user, pass) {
- w.Header().Add("WWW-Authenticate", "Basic")
- http.Error(w, "wrong auth", http.StatusUnauthorized)
- return
- }
-
- if r.URL.Path == "/" {
- r.URL.Path = "/index.html"
- }
-
- err := f.templates.ExecuteTemplate(
- w, r.URL.Path[1:],
- r.URL.Query(),
- )
- if err != nil {
- println("execute", err.Error())
- }
- }
-}
-
-func (f FileHandler) AssetsHandler() http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- http.ServeFile(w, r, path.Join(f.assetsPath, r.URL.Path[1:]))
- }
-}
func (f FileHandler) Save(email interfaces.Email) error {
mailDir := path.Join(f.root, email.To[0], email.Subject)
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/interfaces/gwi/gwi.go b/interfaces/gwi/gwi.go
index c945455..87ab1ab 100644
--- a/interfaces/gwi/gwi.go
+++ b/interfaces/gwi/gwi.go
@@ -5,19 +5,23 @@ import (
"bytes"
"crypto"
"crypto/x509"
+ "encoding/base64"
"encoding/pem"
"fmt"
+ "io"
"io/ioutil"
"mime/multipart"
"net"
"net/smtp"
"os"
"path"
+ "sort"
"strings"
"time"
"blmayer.dev/x/dovel/config"
"blmayer.dev/x/dovel/interfaces"
+ "github.com/OfimaticSRL/parsemail"
"github.com/emersion/go-msgauth/dkim"
)
@@ -27,7 +31,7 @@ import (
// key of the map specifies a trigger in the form "key!". That is, if the email
// body starts with key! then the command key is run.
type GWIHandler struct {
- Root string
+ root string
Commands map[string]func(email interfaces.Email) error
domain string
privateKey crypto.Signer
@@ -35,7 +39,7 @@ type GWIHandler struct {
}
func NewGWIHandler(c config.InboxConfig, vault interfaces.Vault) (GWIHandler, error) {
- g := GWIHandler{Root: c.Root, domain: c.Domain, vault: vault}
+ g := GWIHandler{root: c.Root, domain: c.Domain, vault: vault}
if c.DKIMKeyPath != "" {
key, err := ioutil.ReadFile(c.DKIMKeyPath)
if err != nil {
@@ -66,7 +70,7 @@ func (g GWIHandler) Save(email interfaces.Email) error {
}
user, repo := userRepo[0], userRepo[1]
- if _, err := os.Stat(path.Join(g.Root, user, repo)); err != nil {
+ if _, err := os.Stat(path.Join(g.root, user, repo)); err != nil {
println("stat repo", err.Error())
return err
}
@@ -79,7 +83,7 @@ func (g GWIHandler) Save(email interfaces.Email) error {
}
title := strings.TrimSpace(email.Subject[start:])
- mailDir := path.Join(g.Root, user, repo, "mail", title)
+ mailDir := path.Join(g.root, user, repo, "mail", title)
os.MkdirAll(mailDir, os.ModeDir|0o700)
mailFile, err := os.Create(path.Join(mailDir, email.ID))
@@ -213,3 +217,121 @@ func (g GWIHandler) Send(mail interfaces.Email) error {
}
return nil
}
+
+func (f GWIHandler) Mailboxes(folder string) ([]interfaces.Mailbox, error) {
+ fmt.Println("mailer mailboxes for", folder)
+ dir, err := os.ReadDir(path.Join(f.root, folder))
+ if err != nil {
+ fmt.Println("readDir error:", err.Error())
+ return nil, err
+ }
+
+ var threads []interfaces.Mailbox
+ for _, d := range dir {
+ if !d.IsDir() {
+ continue
+ }
+
+ info, err := d.Info()
+ if err != nil {
+ fmt.Println("dir info", err.Error())
+ continue
+ }
+ t := interfaces.Mailbox{
+ Title: d.Name(),
+ LastMod: info.ModTime(),
+ }
+
+ threads = append(threads, t)
+ }
+ sort.Slice(
+ threads,
+ func(i, j int) bool {
+ return threads[i].LastMod.After(threads[j].LastMod)
+ },
+ )
+
+ return threads, nil
+}
+
+func (f GWIHandler) Mails(folder string) ([]interfaces.Email, error) {
+ fmt.Println("mailer mails for", folder)
+
+ dir := path.Join(f.root, folder)
+ threadDir, err := os.ReadDir(dir)
+ if err != nil {
+ fmt.Println("readDir error:", err.Error())
+ return nil, err
+ }
+
+ var emails []interfaces.Email
+ for _, t := range threadDir {
+ mail, err := f.Mail(path.Join(folder, t.Name()))
+ if err != nil {
+ fmt.Println("mail error:", err.Error())
+ continue
+ }
+
+ emails = append(emails, mail)
+ }
+ sort.Slice(
+ emails,
+ func(i, j int) bool {
+ return emails[i].Date.Before(emails[j].Date)
+ },
+ )
+ fmt.Println("found", len(emails), "emails")
+
+ return emails, err
+}
+
+func (g GWIHandler) Mail(file string) (interfaces.Email, error) {
+ fmt.Println("mailer getting", file)
+
+ mailFile, err := os.Open(path.Join(g.root, file))
+ if err != nil {
+ fmt.Println("open mail error:", err.Error())
+ return interfaces.Email{}, err
+ }
+ defer mailFile.Close()
+
+ mail, err := parsemail.Parse(mailFile)
+ if err != nil {
+ fmt.Println("email parse error:", err.Error())
+ return interfaces.Email{}, err
+ }
+
+ email := interfaces.Email{
+ ID: mail.MessageID,
+ From: mail.From[0].Address,
+ Date: mail.Date,
+ Subject: mail.Subject,
+ Body: mail.TextBody,
+ Attachments: map[string]interfaces.Attachment{},
+ }
+ for _, to := range mail.To {
+ email.To = append(email.To, to.Address)
+ }
+
+ if len(mail.Cc) > 0 {
+ for _, cc := range mail.Cc {
+ email.Cc = append(email.Cc, cc.Address)
+ }
+ }
+
+ for _, a := range mail.Attachments {
+ content, err := io.ReadAll(a.Data)
+ if err != nil {
+ fmt.Println("read attachment", err.Error())
+ continue
+ }
+
+ encContent := base64.StdEncoding.EncodeToString(content)
+ email.Attachments[a.Filename] = interfaces.Attachment{
+ Name: a.Filename,
+ ContentType: a.ContentType,
+ Data: []byte(encContent),
+ }
+ }
+ return email, nil
+}
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/interfaces/main.go b/interfaces/main.go
index 09300a2..03e74a4 100644
--- a/interfaces/main.go
+++ b/interfaces/main.go
@@ -83,5 +83,8 @@ func ToEmail(mail parsemail.Email) Email {
type Mailer interface {
Send(mail Email) error
Save(mail Email) error
+ Mailboxes(folder string) ([]Mailbox, error)
+ Mails(folder string) ([]Email, error)
+ Mail(file string) (Email, error)
}
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/www/compose.html b/www/compose.html
index ff0b41a..24d96b0 100644
--- a/www/compose.html
+++ b/www/compose.html
@@ -5,11 +5,11 @@
<table>
<tr>
<td style="padding-left: 0"><label for="from">From: </label></td>
- <td><input style="width:100%" name=from {{if .inbox}}value="{{index .inbox 0}}"{{end}}></td>
+ <td><input style="width:100%" name=from {{if .Query.inbox}}value="{{.Query.Get "inbox" }}"{{end}}></td>
</tr>
<tr>
<td style="padding-left: 0"><label>To: </label></td>
- <td><input style="width:100%" name=to {{if .to}}value="{{index .to 0}}"{{end}}></td>
+ <td><input style="width:100%" name=to {{if .Query.to}}value="{{.Query.Get "to"}}"{{end}}></td>
</tr>
<tr>
<td style="padding-left: 0"><label>CC: </label></td>
@@ -21,7 +21,7 @@
</tr>
<tr>
<td style="padding-left: 0"><label>Subject: </label></td>
- <td><input style="width:100%" name=subject {{if .subj}}value="{{index .subj 0}}"{{end}}></td>
+ <td><input style="width:100%" name=subject {{if .Query.subj}}value="{{.Query.Get "subj"}}"{{end}}></td>
</tr>
</table>
<field><textarea style="width:100%" name=body rows="7" cols="50" placeholder="Body:"></textarea><br></field>
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/www/inboxes.html b/www/inboxes.html
index 0b82ec3..3bbf0a6 100644
--- a/www/inboxes.html
+++ b/www/inboxes.html
@@ -1,12 +1,12 @@
{{template "head.html"}}
-<b>inbox {{.Get "inbox"}}</b><br><br>
+<h2>{{.Query.Get "inbox"}}</h2>
<a href="index.html">index</a> 
-<a href="compose.html?inbox={{.Get "inbox"}}">compose</a>
+<a href="compose.html?inbox={{.Query.Get "inbox"}}">compose</a>
<br><br>
-{{range (inboxes (.Get "inbox"))}}
- <a href="mails.html?inbox={{$.Get "inbox"}}&subj={{.Title}}">{{.Title}}</a><br>
+{{range (.Mailer.Mailboxes (.Query.Get "inbox"))}}
+ <a href="mails.html?inbox={{$.Query.Get "inbox"}}&subj={{.Title}}">{{.Title}}</a><br>
Updated: {{.LastMod.Format "Mon, 02 Jan 2006 15:04:05 MST"}}<br>
<br>
{{end}}
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/www/index.html b/www/index.html
index 5521891..5d17e43 100644
--- a/www/index.html
+++ b/www/index.html
@@ -16,13 +16,11 @@ _ __ /_ __ \_ | / / _ \_ /
</pre>
</center>
-<b>inboxes!</b><br>
-<br>
<a href=compose.html>compose</a><br>
<br>
-{{range (inboxes ".")}}
+{{range (.Mailer.Mailboxes ".")}}
<a href="inboxes.html?inbox={{.Title}}">{{.Title}}</a><br>
Updated: {{.LastMod.Format "Mon, 02 Jan 2006 15:04:05 MST"}}<br>
<br>
commit 163adfe2975af375af47a45c1009026ab1f40dee
Author: blmayer <bleemayer@gmail.com>
Date: Sat Apr 22 22:06:17 2023 -0300
Refactored backend and handlers
- Also did a small style on pages
diff --git a/www/mails.html b/www/mails.html
index 235b2c0..c9a8f27 100644
--- a/www/mails.html
+++ b/www/mails.html
@@ -1,12 +1,11 @@
{{template "head.html"}}
-<b>about {{.Get "subj"}}</b><br>
-<br>
-<a href="inboxes.html?inbox={{.Get "inbox"}}">inbox</a> 
-<a href="compose.html?inbox={{.Get "inbox"}}&subj={{.Get "subj"}}">compose</a><br>
-
-{{range (mails (print (.Get "inbox") "/" (.Get "subj")))}}
+<h2>{{.Query.Get "subj"}}</h2>
+<a href="inboxes.html?inbox={{.Query.Get "inbox"}}">inbox</a> 
+<a href="compose.html?inbox={{.Query.Get "inbox"}}&subj={{.Query.Get "subj"}}">compose</a><br>
+{{$inbox := printf "%s/%s" (.Query.Get "inbox") (.Query.Get "subj")}}
+{{range (.Mailer.Mails $inbox)}}
<p>
- {{if eq ($.Get "inbox") .From}}To: {{index .To 0}}{{else}}From: {{.From}}{{end}}<br>
+ {{if eq ($.Query.Get "inbox") .From}}To: {{index .To 0}}{{else}}From: {{.From}}{{end}}<br>
Date: {{.Date.Format "Mon, 02 Jan 2006 15:04:05 MST"}}<br>
{{if .Cc}}Cc: .Cc<br>{{end}}
{{if .Attachments}}
@@ -18,6 +17,6 @@
{{if .Body}}
<pre>{{.Body}}</pre>
{{end}}
- <a href="compose.html?inbox={{$.Get "inbox"}}&subj={{.Subject}}&to={{.From}}">reply</a>
+ <a href="compose.html?inbox={{$.Query.Get "inbox"}}&subj={{.Subject}}&to={{.From}}">reply</a>
</p>
{{end}}