diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed3beb6..c732c82 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,4 +8,4 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '^1.16.0' - - run: go version + - run: go test ./... diff --git a/app/webmention/handler.go b/app/webmention/handler.go index 0bc57e0..ad59fba 100644 --- a/app/webmention/handler.go +++ b/app/webmention/handler.go @@ -22,6 +22,11 @@ func HandlePut(conf *common.Config) http.HandlerFunc { func HandlePost(conf *common.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + if !validate(r, r.Header, conf) { + http.Error(w, "400 bad request", http.StatusBadRequest) + } + fmt.Printf("%+v\n", r.Header) } } diff --git a/app/webmention/validate.go b/app/webmention/validate.go new file mode 100644 index 0000000..d02555d --- /dev/null +++ b/app/webmention/validate.go @@ -0,0 +1,37 @@ + +package webmention + +import ( + "strings" + + "github.com/wgroeneveld/go-jamming/common" +) + +func isValidUrl(url string) bool { + return url != "" && + (strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) +} + +func isValidDomain(url string, conf *common.Config) bool { + for _, domain := range conf.AllowedWebmentionSources { + if strings.Index(url, domain) != -1 { + return true + } + } + return false +} + +type httpReq interface { + FormValue(key string) string +} +type httpHeader interface { + Get(key string) string +} + +func validate(r httpReq, h httpHeader, conf *common.Config) bool { + return h.Get("Content-Type") == "application/x-www-form-urlencoded" && + isValidUrl(r.FormValue("source")) && + isValidUrl(r.FormValue("target")) && + r.FormValue("source") != r.FormValue("target") && + isValidDomain(r.FormValue("target"), conf) +} diff --git a/app/webmention/validate_test.go b/app/webmention/validate_test.go new file mode 100644 index 0000000..f6893dd --- /dev/null +++ b/app/webmention/validate_test.go @@ -0,0 +1,115 @@ + +package webmention + +import ( + "testing" + + "github.com/wgroeneveld/go-jamming/common" +) + +type httpReqMock struct { + source string + target string +} +type httpHeaderMock struct { + contentType string +} +func (mock *httpHeaderMock) Get(key string) string { + return mock.contentType +} +func (mock *httpReqMock) FormValue(key string) string { + switch key { + case "source": return mock.source + case "target": return mock.target + default: return "" + } +} +func buildHttpReq(source string, target string) *httpReqMock { + return &httpReqMock{ + source: source, + target: target, + } +} + +var config = common.Configure() + +func TestValidate(t *testing.T) { + cases := []struct { + label string + source string + target string + contentType string + expected bool + } { + { + "is valid if source and target https urls", + "http://brainbaking.com/bla1", + "http://jefklakscodex.com/bla", + "application/x-www-form-urlencoded", + true, + }, + { + "is NOT valid if target is a valid url but not form valid domain", + "http://brainbaking.com/bla1", + "http://brainthe.bake/jup", + "application/x-www-form-urlencoded", + false, + }, + { + "is NOT valid if source and target are the same urls", + "http://brainbaking.com/bla1", + "http://brainbaking.com/bla1", + "application/x-www-form-urlencoded", + false, + }, + { + "is NOT valid if source is not a valid url", + "lolz", + "http://brainbaking.com/bla1", + "application/x-www-form-urlencoded", + false, + }, + { + "is NOT valid if source is missing", + "", + "http://brainbaking.com/bla1", + "application/x-www-form-urlencoded", + false, + }, + { + "is NOT valid if target is missing", + "http://brainbaking.com/bla1", + "", + "application/x-www-form-urlencoded", + false, + }, + { + "is NOT valid if no valid encoded form", + "http://brainbaking.com/bla1", + "http://jefklakscodex.com/bla", + "application/lolz", + false, + }, + { + "is NOT valid if body is missing", + "", + "", + "application/x-www-form-urlencoded", + false, + }, + } + + for _, tc := range cases { + t.Run(tc.label, func(t *testing.T) { + httpReq := buildHttpReq(tc.source, tc.target) + httpHeader := &httpHeaderMock{ contentType: tc.contentType } + + actual := validate(httpReq, httpHeader, config) + if actual != tc.expected { + t.Fatalf("got %v, want %v", actual, tc.expected) + } + }) + } + + +} diff --git a/go.mod b/go.mod index 4086a16..c641803 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/wgroeneveld/go-jamming go 1.16 -require github.com/gorilla/mux v1.8.0 // indirect +require github.com/gorilla/mux v1.8.0