@ -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)) | |||
} |