diff --git a/satellite/handlers/root.go b/satellite/handlers/root.go index 9fab301..d351875 100644 --- a/satellite/handlers/root.go +++ b/satellite/handlers/root.go @@ -111,6 +111,6 @@ func (h RootHandler) log(req *http.Request, respCode int) { "ja3": ja3, "response": respCode, "user_agent": req.UserAgent(), - "geo_ip": cc, + "geo_ip": cc, }).Info("request") } diff --git a/satellite/path/conditionals.go b/satellite/path/conditionals.go index 868d648..bf61d93 100644 --- a/satellite/path/conditionals.go +++ b/satellite/path/conditionals.go @@ -281,8 +281,6 @@ func (c *RequestConditions) blacklistIPRange(req *http.Request) bool { } func (c *RequestConditions) authorizedMethods(req *http.Request) bool { - correctMethods := false - if len(c.AuthorizedMethods) == 0 { log.Trace("No authorized methods") return true @@ -293,14 +291,14 @@ func (c *RequestConditions) authorizedMethods(req *http.Request) bool { log.WithFields(log.Fields{ "method": m, }).Debug("Matched HTTP method") - correctMethods = true + return true } log.WithFields(log.Fields{ "method": m, }).Trace("Did not match HTTP method") } - return correctMethods + return false } func (c *RequestConditions) authorizedHeaders(req *http.Request) bool { diff --git a/satellite/path/paths.go b/satellite/path/paths.go index e7efe1a..8ab5fc2 100644 --- a/satellite/path/paths.go +++ b/satellite/path/paths.go @@ -79,24 +79,38 @@ func (paths *Paths) Len() int { // Match matches a page given a URI. It returns the specified Path and a boolean // value to determine if there was a page that matched the URI func (paths *Paths) Match(uri string) (*Path, bool) { + hostedFileFromPath := func(v *Path) (*Path, bool) { + if v.HostedFile != "" { + return v, true + } + if _, err := os.Stat(path.Join(paths.base, v.Path)); err == nil { + v.HostedFile = v.Path + } else { + v.HostedFile = uri + } + return v, true + } + + // Prioritize direct matches over globs + for _, v := range paths.list { + if v.Path == uri { + return hostedFileFromPath(v) + } + } + + // Secondarily accept globs. Path is indeterminate if multiple globs match for _, v := range paths.list { g := glob.MustCompile(v.Path, '/') if g.Match(uri) { - if v.HostedFile != "" { - return v, true - } - if _, err := os.Stat(path.Join(paths.base, v.Path)); err != nil { - v.HostedFile = v.Path - } else { - v.HostedFile = uri - } - return v, true + return hostedFileFromPath(v) } } + info, err := os.Stat(path.Join(paths.base, uri)) if err == nil && !info.IsDir() { return &Path{Path: uri, HostedFile: uri}, true } + return nil, false } @@ -222,6 +236,21 @@ func (paths *Paths) Serve(w http.ResponseWriter, req *http.Request) error { return nil } +func applyAllConditionals(uri string, paths *Paths, matchedPath *Path) error { + for _, path := range paths.list { + g := glob.MustCompile(path.Path, '/') + if g.Match(uri) { + newP, err := MergeRequestConditions(matchedPath.Conditions, path.Conditions) + if err != nil { + return err + } + matchedPath.Conditions = newP + } + } + paths.applyGlobalConditionals(matchedPath) + return nil +} + // MatchAndServe matches a path, determines if the path should be served, and serves the file based on an HTTP request. If a failure occurs, this function will serve failed pages. // // This is a helper function which combines already-exposed functions to make file serving easy. @@ -235,7 +264,7 @@ func (paths *Paths) MatchAndServe(w http.ResponseWriter, req *http.Request) (boo return false, nil } - paths.applyGlobalConditionals(matchedPath) + applyAllConditionals(uri, paths, matchedPath) shouldHost := matchedPath.ShouldHost(req, paths.state, paths.GeoipDB) if shouldHost { diff --git a/satellite/path/paths_test.go b/satellite/path/paths_test.go index 821a62a..2a58fca 100644 --- a/satellite/path/paths_test.go +++ b/satellite/path/paths_test.go @@ -33,6 +33,11 @@ func (t TempDir) CreateFile(name, content string) { ioutil.WriteFile(fullpath, []byte(content), 0666) } +func (t TempDir) CreateDirectory(name string) { + fullpath := filepath.Join(t.Path, name) + os.Mkdir(fullpath, 0777) +} + func (t TempDir) CreateIndexFile() { t.CreateFile("index.html", Sentinal) } @@ -550,6 +555,172 @@ func TestPaths_MatchAndServe_file_noinfo(t *testing.T) { } } +func TestPaths_MatchAndServe_glob_file(t *testing.T) { + // Create project directory + tmpdir, err := NewTempDir() + if err != nil { + t.Error(err) + } + defer tmpdir.Close() + tmpdir.CreateFile("first.html", Sentinal) + tmpdir.CreateFile("second.html", "lol") + + pathList := `- path: /*.html + hosted_file: /first.html` + + tmpdir.CreatePathList(pathList) + + // Create paths object + paths, err := NewDefaultTest(tmpdir.Path) + if err != nil { + t.Error(err) + } + + // Create HTTP request + req := httptest.NewRequest("GET", "/second.html", nil) + w := httptest.NewRecorder() + + // Execute request + didMatch, err := paths.MatchAndServe(w, req) + if err != nil { + t.Error(err) + } + if didMatch == false || w.Code != 200 || w.Body.String() != Sentinal { + t.Fail() + } +} + +func buildTestEnv() (TempDir, error) { + tmpdir, err := NewTempDir() + if err != nil { + return TempDir{}, err + } + tmpdir.CreateDirectory("testdir1") + tmpdir.CreateFile("/testdir1/first.html", Sentinal) + tmpdir.CreateFile("/testdir1/second.html", Sentinal+"4") + tmpdir.CreateDirectory("testdir2") + tmpdir.CreateFile("/testdir2/first.html", Sentinal+"2") + tmpdir.CreateFile("/second.html", Sentinal+"3") + + return tmpdir, nil +} + +func TestPaths_MatchAndServe_glob_extensions_block(t *testing.T) { + tmpdir, err := buildTestEnv() + if err != nil { + t.Error(err) + } + defer tmpdir.Close() + + pathList := `- path: /**.html + authorized_methods: [PUT]` + tmpdir.CreatePathList(pathList) + + // Create paths object + paths, err := NewDefaultTest(tmpdir.Path) + if err != nil { + t.Error(err) + } + + // Make GET request to path that should only be PUT + req := httptest.NewRequest("GET", "/testdir1/first.html", nil) + w := httptest.NewRecorder() + + // Execute request + didMatch, err := paths.MatchAndServe(w, req) + if err != nil { + t.Error(err) + } + if didMatch { + t.Fatal("Request should not have matched due to glob block on GET") + } +} + +func TestPaths_MatchAndServe_glob_extensions_block_multiple(t *testing.T) { + tmpdir, err := buildTestEnv() + if err != nil { + t.Error(err) + } + defer tmpdir.Close() + + pathList := `- path: /**.html + authorized_methods: [PUT] +- path: /testdir2/*.html + authorized_methods: [GET]` + tmpdir.CreatePathList(pathList) + + // Create paths object + paths, err := NewDefaultTest(tmpdir.Path) + if err != nil { + t.Error(err) + } + + // Make GET request to path that should only be PUT + req := httptest.NewRequest("GET", "/testdir1/first.html", nil) + w := httptest.NewRecorder() + + // Execute request + didMatch, err := paths.MatchAndServe(w, req) + if err != nil { + t.Error(err) + } + if didMatch { + t.Fatal("GET request to /testdir1/first.html should not have matched") + } + + // Make GET request to path that should only be PUT + req2 := httptest.NewRequest("GET", "/testdir2/first.html", nil) + w2 := httptest.NewRecorder() + + // Execute request + didMatch2, err := paths.MatchAndServe(w2, req2) + if err != nil { + t.Error(err) + } + if !didMatch2 { + t.Fatal("GET request to /testdir2/second.html should have matched") + } +} + +func TestPaths_MatchAndServe_jlob_directory(t *testing.T) { + // Create project directory + tmpdir, err := buildTestEnv() + if err != nil { + t.Fatal(err) + } + defer tmpdir.Close() + + pathList := `- path: /testdir1/* + hosted_file: /testdir1/first.html` + + tmpdir.CreatePathList(pathList) + + // Create paths object + paths, err := NewDefaultTest(tmpdir.Path) + if err != nil { + t.Error(err) + } + + // Create HTTP request + req := httptest.NewRequest("GET", "/testdir1/second.html", nil) + w := httptest.NewRecorder() + + // Execute request + didMatch, err := paths.MatchAndServe(w, req) + if err != nil { + t.Error(err) + } + if !didMatch { + t.Error("Request should have matched:", didMatch) + } + if w.Code != 200 { + t.Error("Request should have been 200:", w.Code) + } + if w.Body.String() != Sentinal { + t.Error("/testdir1/second.html should have returned", Sentinal, "but returned", w.Body.String()) + } +} + func TestPaths_Reload_globalconditionals_makeNone(t *testing.T) { serverRoot, err := NewTempDir() if err != nil {