jlelse
/
MailyGo
Archived
1
Fork 0

Use pointers

This commit is contained in:
Jan-Lukas Else 2020-04-12 12:34:51 +02:00
parent 308d1b6073
commit 78eaed64d5
9 changed files with 86 additions and 83 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"errors" "errors"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v6"
) )
@ -19,15 +20,15 @@ type config struct {
Blacklist []string `env:"BLACKLIST" envSeparator:"," envDefault:"gambling,casino"` Blacklist []string `env:"BLACKLIST" envSeparator:"," envDefault:"gambling,casino"`
} }
func parseConfig() (config, error) { func parseConfig() (*config, error) {
cfg := config{} cfg := &config{}
if err := env.Parse(&cfg); err != nil { if err := env.Parse(cfg); err != nil {
return cfg, errors.New("failed to parse config") return cfg, errors.New("failed to parse config")
} }
return cfg, nil return cfg, nil
} }
func checkRequiredConfig(cfg config) bool { func checkRequiredConfig(cfg *config) bool {
if cfg.DefaultRecipient == "" { if cfg.DefaultRecipient == "" {
return false return false
} }
@ -47,4 +48,4 @@ func checkRequiredConfig(cfg config) bool {
return false return false
} }
return true return true
} }

View File

@ -90,7 +90,7 @@ func Test_parseConfig(t *testing.T) {
} }
func Test_checkRequiredConfig(t *testing.T) { func Test_checkRequiredConfig(t *testing.T) {
validConfig := config{ validConfig := &config{
Port: 8080, Port: 8080,
HoneyPots: []string{"_t_email"}, HoneyPots: []string{"_t_email"},
DefaultRecipient: "mail@example.com", DefaultRecipient: "mail@example.com",
@ -107,44 +107,44 @@ func Test_checkRequiredConfig(t *testing.T) {
} }
}) })
t.Run("Default recipient missing", func(t *testing.T) { t.Run("Default recipient missing", func(t *testing.T) {
newConfig := validConfig newConfig := *validConfig
newConfig.DefaultRecipient = "" newConfig.DefaultRecipient = ""
if false != checkRequiredConfig(newConfig) { if false != checkRequiredConfig(&newConfig) {
t.Error() t.Error()
} }
}) })
t.Run("Allowed recipients missing", func(t *testing.T) { t.Run("Allowed recipients missing", func(t *testing.T) {
newConfig := validConfig newConfig := *validConfig
newConfig.AllowedRecipients = nil newConfig.AllowedRecipients = nil
if false != checkRequiredConfig(newConfig) { if false != checkRequiredConfig(&newConfig) {
t.Error() t.Error()
} }
}) })
t.Run("Sender missing", func(t *testing.T) { t.Run("Sender missing", func(t *testing.T) {
newConfig := validConfig newConfig := *validConfig
newConfig.Sender = "" newConfig.Sender = ""
if false != checkRequiredConfig(newConfig) { if false != checkRequiredConfig(&newConfig) {
t.Error() t.Error()
} }
}) })
t.Run("SMTP user missing", func(t *testing.T) { t.Run("SMTP user missing", func(t *testing.T) {
newConfig := validConfig newConfig := *validConfig
newConfig.SmtpUser = "" newConfig.SmtpUser = ""
if false != checkRequiredConfig(newConfig) { if false != checkRequiredConfig(&newConfig) {
t.Error() t.Error()
} }
}) })
t.Run("SMTP password missing", func(t *testing.T) { t.Run("SMTP password missing", func(t *testing.T) {
newConfig := validConfig newConfig := *validConfig
newConfig.SmtpPassword = "" newConfig.SmtpPassword = ""
if false != checkRequiredConfig(newConfig) { if false != checkRequiredConfig(&newConfig) {
t.Error() t.Error()
} }
}) })
t.Run("SMTP host missing", func(t *testing.T) { t.Run("SMTP host missing", func(t *testing.T) {
newConfig := validConfig newConfig := *validConfig
newConfig.SmtpHost = "" newConfig.SmtpHost = ""
if false != checkRequiredConfig(newConfig) { if false != checkRequiredConfig(&newConfig) {
t.Error() t.Error()
} }
}) })

View File

@ -1,10 +1,11 @@
package main package main
import ( import (
"github.com/microcosm-cc/bluemonday"
"html" "html"
"net/http" "net/http"
"net/url" "net/url"
"github.com/microcosm-cc/bluemonday"
) )
type FormValues map[string][]string type FormValues map[string][]string
@ -20,7 +21,7 @@ func FormHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
_ = r.ParseForm() _ = r.ParseForm()
sanitizedForm := sanitizeForm(r.PostForm) sanitizedForm := sanitizeForm(&r.PostForm)
go func() { go func() {
if !isBot(sanitizedForm) { if !isBot(sanitizedForm) {
sendForm(sanitizedForm) sendForm(sanitizedForm)
@ -30,23 +31,23 @@ func FormHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
func sanitizeForm(values url.Values) FormValues { func sanitizeForm(values *url.Values) *FormValues {
p := bluemonday.StrictPolicy() p := bluemonday.StrictPolicy()
sanitizedForm := make(FormValues) sanitizedForm := make(FormValues)
for key, values := range values { for key, values := range *values {
var sanitizedValues []string var sanitizedValues []string
for _, value := range values { for _, value := range values {
sanitizedValues = append(sanitizedValues, html.UnescapeString(p.Sanitize(value))) sanitizedValues = append(sanitizedValues, html.UnescapeString(p.Sanitize(value)))
} }
sanitizedForm[html.UnescapeString(p.Sanitize(key))] = sanitizedValues sanitizedForm[html.UnescapeString(p.Sanitize(key))] = sanitizedValues
} }
return sanitizedForm return &sanitizedForm
} }
func isBot(values FormValues) bool { func isBot(values *FormValues) bool {
for _, honeyPot := range appConfig.HoneyPots { for _, honeyPot := range appConfig.HoneyPots {
if len(values[honeyPot]) > 0 { if len((*values)[honeyPot]) > 0 {
for _, value := range values[honeyPot] { for _, value := range (*values)[honeyPot] {
if value != "" { if value != "" {
return true return true
} }
@ -56,15 +57,15 @@ func isBot(values FormValues) bool {
return checkValues(values) return checkValues(values)
} }
func sendResponse(values FormValues, w http.ResponseWriter) { func sendResponse(values *FormValues, w http.ResponseWriter) {
if len(values["_redirectTo"]) == 1 && values["_redirectTo"][0] != "" { if len((*values)["_redirectTo"]) == 1 && (*values)["_redirectTo"][0] != "" {
w.Header().Add("Location", values["_redirectTo"][0]) w.Header().Add("Location", (*values)["_redirectTo"][0])
w.WriteHeader(http.StatusSeeOther) w.WriteHeader(http.StatusSeeOther)
_, _ = w.Write([]byte("Go to " + values["_redirectTo"][0])) _, _ = w.Write([]byte("Go to " + (*values)["_redirectTo"][0]))
return return
} else { } else {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte("Submitted form")) _, _ = w.Write([]byte("Submitted form"))
return return
} }
} }

View File

@ -11,9 +11,9 @@ import (
func Test_sanitizeForm(t *testing.T) { func Test_sanitizeForm(t *testing.T) {
t.Run("Sanitize form", func(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>"}}) result := sanitizeForm(&url.Values{"<b>Test</b>": {"<a href=\"https://example.com\">Test</a>"}})
want := FormValues{"Test": {"Test"}} want := FormValues{"Test": {"Test"}}
if !reflect.DeepEqual(result, want) { if !reflect.DeepEqual(*result, want) {
t.Error() t.Error()
} }
}) })
@ -52,21 +52,21 @@ func TestFormHandler(t *testing.T) {
func Test_isBot(t *testing.T) { func Test_isBot(t *testing.T) {
t.Run("No bot", func(t *testing.T) { t.Run("No bot", func(t *testing.T) {
os.Clearenv() os.Clearenv()
result := isBot(FormValues{"_t_email": {""}}) result := isBot(&FormValues{"_t_email": {""}})
if !reflect.DeepEqual(result, false) { if !reflect.DeepEqual(result, false) {
t.Error() t.Error()
} }
}) })
t.Run("No honeypot", func(t *testing.T) { t.Run("No honeypot", func(t *testing.T) {
os.Clearenv() os.Clearenv()
result := isBot(FormValues{}) result := isBot(&FormValues{})
if !reflect.DeepEqual(result, false) { if !reflect.DeepEqual(result, false) {
t.Error() t.Error()
} }
}) })
t.Run("Bot", func(t *testing.T) { t.Run("Bot", func(t *testing.T) {
os.Clearenv() os.Clearenv()
result := isBot(FormValues{"_t_email": {"Test", ""}}) result := isBot(&FormValues{"_t_email": {"Test", ""}})
if !reflect.DeepEqual(result, true) { if !reflect.DeepEqual(result, true) {
t.Error() t.Error()
} }
@ -75,7 +75,7 @@ func Test_isBot(t *testing.T) {
func Test_sendResponse(t *testing.T) { func Test_sendResponse(t *testing.T) {
t.Run("No redirect", func(t *testing.T) { t.Run("No redirect", func(t *testing.T) {
values := FormValues{} values := &FormValues{}
w := httptest.NewRecorder() w := httptest.NewRecorder()
sendResponse(values, w) sendResponse(values, w)
if w.Code != http.StatusCreated { if w.Code != http.StatusCreated {
@ -83,7 +83,7 @@ func Test_sendResponse(t *testing.T) {
} }
}) })
t.Run("No redirect 2", func(t *testing.T) { t.Run("No redirect 2", func(t *testing.T) {
values := FormValues{ values := &FormValues{
"_redirectTo": {""}, "_redirectTo": {""},
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -93,7 +93,7 @@ func Test_sendResponse(t *testing.T) {
} }
}) })
t.Run("No redirect 3", func(t *testing.T) { t.Run("No redirect 3", func(t *testing.T) {
values := FormValues{ values := &FormValues{
"_redirectTo": {"abc", "def"}, "_redirectTo": {"abc", "def"},
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -103,7 +103,7 @@ func Test_sendResponse(t *testing.T) {
} }
}) })
t.Run("Redirect", func(t *testing.T) { t.Run("Redirect", func(t *testing.T) {
values := FormValues{ values := &FormValues{
"_redirectTo": {"https://example.com"}, "_redirectTo": {"https://example.com"},
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -115,4 +115,4 @@ func Test_sendResponse(t *testing.T) {
t.Error() t.Error()
} }
}) })
} }

32
mail.go
View File

@ -10,12 +10,12 @@ import (
"time" "time"
) )
func sendForm(values FormValues) { func sendForm(values *FormValues) {
recipient := findRecipient(values) recipient := findRecipient(values)
sendMail(recipient, buildMessage(recipient, time.Now(), values)) sendMail(recipient, buildMessage(recipient, time.Now(), values))
} }
func buildMessage(recipient string, date time.Time, values FormValues) string { func buildMessage(recipient string, date time.Time, values *FormValues) string {
var msgBuffer bytes.Buffer var msgBuffer bytes.Buffer
_, _ = fmt.Fprintf(&msgBuffer, "From: Forms <%s>", appConfig.Sender) _, _ = fmt.Fprintf(&msgBuffer, "From: Forms <%s>", appConfig.Sender)
_, _ = fmt.Fprintln(&msgBuffer) _, _ = fmt.Fprintln(&msgBuffer)
@ -32,14 +32,14 @@ func buildMessage(recipient string, date time.Time, values FormValues) string {
_, _ = fmt.Fprintln(&msgBuffer) _, _ = fmt.Fprintln(&msgBuffer)
bodyValues := removeMetaValues(values) bodyValues := removeMetaValues(values)
var keys []string var keys []string
for key, _ := range bodyValues { for key := range *bodyValues {
keys = append(keys, key) keys = append(keys, key)
} }
sort.Strings(keys) sort.Strings(keys)
for _, key := range keys { for _, key := range keys {
_, _ = fmt.Fprint(&msgBuffer, key) _, _ = fmt.Fprint(&msgBuffer, key)
_, _ = fmt.Fprint(&msgBuffer, ": ") _, _ = fmt.Fprint(&msgBuffer, ": ")
_, _ = fmt.Fprintln(&msgBuffer, strings.Join(bodyValues[key], ", ")) _, _ = fmt.Fprintln(&msgBuffer, strings.Join((*bodyValues)[key], ", "))
} }
return msgBuffer.String() return msgBuffer.String()
} }
@ -52,9 +52,9 @@ func sendMail(to, message string) {
} }
} }
func findRecipient(values FormValues) string { func findRecipient(values *FormValues) string {
if len(values["_to"]) == 1 && values["_to"][0] != "" { if len((*values)["_to"]) == 1 && (*values)["_to"][0] != "" {
formDefinedRecipient := values["_to"][0] formDefinedRecipient := (*values)["_to"][0]
for _, allowed := range appConfig.AllowedRecipients { for _, allowed := range appConfig.AllowedRecipients {
if formDefinedRecipient == allowed { if formDefinedRecipient == allowed {
return formDefinedRecipient return formDefinedRecipient
@ -64,26 +64,26 @@ func findRecipient(values FormValues) string {
return appConfig.DefaultRecipient return appConfig.DefaultRecipient
} }
func findFormName(values FormValues) string { func findFormName(values *FormValues) string {
if len(values["_formName"]) == 1 && values["_formName"][0] != "" { if len((*values)["_formName"]) == 1 && (*values)["_formName"][0] != "" {
return values["_formName"][0] return (*values)["_formName"][0]
} }
return "a form" return "a form"
} }
func findReplyTo(values FormValues) string { func findReplyTo(values *FormValues) string {
if len(values["_replyTo"]) == 1 && values["_replyTo"][0] != "" { if len((*values)["_replyTo"]) == 1 && (*values)["_replyTo"][0] != "" {
return values["_replyTo"][0] return (*values)["_replyTo"][0]
} }
return "" return ""
} }
func removeMetaValues(values FormValues) FormValues { func removeMetaValues(values *FormValues) *FormValues {
cleanedValues := FormValues{} cleanedValues := FormValues{}
for key, value := range values { for key, value := range *values {
if !strings.HasPrefix(key, "_") { if !strings.HasPrefix(key, "_") {
cleanedValues[key] = value cleanedValues[key] = value
} }
} }
return cleanedValues return &cleanedValues
} }

View File

@ -17,7 +17,7 @@ func Test_findRecipient(t *testing.T) {
} }
t.Run("No recipient specified", func(t *testing.T) { t.Run("No recipient specified", func(t *testing.T) {
prepare() prepare()
values := FormValues{} values := &FormValues{}
result := findRecipient(values) result := findRecipient(values)
if result != "mail@example.com" { if result != "mail@example.com" {
t.Error() t.Error()
@ -25,7 +25,7 @@ func Test_findRecipient(t *testing.T) {
}) })
t.Run("Multiple recipients specified", func(t *testing.T) { t.Run("Multiple recipients specified", func(t *testing.T) {
prepare() prepare()
values := FormValues{ values := &FormValues{
"_to": {"abc@example.com", "def@example.com"}, "_to": {"abc@example.com", "def@example.com"},
} }
result := findRecipient(values) result := findRecipient(values)
@ -35,7 +35,7 @@ func Test_findRecipient(t *testing.T) {
}) })
t.Run("Allowed recipient specified", func(t *testing.T) { t.Run("Allowed recipient specified", func(t *testing.T) {
prepare() prepare()
values := FormValues{ values := &FormValues{
"_to": {"test@example.com"}, "_to": {"test@example.com"},
} }
result := findRecipient(values) result := findRecipient(values)
@ -45,7 +45,7 @@ func Test_findRecipient(t *testing.T) {
}) })
t.Run("Forbidden recipient specified", func(t *testing.T) { t.Run("Forbidden recipient specified", func(t *testing.T) {
prepare() prepare()
values := FormValues{ values := &FormValues{
"_to": {"forbidden@example.com"}, "_to": {"forbidden@example.com"},
} }
result := findRecipient(values) result := findRecipient(values)
@ -57,17 +57,17 @@ func Test_findRecipient(t *testing.T) {
func Test_findFormName(t *testing.T) { func Test_findFormName(t *testing.T) {
t.Run("No form name", func(t *testing.T) { t.Run("No form name", func(t *testing.T) {
if "a form" != findFormName(FormValues{}) { if "a form" != findFormName(&FormValues{}) {
t.Error() t.Error()
} }
}) })
t.Run("Multiple form names", func(t *testing.T) { t.Run("Multiple form names", func(t *testing.T) {
if "a form" != findFormName(FormValues{"_formName": {"Test", "ABC"}}) { if "a form" != findFormName(&FormValues{"_formName": {"Test", "ABC"}}) {
t.Error() t.Error()
} }
}) })
t.Run("Form name", func(t *testing.T) { t.Run("Form name", func(t *testing.T) {
if "Test" != findFormName(FormValues{"_formName": {"Test"}}) { if "Test" != findFormName(&FormValues{"_formName": {"Test"}}) {
t.Error() t.Error()
} }
}) })
@ -75,17 +75,17 @@ func Test_findFormName(t *testing.T) {
func Test_findReplyTo(t *testing.T) { func Test_findReplyTo(t *testing.T) {
t.Run("No replyTo", func(t *testing.T) { t.Run("No replyTo", func(t *testing.T) {
if "" != findReplyTo(FormValues{}) { if "" != findReplyTo(&FormValues{}) {
t.Error() t.Error()
} }
}) })
t.Run("Multiple replyTo", func(t *testing.T) { t.Run("Multiple replyTo", func(t *testing.T) {
if "" != findReplyTo(FormValues{"_replyTo": {"test@example.com", "test2@example.com"}}) { if "" != findReplyTo(&FormValues{"_replyTo": {"test@example.com", "test2@example.com"}}) {
t.Error() t.Error()
} }
}) })
t.Run("replyTo", func(t *testing.T) { t.Run("replyTo", func(t *testing.T) {
if "test@example.com" != findReplyTo(FormValues{"_replyTo": {"test@example.com"}}) { if "test@example.com" != findReplyTo(&FormValues{"_replyTo": {"test@example.com"}}) {
t.Error() t.Error()
} }
}) })
@ -93,14 +93,14 @@ func Test_findReplyTo(t *testing.T) {
func Test_removeMetaValues(t *testing.T) { func Test_removeMetaValues(t *testing.T) {
t.Run("Remove meta values", func(t *testing.T) { t.Run("Remove meta values", func(t *testing.T) {
result := removeMetaValues(FormValues{ result := removeMetaValues(&FormValues{
"_test": {"abc"}, "_test": {"abc"},
"test": {"def"}, "test": {"def"},
}) })
want := FormValues{ want := FormValues{
"test": {"def"}, "test": {"def"},
} }
if !reflect.DeepEqual(result, want) { if !reflect.DeepEqual(*result, want) {
t.Error() t.Error()
} }
}) })
@ -113,7 +113,7 @@ func Test_buildMessage(t *testing.T) {
_ = os.Setenv("ALLOWED_TO", "mail@example.com,test@example.com") _ = os.Setenv("ALLOWED_TO", "mail@example.com,test@example.com")
_ = os.Setenv("EMAIL_FROM", "forms@example.com") _ = os.Setenv("EMAIL_FROM", "forms@example.com")
appConfig, _ = parseConfig() appConfig, _ = parseConfig()
values := FormValues{ values := &FormValues{
"_formName": {"Testform"}, "_formName": {"Testform"},
"_replyTo": {"reply@example.com"}, "_replyTo": {"reply@example.com"},
"Testkey": {"Testvalue"}, "Testkey": {"Testvalue"},
@ -134,4 +134,4 @@ func Test_buildMessage(t *testing.T) {
t.Error() t.Error()
} }
}) })
} }

View File

@ -6,7 +6,7 @@ import (
"strconv" "strconv"
) )
var appConfig config var appConfig *config
func init() { func init() {
cfg, err := parseConfig() cfg, err := parseConfig()

View File

@ -1,16 +1,17 @@
package main package main
import ( import (
"github.com/google/safebrowsing"
"net/url" "net/url"
"strings" "strings"
"github.com/google/safebrowsing"
) )
// Returns true when it spam // Returns true when it spam
func checkValues(values FormValues) bool { func checkValues(values *FormValues) bool {
var urlsToCheck []string var urlsToCheck []string
var allValues []string var allValues []string
for _, value := range values { for _, value := range *values {
for _, singleValue := range value { for _, singleValue := range value {
allValues = append(allValues, singleValue) allValues = append(allValues, singleValue)
if strings.Contains(singleValue, "http") { if strings.Contains(singleValue, "http") {
@ -21,11 +22,11 @@ func checkValues(values FormValues) bool {
} }
} }
} }
return checkBlacklist(allValues) || checkUrls(urlsToCheck) return checkBlacklist(&allValues) || checkUrls(&urlsToCheck)
} }
func checkBlacklist(values []string) bool { func checkBlacklist(values *[]string) bool {
for _, value := range values { for _, value := range *values {
for _, blacklistedString := range appConfig.Blacklist { for _, blacklistedString := range appConfig.Blacklist {
if strings.Contains(strings.ToLower(value), strings.ToLower(blacklistedString)) { if strings.Contains(strings.ToLower(value), strings.ToLower(blacklistedString)) {
return true return true
@ -38,8 +39,8 @@ func checkBlacklist(values []string) bool {
// Only tests when GOOGLE_API_KEY is set // Only tests when GOOGLE_API_KEY is set
// Returns true when it spam // Returns true when it spam
func checkUrls(urlsToCheck []string) bool { func checkUrls(urlsToCheck *[]string) bool {
if len(appConfig.GoogleApiKey) < 1 || len(urlsToCheck) == 0 { if len(appConfig.GoogleApiKey) < 1 || len(*urlsToCheck) == 0 {
return false return false
} }
sb, err := safebrowsing.NewSafeBrowser(safebrowsing.Config{ sb, err := safebrowsing.NewSafeBrowser(safebrowsing.Config{
@ -49,7 +50,7 @@ func checkUrls(urlsToCheck []string) bool {
if err != nil { if err != nil {
return false return false
} }
allThreats, err := sb.LookupURLs(urlsToCheck) allThreats, err := sb.LookupURLs(*urlsToCheck)
if err != nil { if err != nil {
return false return false
} }

View File

@ -13,13 +13,13 @@ func Test_checkBlacklist(t *testing.T) {
} }
t.Run("Allowed values", func(t *testing.T) { t.Run("Allowed values", func(t *testing.T) {
prepare() prepare()
if checkBlacklist([]string{"Hello", "How are you?"}) == true { if checkBlacklist(&[]string{"Hello", "How are you?"}) == true {
t.Error() t.Error()
} }
}) })
t.Run("Forbidden values", func(t *testing.T) { t.Run("Forbidden values", func(t *testing.T) {
prepare() prepare()
if checkBlacklist([]string{"How are you?", "Hello TeSt1"}) == false { if checkBlacklist(&[]string{"How are you?", "Hello TeSt1"}) == false {
t.Error() t.Error()
} }
}) })