commit - 5b8260a55717a51bd99ac6e148cd9bf179561126
commit + a7e7d1d5189113e16d1cd8eb7a6d044d3eca0aca
blob - d12434921090a07f23a8ab1eec3054d934401d8a
blob + 98a9f070192863dc80ac6d29db43f0aa03ec5064
--- client.go
+++ client.go
"bytes"
"encoding/json"
"fmt"
+ "io"
"net/http"
+
+ "mailmux.net/aliases"
)
const apiurl = "https://mailmux.net/v1/aliases"
return fmt.Errorf("register %s: %s", username, rerror.Error)
}
-func (c *Client) NewAlias() (Alias, error) {
- mcall := &Mcall{
+func (c *Client) NewAlias() (aliases.Aliases, error) {
+ tmsg := &Mcall{
Type: Tnew,
Username: c.user,
Password: c.token,
}
buf := &bytes.Buffer{}
- if err := json.NewEncoder(buf).Encode(mcall); err != nil {
- return Alias{}, fmt.Errorf("new alias: %w", err)
+ if err := json.NewEncoder(buf).Encode(tmsg); err != nil {
+ return nil, fmt.Errorf("new alias: %w", err)
}
+
resp, err := http.Post(c.addr+"/aliases", jsonContentType, buf)
if err != nil {
- return Alias{}, fmt.Errorf("new alias: %w", err)
+ return nil, fmt.Errorf("new alias: %w", err)
}
-
defer resp.Body.Close()
rmsg, err := ParseMcall(resp.Body)
if err != nil {
- return Alias{}, fmt.Errorf("new alias: parse response: %w", err)
+ return nil, fmt.Errorf("new alias: parse response: %w", err)
}
if rmsg.Type == Rerror {
- return Alias{}, fmt.Errorf("new alias: %v", rmsg.Error)
+ return nil, fmt.Errorf("new alias: %v", rmsg.Error)
}
- return rmsg.Aliases[0], nil
+ return rmsg.Aliases, nil
}
-func (c *Client) Aliases() ([]Alias, error) {
+func (c *Client) Aliases() (aliases.Aliases, error) {
tmsg := &Mcall{
Type: Tlist,
Username: c.user,
}
defer resp.Body.Close()
- rmsg, err := ParseMcall(resp.Body)
+ buf.Reset()
+ io.Copy(buf, resp.Body)
+ fmt.Println(string(buf.Bytes()))
+ rmsg, err := ParseMcall(buf)
if err != nil {
return nil, fmt.Errorf("list aliases: parse response: %w", err)
}
blob - 95eb5dc5f0570ad7be1eab427a0896ff34a8f85c
blob + 51e0bf7797f62622a549f18aa0f43c9b2b4b2d2e
--- cmd/mailmux/mailmux.go
+++ cmd/mailmux/mailmux.go
)
type server struct {
- seedfile string
+ seedfile string
aliaspath string
- aliases aliases.Aliases
- users mailmux.UserStore
+ aliases aliases.Aliases
+ users mailmux.UserStore
}
func (srv *server) aliasHandler(w http.ResponseWriter, req *http.Request) {
- if err := req.ParseForm(); err != nil {
- w.WriteHeader(http.StatusBadRequest)
+ tmsg, err := mailmux.ParseMcall(req.Body)
+ if err != nil {
+ rerror(w, err.Error(), http.StatusBadRequest)
return
}
- user := req.Form.Get("username")
- if user == "" {
- http.Error(w, "empty user", http.StatusBadRequest)
- return
- }
- token := req.Form.Get("token")
- if token == "" {
- http.Error(w, "empty token", http.StatusBadRequest)
- return
- }
- _, err := srv.users.Lookup(user)
+ _, err = srv.users.Lookup(tmsg.Username)
if err != nil {
- w.WriteHeader(http.StatusUnauthorized)
+ rerror(w, "unauthorised", http.StatusUnauthorized)
log.Println(err)
return
}
- switch req.Method {
- case http.MethodGet:
- srv.listAliasHandler(w, req)
+ // TODO authenticate user
+
+ switch tmsg.Type {
+ case mailmux.Tnew:
+ srv.newAlias(w, tmsg)
return
- case http.MethodPost:
- srv.newAliasHandler(w, req)
+ case mailmux.Tlist:
+ srv.listAliasHandler(w, tmsg)
return
- case http.MethodDelete:
- srv.deleteAlias(w, req)
- return
}
- w.WriteHeader(http.StatusMethodNotAllowed)
+ rerror(w, "not implemented yet", http.StatusNotImplemented)
}
-func (srv *server) listAliasHandler(w http.ResponseWriter, req *http.Request) {
- username := req.Form.Get("username")
+func (srv *server) listAliasHandler(w http.ResponseWriter, tmsg *mailmux.Mcall) {
filtered := make(aliases.Aliases)
for rcpt, dest := range srv.aliases {
- if dest == username {
+ if dest == tmsg.Username {
filtered[rcpt] = dest
}
}
- if err := json.NewEncoder(w).Encode(filtered); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ rmsg := &mailmux.Mcall{
+ Type: mailmux.Rlist,
+ Username: tmsg.Username,
+ Aliases: filtered,
+ }
+ if err := json.NewEncoder(w).Encode(rmsg); err != nil {
+ rerror(w, err.Error(), http.StatusInternalServerError)
return
}
}
-func (srv *server) newAliasHandler(w http.ResponseWriter, req *http.Request) {
- username := req.Form.Get("username")
+func (srv *server) newAlias(w http.ResponseWriter, tmsg *mailmux.Mcall) {
+ if tmsg.Type != mailmux.Tnew {
+ rerror(w, "message type is not Tnew", http.StatusBadRequest)
+ return
+ }
+
rcpt, err := mailmux.RandomUsername(srv.seedfile)
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ s := fmt.Sprintf("random username: %v", err)
+ rerror(w, s, http.StatusInternalServerError)
return
}
- srv.aliases[rcpt] = username
+ srv.aliases[rcpt] = tmsg.Username
if err := aliases.Put(srv.aliases, srv.aliaspath); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ s := fmt.Sprintf("put: %v", err)
+ rerror(w, s, http.StatusInternalServerError)
return
}
- rcpt = rcpt + "@mailmux.net"
- if err := json.NewEncoder(w).Encode(rcpt); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+
+ rnew := &mailmux.Mcall{
+ Type: mailmux.Rnew,
+ Username: tmsg.Username,
+ Aliases: aliases.Aliases{rcpt: tmsg.Username},
+ }
+ if err := json.NewEncoder(w).Encode(rnew); err != nil {
+ rerror(w, err.Error(), http.StatusInternalServerError)
return
}
}
// with its Error field set to errormsg.
// Just like http.Error, callers should ensure no further writes are done to w.
func rerror(w http.ResponseWriter, errormsg string, status int) {
- mcall := &mailmux.Mcall{Type: mailmux.Rerror, Error: errormsg}
- if err := json.NewEncoder(w).Encode(mcall); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
w.WriteHeader(status)
- return
+ rmsg := &mailmux.Mcall{Type: mailmux.Rerror, Error: errormsg}
+ if err := json.NewEncoder(w).Encode(rmsg); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
}
func main() {
blob - 52f9c160dcf7b2ab08ae17f9b0923353ce2d9b0f
blob + 26966c2e5dd6cb30b90898b3dedd30a52773a6a6
--- cmd/mailmux/mailmux_test.go
+++ cmd/mailmux/mailmux_test.go
package main
import (
+ "fmt"
"net"
"net/http"
"os"
"testing"
mailmux "mailmux.net"
+ "mailmux.net/aliases"
)
func newTestServerClient(t *testing.T) *mailmux.Client {
}
srv := &server{
users: db,
+ aliases: make(aliases.Aliases),
aliaspath: path.Join(dir, "aliases"),
seedfile: "/usr/share/dict/words",
}
t.Fatal(err)
}
- http.HandleFunc("/register", srv.registerHandler)
- http.HandleFunc("/aliases", srv.aliasHandler)
+ mux := http.NewServeMux()
+ mux.HandleFunc("/register", srv.registerHandler)
+ mux.HandleFunc("/aliases", srv.aliasHandler)
go func() {
- t.Fatal(http.Serve(ln, nil))
+ t.Fatal(http.Serve(ln, mux))
}()
- return mailmux.Dial("http://" + ln.Addr().String(), "test", "test")
+ client := mailmux.Dial("http://"+ln.Addr().String(), "test@example.com", "test")
+ if err := client.Register("test@example.com", "test"); err != nil {
+ t.Fatal(err)
+ }
+ return client
}
func TestBadRegistration(t *testing.T) {
client := newTestServerClient(t)
registrations := map[string]string{
- "djfkjskdjf": "dfjkdkfjsd",
- "": "asdfgjkl",
- "fjdklskjdsf": "",
- "@@@@": "dfjksjkdf",
+ "djfkjskdjf": "dfjkdkfjsd",
+ "": "asdfgjkl",
+ "fjdklskjdsf": "",
+ "@@@@": "dfjksjkdf",
"foo@example.com": "",
}
for username, password := range registrations {
}
}
}
+
+func TestAliases(t *testing.T) {
+ client := newTestServerClient(t)
+ for i := 0; i <= 100; i++ {
+ a, err := client.NewAlias()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Println(a)
+ }
+ a, err := client.Aliases()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Println(a)
+}
blob - 46f5ee368b9a28a1cf6f475ad9dfa4a501d8d6e9
blob + 9428a3263c8c7a06be0274eedf32b489c0ca49f3
--- cmd/mailmux/register.go
+++ cmd/mailmux/register.go
func (srv *server) registerUser(name string, pw mailmux.Password) error {
_, err := srv.users.Lookup(name)
- if err == nil {
- return mailmux.ErrUserExist
- }
if errors.Is(err, mailmux.ErrUnknownUser) {
return srv.users.Change(name, pw)
}
+ if err == nil {
+ return mailmux.ErrUserExist
+ }
return err
}
return
}
- var mcall mailmux.Mcall
- if err := json.NewDecoder(req.Body).Decode(&mcall); err != nil {
+ var tmsg mailmux.Mcall
+ if err := json.NewDecoder(req.Body).Decode(&tmsg); err != nil {
rerror(w, err.Error(), http.StatusBadRequest)
return
}
- if mcall.Type != mailmux.Tregister {
- s := fmt.Sprintf("mcall type %d is not Tregister", mcall.Type)
+ if tmsg.Type != mailmux.Tregister {
+ s := fmt.Sprintf("mcall type %d is not Tregister", tmsg.Type)
rerror(w, s, http.StatusBadRequest)
return
}
- if mcall.Username == "" {
+ if tmsg.Username == "" {
rerror(w, "empty username", http.StatusBadRequest)
return
}
- if mcall.Password == "" {
+ if tmsg.Password == "" {
rerror(w, "empty password", http.StatusBadRequest)
return
}
- err := srv.registerUser(mcall.Username, mailmux.Password(mcall.Password))
+ err := srv.registerUser(tmsg.Username, mailmux.Password(tmsg.Password))
if err != nil {
rerror(w, err.Error(), http.StatusInternalServerError)
+ return
}
}
blob - b3008bfc6eabb6e3a0750bd60e55419257c9291e
blob + f147c4457cb40a47a4144343f9916eac478b7d86
--- mcall.go
+++ mcall.go
"errors"
"io"
"time"
+
+ "mailmux.net/aliases"
)
const (
// This design is loosely based on the Plan 9 network file protocol 9P.
type Mcall struct {
Type uint
- Username string // Tregister, Rregister
- Password string // Tregister
- Error string // Rerror
- Aliases []Alias // Rnew, Rlist, Tremove
- Expiry time.Time // Tnew, Rnew
+ Username string // Tregister, Rregister
+ Password string // Tregister
+ Error string // Rerror
+ Aliases aliases.Aliases // Rnew, Rlist, Tremove
+ Expiry time.Time // Tnew, Rnew
}
// ParseMcall parses and validates one Mcall from r.
if err := json.NewDecoder(r).Decode(&mc); err != nil {
return nil, err
}
- if mc.Username == "" {
- return nil, errors.New("empty username")
+ switch mc.Type {
+ case Rerror:
+ if mc.Error == "" {
+ return nil, errors.New("empty error message")
+ }
+ case Tregister, Tnew, Tlist, Tremove:
+ if mc.Username == "" {
+ return nil, errors.New("empty username")
+ } else if mc.Password == "" {
+ return nil, errors.New("empty password")
+ }
}
- if mc.Password == "" {
- return nil, errors.New("empty password")
- }
- if mc.Type != Rerror && mc.Error != "" {
- return nil, errors.New("non-empty error field")
- }
- if mc.Type == Rerror && mc.Error == "" {
- return nil, errors.New("empty error message")
- }
return &mc, nil
}