@ -0,0 +1 @@ | |||||
/.idea |
@ -0,0 +1,48 @@ | |||||
package main | |||||
import ( | |||||
"errors" | |||||
"github.com/caarlos0/env/v6" | |||||
) | |||||
type config struct { | |||||
Port int `env:"PORT" envDefault:"8080"` | |||||
HoneyPots []string `env:"HONEYPOTS" envDefault:"_t_email" envSeparator:","` | |||||
DefaultRecipient string `env:"DEFAULT_TO"` | |||||
AllowedRecipients []string `env:"ALLOWED_TO" envSeparator:","` | |||||
Sender string `env:"EMAIL_FROM"` | |||||
SmtpUser string `env:"SMTP_USER"` | |||||
SmtpPassword string `env:"SMTP_PASS"` | |||||
SmtpHost string `env:"SMTP_HOST"` | |||||
SmtpPort int `env:"SMTP_PORT" envDefault:"587"` | |||||
} | |||||
func parseConfig() (config, error) { | |||||
cfg := config{} | |||||
if err := env.Parse(&cfg); err != nil { | |||||
return cfg, errors.New("failed to parse config") | |||||
} | |||||
return cfg, nil | |||||
} | |||||
func checkRequiredConfig(cfg config) bool { | |||||
if cfg.DefaultRecipient == "" { | |||||
return false | |||||
} | |||||
if len(cfg.AllowedRecipients) < 1 { | |||||
return false | |||||
} | |||||
if cfg.Sender == "" { | |||||
return false | |||||
} | |||||
if cfg.SmtpUser == "" { | |||||
return false | |||||
} | |||||
if cfg.SmtpPassword == "" { | |||||
return false | |||||
} | |||||
if cfg.SmtpHost == "" { | |||||
return false | |||||
} | |||||
return true | |||||
} |
@ -0,0 +1,140 @@ | |||||
package main | |||||
import ( | |||||
"os" | |||||
"reflect" | |||||
"testing" | |||||
) | |||||
func Test_parseConfig(t *testing.T) { | |||||
t.Run("Default config", func(t *testing.T) { | |||||
os.Clearenv() | |||||
cfg, err := parseConfig() | |||||
if err != nil { | |||||
t.Error() | |||||
return | |||||
} | |||||
if cfg.Port != 8080 { | |||||
t.Error("Default Port not 8080") | |||||
} | |||||
if len(cfg.HoneyPots) != 1 || cfg.HoneyPots[0] != "_t_email" { | |||||
t.Error("Default HoneyPots are wrong") | |||||
} | |||||
if cfg.SmtpPort != 587 { | |||||
t.Error("SMTP Port not 587") | |||||
} | |||||
}) | |||||
t.Run("Correct config parsing", func(t *testing.T) { | |||||
os.Clearenv() | |||||
_ = os.Setenv("PORT", "1111") | |||||
_ = os.Setenv("HONEYPOTS", "pot,abc") | |||||
_ = os.Setenv("DEFAULT_TO", "mail@example.com") | |||||
_ = os.Setenv("ALLOWED_TO", "mail@example.com,test@example.com") | |||||
_ = os.Setenv("EMAIL_FROM", "forms@example.com") | |||||
_ = os.Setenv("SMTP_USER", "test@example.com") | |||||
_ = os.Setenv("SMTP_PASS", "secret") | |||||
_ = os.Setenv("SMTP_HOST", "smtp.example.com") | |||||
_ = os.Setenv("SMTP_PORT", "100") | |||||
cfg, err := parseConfig() | |||||
if err != nil { | |||||
t.Error() | |||||
return | |||||
} | |||||
if !reflect.DeepEqual(cfg.Port, 1111) { | |||||
t.Error("Port is wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.HoneyPots, []string{"pot", "abc"}) { | |||||
t.Error("HoneyPots are wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.DefaultRecipient, "mail@example.com") { | |||||
t.Error("DefaultRecipient is wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.AllowedRecipients, []string{"mail@example.com", "test@example.com"}) { | |||||
t.Error("AllowedRecipients are wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.Sender, "forms@example.com") { | |||||
t.Error("Sender is wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.SmtpUser, "test@example.com") { | |||||
t.Error("SMTP user is wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.SmtpPassword, "secret") { | |||||
t.Error("SMTP password is wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.SmtpHost, "smtp.example.com") { | |||||
t.Error("SMTP host is wrong") | |||||
} | |||||
if !reflect.DeepEqual(cfg.SmtpPort, 100) { | |||||
t.Error("SMTP port is wrong") | |||||
} | |||||
}) | |||||
t.Run("Error when wrong config", func(t *testing.T) { | |||||
os.Clearenv() | |||||
_ = os.Setenv("PORT", "ABC") | |||||
_, err := parseConfig() | |||||
if err == nil { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_checkRequiredConfig(t *testing.T) { | |||||
validConfig := config{ | |||||
Port: 8080, | |||||
HoneyPots: []string{"_t_email"}, | |||||
DefaultRecipient: "mail@example.com", | |||||
AllowedRecipients: []string{"mail@example.com"}, | |||||
Sender: "forms@example.com", | |||||
SmtpUser: "test@example.com", | |||||
SmtpPassword: "secret", | |||||
SmtpHost: "smtp.example.com", | |||||
SmtpPort: 587, | |||||
} | |||||
t.Run("Valid config", func(t *testing.T) { | |||||
if true != checkRequiredConfig(validConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Default recipient missing", func(t *testing.T) { | |||||
newConfig := validConfig | |||||
newConfig.DefaultRecipient = "" | |||||
if false != checkRequiredConfig(newConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Allowed recipients missing", func(t *testing.T) { | |||||
newConfig := validConfig | |||||
newConfig.AllowedRecipients = nil | |||||
if false != checkRequiredConfig(newConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Sender missing", func(t *testing.T) { | |||||
newConfig := validConfig | |||||
newConfig.Sender = "" | |||||
if false != checkRequiredConfig(newConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("SMTP user missing", func(t *testing.T) { | |||||
newConfig := validConfig | |||||
newConfig.SmtpUser = "" | |||||
if false != checkRequiredConfig(newConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("SMTP password missing", func(t *testing.T) { | |||||
newConfig := validConfig | |||||
newConfig.SmtpPassword = "" | |||||
if false != checkRequiredConfig(newConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("SMTP host missing", func(t *testing.T) { | |||||
newConfig := validConfig | |||||
newConfig.SmtpHost = "" | |||||
if false != checkRequiredConfig(newConfig) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} |
@ -0,0 +1,29 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<title>Test-Form</title> | |||||
</head> | |||||
<body> | |||||
<form action="//localhost:8080" method="post"> | |||||
<input type="hidden" name="_to" value="test@example.com"> | |||||
<input type="hidden" name="_formName" value="Test Form"> | |||||
<input type="hidden" name="_redirectTo" value="https://example.com"> | |||||
<label for="TEmail" style="display: none;">Test</label> | |||||
<input id="TEmail" type="email" name="_t_email" style="display: none;"> | |||||
<label for="Email">Email</label> | |||||
<br /> | |||||
<input id="Email" type="email" name="_replyTo" required> | |||||
<br /> | |||||
<label for="Name">Name</label> | |||||
<br /> | |||||
<input id="Name" type="text" name="Name" required> | |||||
<br /> | |||||
<label for="Message">Message</label> | |||||
<br /> | |||||
<textarea id="Message" name="Message"></textarea> | |||||
<br /> | |||||
<input type="submit" value="Send"> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
@ -0,0 +1,67 @@ | |||||
package main | |||||
import ( | |||||
"github.com/microcosm-cc/bluemonday" | |||||
"net/http" | |||||
"net/url" | |||||
) | |||||
type FormValues map[string][]string | |||||
func FormHandler(w http.ResponseWriter, r *http.Request) { | |||||
if r.Method == http.MethodGet { | |||||
_, _ = w.Write([]byte("MailyGo works!")) | |||||
return | |||||
} | |||||
if r.Method != http.MethodPost { | |||||
w.WriteHeader(http.StatusMethodNotAllowed) | |||||
_, _ = w.Write([]byte("The HTTP method is not allowed, make a POST request")) | |||||
return | |||||
} | |||||
_ = r.ParseForm() | |||||
sanitizedForm := sanitizeForm(r.PostForm) | |||||
if !isBot(sanitizedForm) { | |||||
sendForm(sanitizedForm) | |||||
} | |||||
sendResponse(sanitizedForm, w) | |||||
return | |||||
} | |||||
func sanitizeForm(values url.Values) FormValues { | |||||
p := bluemonday.StrictPolicy() | |||||
sanitizedForm := make(FormValues) | |||||
for key, values := range values { | |||||
var sanitizedValues []string | |||||
for _, value := range values { | |||||
sanitizedValues = append(sanitizedValues, p.Sanitize(value)) | |||||
} | |||||
sanitizedForm[p.Sanitize(key)] = sanitizedValues | |||||
} | |||||
return sanitizedForm | |||||
} | |||||
func isBot(values FormValues) bool { | |||||
for _, honeyPot := range appConfig.HoneyPots { | |||||
if len(values[honeyPot]) > 0 { | |||||
for _, value := range values[honeyPot] { | |||||
if value != "" { | |||||
return true | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
func sendResponse(values FormValues, w http.ResponseWriter) { | |||||
if len(values["_redirectTo"]) == 1 && values["_redirectTo"][0] != "" { | |||||
w.Header().Add("Location", values["_redirectTo"][0]) | |||||
w.WriteHeader(http.StatusSeeOther) | |||||
_, _ = w.Write([]byte("Go to " + values["_redirectTo"][0])) | |||||
return | |||||
} else { | |||||
w.WriteHeader(http.StatusCreated) | |||||
_, _ = w.Write([]byte("Submitted form")) | |||||
return | |||||
} | |||||
} |
@ -0,0 +1,118 @@ | |||||
package main | |||||
import ( | |||||
"net/http" | |||||
"net/http/httptest" | |||||
"net/url" | |||||
"os" | |||||
"reflect" | |||||
"testing" | |||||
) | |||||
func Test_sanitizeForm(t *testing.T) { | |||||
t.Run("Sanitize form", func(t *testing.T) { | |||||
result := sanitizeForm(url.Values{"<b>Test</b>": {"<a href=\"https://example.com\">Test</a>"}}) | |||||
want := FormValues{"Test": {"Test"}} | |||||
if !reflect.DeepEqual(result, want) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func TestFormHandler(t *testing.T) { | |||||
t.Run("GET request to FormHandler", func(t *testing.T) { | |||||
req := httptest.NewRequest("GET", "http://example.com/", nil) | |||||
w := httptest.NewRecorder() | |||||
FormHandler(w, req) | |||||
resp := w.Result() | |||||
if resp.StatusCode != http.StatusOK { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("POST request to FormHandler", func(t *testing.T) { | |||||
req := httptest.NewRequest("POST", "http://example.com/", nil) | |||||
w := httptest.NewRecorder() | |||||
FormHandler(w, req) | |||||
resp := w.Result() | |||||
if resp.StatusCode != http.StatusCreated { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Wrong method request to FormHandler", func(t *testing.T) { | |||||
req := httptest.NewRequest("DELETE", "http://example.com/", nil) | |||||
w := httptest.NewRecorder() | |||||
FormHandler(w, req) | |||||
resp := w.Result() | |||||
if resp.StatusCode != http.StatusMethodNotAllowed { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_isBot(t *testing.T) { | |||||
t.Run("No bot", func(t *testing.T) { | |||||
os.Clearenv() | |||||
result := isBot(FormValues{"_t_email": {""}}) | |||||
if !reflect.DeepEqual(result, false) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("No honeypot", func(t *testing.T) { | |||||
os.Clearenv() | |||||
result := isBot(FormValues{}) | |||||
if !reflect.DeepEqual(result, false) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Bot", func(t *testing.T) { | |||||
os.Clearenv() | |||||
result := isBot(FormValues{"_t_email": {"Test", ""}}) | |||||
if !reflect.DeepEqual(result, true) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_sendResponse(t *testing.T) { | |||||
t.Run("No redirect", func(t *testing.T) { | |||||
values := FormValues{} | |||||
w := httptest.NewRecorder() | |||||
sendResponse(values, w) | |||||
if w.Code != http.StatusCreated { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("No redirect 2", func(t *testing.T) { | |||||
values := FormValues{ | |||||
"_redirectTo": {""}, | |||||
} | |||||
w := httptest.NewRecorder() | |||||
sendResponse(values, w) | |||||
if w.Code != http.StatusCreated { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("No redirect 3", func(t *testing.T) { | |||||
values := FormValues{ | |||||
"_redirectTo": {"abc", "def"}, | |||||
} | |||||
w := httptest.NewRecorder() | |||||
sendResponse(values, w) | |||||
if w.Code != http.StatusCreated { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Redirect", func(t *testing.T) { | |||||
values := FormValues{ | |||||
"_redirectTo": {"https://example.com"}, | |||||
} | |||||
w := httptest.NewRecorder() | |||||
sendResponse(values, w) | |||||
if w.Code != http.StatusSeeOther { | |||||
t.Error() | |||||
} | |||||
if w.Header().Get("Location") != "https://example.com" { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} |
@ -0,0 +1,9 @@ | |||||
module codeberg.org/jlelse/mailygo | |||||
go 1.14 | |||||
require ( | |||||
github.com/caarlos0/env/v6 v6.2.1 | |||||
github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 | |||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect | |||||
) |
@ -0,0 +1,28 @@ | |||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= | |||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= | |||||
github.com/caarlos0/env/v6 v6.2.1 h1:/bFpX1dg4TNioJjg7mrQaSrBoQvRfLUHNfXivdFbbEo= | |||||
github.com/caarlos0/env/v6 v6.2.1/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw= | |||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= | |||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= | |||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= | |||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= | |||||
github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 h1:hJde9rA24hlTcAYSwJoXpDUyGtfKQ/jsofw+WaDqGrI= | |||||
github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= | |||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= | |||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | |||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= | |||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= | |||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | |||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | |||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
@ -0,0 +1,82 @@ | |||||
package main | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
"net/smtp" | |||||
"strconv" | |||||
"strings" | |||||
"time" | |||||
) | |||||
func sendForm(values FormValues) { | |||||
recipient := findRecipient(values) | |||||
sendMail(recipient, buildMessage(recipient, time.Now(), values)) | |||||
} | |||||
func buildMessage(recipient string, date time.Time, values FormValues) string { | |||||
var msgBuffer bytes.Buffer | |||||
_, _ = fmt.Fprintf(&msgBuffer, "From: Forms <%s>", appConfig.Sender) | |||||
_, _ = fmt.Fprintln(&msgBuffer) | |||||
_, _ = fmt.Fprintf(&msgBuffer, "To: %s", recipient) | |||||
_, _ = fmt.Fprintln(&msgBuffer) | |||||
if replyTo := findReplyTo(values); replyTo != "" { | |||||
_, _ = fmt.Fprintf(&msgBuffer, "Reply-To: %s", replyTo) | |||||
_, _ = fmt.Fprintln(&msgBuffer) | |||||
} | |||||
_, _ = fmt.Fprintf(&msgBuffer, "Date: %s", date.Format(time.RFC1123Z)) | |||||
_, _ = fmt.Fprintln(&msgBuffer) | |||||
_, _ = fmt.Fprintf(&msgBuffer, "Subject: New submission on %s", findFormName(values)) | |||||
_, _ = fmt.Fprintln(&msgBuffer) | |||||
_, _ = fmt.Fprintln(&msgBuffer) | |||||
for key, value := range removeMetaValues(values) { | |||||
_, _ = fmt.Fprint(&msgBuffer, key) | |||||
_, _ = fmt.Fprint(&msgBuffer, ": ") | |||||
_, _ = fmt.Fprintln(&msgBuffer, strings.Join(value, ", ")) | |||||
} | |||||
return msgBuffer.String() | |||||
} | |||||
func sendMail(to, message string) { | |||||
auth := smtp.PlainAuth("", appConfig.SmtpUser, appConfig.SmtpPassword, appConfig.SmtpHost) | |||||
err := smtp.SendMail(appConfig.SmtpHost+":"+strconv.Itoa(appConfig.SmtpPort), auth, appConfig.Sender, []string{to}, []byte(message)) | |||||
if err != nil { | |||||
fmt.Println("Failed to send mail:", err.Error()) | |||||
} | |||||
} | |||||
func findRecipient(values FormValues) string { | |||||
if len(values["_to"]) == 1 && values["_to"][0] != "" { | |||||
formDefinedRecipient := values["_to"][0] | |||||
for _, allowed := range appConfig.AllowedRecipients { | |||||
if formDefinedRecipient == allowed { | |||||
return formDefinedRecipient | |||||
} | |||||
} | |||||
} | |||||
return appConfig.DefaultRecipient | |||||
} | |||||
func findFormName(values FormValues) string { | |||||
if len(values["_formName"]) == 1 && values["_formName"][0] != "" { | |||||
return values["_formName"][0] | |||||
} | |||||
return "a form" | |||||
} | |||||
func findReplyTo(values FormValues) string { | |||||
if len(values["_replyTo"]) == 1 && values["_replyTo"][0] != "" { | |||||
return values["_replyTo"][0] | |||||
} | |||||
return "" | |||||
} | |||||
func removeMetaValues(values FormValues) FormValues { | |||||
cleanedValues := FormValues{} | |||||
for key, value := range values { | |||||
if !strings.HasPrefix(key, "_") { | |||||
cleanedValues[key] = value | |||||
} | |||||
} | |||||
return cleanedValues | |||||
} |
@ -0,0 +1,137 @@ | |||||
package main | |||||
import ( | |||||
"os" | |||||
"reflect" | |||||
"strings" | |||||
"testing" | |||||
"time" | |||||
) | |||||
func Test_findRecipient(t *testing.T) { | |||||
prepare := func() { | |||||
os.Clearenv() | |||||
_ = os.Setenv("ALLOWED_TO", "mail@example.com,test@example.com") | |||||
_ = os.Setenv("DEFAULT_TO", "mail@example.com") | |||||
appConfig, _ = parseConfig() | |||||
} | |||||
t.Run("No recipient specified", func(t *testing.T) { | |||||
prepare() | |||||
values := FormValues{} | |||||
result := findRecipient(values) | |||||
if result != "mail@example.com" { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Multiple recipients specified", func(t *testing.T) { | |||||
prepare() | |||||
values := FormValues{ | |||||
"_to": {"abc@example.com", "def@example.com"}, | |||||
} | |||||
result := findRecipient(values) | |||||
if result != "mail@example.com" { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Allowed recipient specified", func(t *testing.T) { | |||||
prepare() | |||||
values := FormValues{ | |||||
"_to": {"test@example.com"}, | |||||
} | |||||
result := findRecipient(values) | |||||
if result != "test@example.com" { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Forbidden recipient specified", func(t *testing.T) { | |||||
prepare() | |||||
values := FormValues{ | |||||
"_to": {"forbidden@example.com"}, | |||||
} | |||||
result := findRecipient(values) | |||||
if result != "mail@example.com" { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_findFormName(t *testing.T) { | |||||
t.Run("No form name", func(t *testing.T) { | |||||
if "a form" != findFormName(FormValues{}) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Multiple form names", func(t *testing.T) { | |||||
if "a form" != findFormName(FormValues{"_formName": {"Test", "ABC"}}) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Form name", func(t *testing.T) { | |||||
if "Test" != findFormName(FormValues{"_formName": {"Test"}}) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_findReplyTo(t *testing.T) { | |||||
t.Run("No replyTo", func(t *testing.T) { | |||||
if "" != findReplyTo(FormValues{}) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("Multiple replyTo", func(t *testing.T) { | |||||
if "" != findReplyTo(FormValues{"_replyTo": {"test@example.com", "test2@example.com"}}) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
t.Run("replyTo", func(t *testing.T) { | |||||
if "test@example.com" != findReplyTo(FormValues{"_replyTo": {"test@example.com"}}) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_removeMetaValues(t *testing.T) { | |||||
t.Run("Remove meta values", func(t *testing.T) { | |||||
result := removeMetaValues(FormValues{ | |||||
"_test": {"abc"}, | |||||
"test": {"def"}, | |||||
}) | |||||
want := FormValues{ | |||||
"test": {"def"}, | |||||
} | |||||
if !reflect.DeepEqual(result, want) { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} | |||||
func Test_buildMessage(t *testing.T) { | |||||
t.Run("Test message", func(t *testing.T) { | |||||
os.Clearenv() | |||||
_ = os.Setenv("EMAIL_TO", "mail@example.com") | |||||
_ = os.Setenv("ALLOWED_TO", "mail@example.com,test@example.com") | |||||
_ = os.Setenv("EMAIL_FROM", "forms@example.com") | |||||
appConfig, _ = parseConfig() | |||||
values := FormValues{ | |||||
"_formName": {"Testform"}, | |||||
"_replyTo": {"reply@example.com"}, | |||||
"Testkey": {"Testvalue"}, | |||||
"Another Key": {"Test", "ABC"}, | |||||
} | |||||
date := time.Now() | |||||
result := buildMessage("test@example.com", date, values) | |||||
if !strings.Contains(result, "Reply-To: reply@example.com") { | |||||
t.Error() | |||||
} | |||||
if !strings.Contains(result, "Subject: New submission on Testform") { | |||||
t.Error() | |||||
} | |||||
if !strings.Contains(result, "Testkey: Testvalue") { | |||||
t.Error() | |||||
} | |||||
if !strings.Contains(result, "Another Key: Test, ABC") { | |||||
t.Error() | |||||
} | |||||
}) | |||||
} |
@ -0,0 +1,25 @@ | |||||
package main | |||||
import ( | |||||
"log" | |||||
"net/http" | |||||
"strconv" | |||||
) | |||||
var appConfig config | |||||
func init() { | |||||
cfg, err := parseConfig() | |||||
if err != nil { | |||||
log.Fatal(err) | |||||
} | |||||
appConfig = cfg | |||||
} | |||||
func main() { | |||||
if !checkRequiredConfig(appConfig) { | |||||
log.Fatal("Not all required configurations are set") | |||||
} | |||||
http.HandleFunc("/", FormHandler) | |||||
log.Fatal(http.ListenAndServe(":"+strconv.Itoa(appConfig.Port), nil)) | |||||
} |