1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

Handle static routes with trailing slash (#1747)

- Fix Static file route not working without trailing slash
- Add tests for static middleware with/without trailing slash
- Add tests for static middleware under group

Co-authored-by: pwli <lipw0755@gmail.com>
This commit is contained in:
Roland Lammel 2021-01-03 00:25:29 +01:00 committed by GitHub
parent 0bdb45c583
commit 716eb18329
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 233 additions and 4 deletions

11
echo.go
View File

@ -503,8 +503,15 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
}
return c.File(name)
}
if prefix == "/" {
return get(prefix+"*", h)
// Handle added routes based on trailing slash:
// /prefix => exact route "/prefix" + any route "/prefix/*"
// /prefix/ => only any route "/prefix/*"
if prefix != "" {
if prefix[len(prefix)-1] == '/' {
// Only add any route for intentional trailing slash
return get(prefix+"*", h)
}
get(prefix, h)
}
return get(prefix+"/*", h)
}

View File

@ -105,6 +105,32 @@ func TestEchoStatic(t *testing.T) {
expectHeaderLocation: "/folder/",
expectBodyStartsWith: "",
},
{
name: "Directory Redirect with non-root path",
givenPrefix: "/static",
givenRoot: "_fixture",
whenURL: "/static",
expectStatus: http.StatusMovedPermanently,
expectHeaderLocation: "/static/",
expectBodyStartsWith: "",
},
{
name: "Prefixed directory 404 (request URL without slash)",
givenPrefix: "/folder/", // trailing slash will intentionally not match "/folder"
givenRoot: "_fixture",
whenURL: "/folder", // no trailing slash
expectStatus: http.StatusNotFound,
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
},
{
name: "Prefixed directory redirect (without slash redirect to slash)",
givenPrefix: "/folder", // no trailing slash shall match /folder and /folder/*
givenRoot: "_fixture",
whenURL: "/folder", // no trailing slash
expectStatus: http.StatusMovedPermanently,
expectHeaderLocation: "/folder/",
expectBodyStartsWith: "",
},
{
name: "Directory with index.html",
givenPrefix: "/",
@ -113,6 +139,22 @@ func TestEchoStatic(t *testing.T) {
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "Prefixed directory with index.html (prefix ending with slash)",
givenPrefix: "/assets/",
givenRoot: "_fixture",
whenURL: "/assets/",
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "Prefixed directory with index.html (prefix ending without slash)",
givenPrefix: "/assets",
givenRoot: "_fixture",
whenURL: "/assets/",
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "Sub-directory with index.html",
givenPrefix: "/",
@ -164,6 +206,40 @@ func TestEchoStatic(t *testing.T) {
}
}
func TestEchoStaticRedirectIndex(t *testing.T) {
assert := assert.New(t)
e := New()
// HandlerFunc
e.Static("/static", "_fixture")
errCh := make(chan error)
go func() {
errCh <- e.Start("127.0.0.1:1323")
}()
time.Sleep(200 * time.Millisecond)
if resp, err := http.Get("http://127.0.0.1:1323/static"); err == nil {
defer resp.Body.Close()
assert.Equal(http.StatusOK, resp.StatusCode)
if body, err := ioutil.ReadAll(resp.Body); err == nil {
assert.Equal(true, strings.HasPrefix(string(body), "<!doctype html>"))
} else {
assert.Fail(err.Error())
}
} else {
assert.Fail(err.Error())
}
if err := e.Close(); err != nil {
t.Fatal(err)
}
}
func TestEchoFile(t *testing.T) {
e := New()
e.File("/walle", "_fixture/images/walle.png")

View File

@ -1,11 +1,13 @@
package middleware
import (
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestStatic(t *testing.T) {
@ -131,3 +133,147 @@ func TestStatic(t *testing.T) {
})
}
}
func TestStatic_GroupWithStatic(t *testing.T) {
var testCases = []struct {
name string
givenGroup string
givenPrefix string
givenRoot string
whenURL string
expectStatus int
expectHeaderLocation string
expectBodyStartsWith string
}{
{
name: "ok",
givenPrefix: "/images",
givenRoot: "../_fixture/images",
whenURL: "/group/images/walle.png",
expectStatus: http.StatusOK,
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
},
{
name: "No file",
givenPrefix: "/images",
givenRoot: "../_fixture/scripts",
whenURL: "/group/images/bolt.png",
expectStatus: http.StatusNotFound,
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
},
{
name: "Directory not found (no trailing slash)",
givenPrefix: "/images",
givenRoot: "../_fixture/images",
whenURL: "/group/images/",
expectStatus: http.StatusNotFound,
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
},
{
name: "Directory redirect",
givenPrefix: "/",
givenRoot: "../_fixture",
whenURL: "/group/folder",
expectStatus: http.StatusMovedPermanently,
expectHeaderLocation: "/group/folder/",
expectBodyStartsWith: "",
},
{
name: "Prefixed directory 404 (request URL without slash)",
givenGroup: "_fixture",
givenPrefix: "/folder/", // trailing slash will intentionally not match "/folder"
givenRoot: "../_fixture",
whenURL: "/_fixture/folder", // no trailing slash
expectStatus: http.StatusNotFound,
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
},
{
name: "Prefixed directory redirect (without slash redirect to slash)",
givenGroup: "_fixture",
givenPrefix: "/folder", // no trailing slash shall match /folder and /folder/*
givenRoot: "../_fixture",
whenURL: "/_fixture/folder", // no trailing slash
expectStatus: http.StatusMovedPermanently,
expectHeaderLocation: "/_fixture/folder/",
expectBodyStartsWith: "",
},
{
name: "Directory with index.html",
givenPrefix: "/",
givenRoot: "../_fixture",
whenURL: "/group/",
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "Prefixed directory with index.html (prefix ending with slash)",
givenPrefix: "/assets/",
givenRoot: "../_fixture",
whenURL: "/group/assets/",
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "Prefixed directory with index.html (prefix ending without slash)",
givenPrefix: "/assets",
givenRoot: "../_fixture",
whenURL: "/group/assets/",
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "Sub-directory with index.html",
givenPrefix: "/",
givenRoot: "../_fixture",
whenURL: "/group/folder/",
expectStatus: http.StatusOK,
expectBodyStartsWith: "<!doctype html>",
},
{
name: "do not allow directory traversal (backslash - windows separator)",
givenPrefix: "/",
givenRoot: "../_fixture/",
whenURL: `/group/..\\middleware/basic_auth.go`,
expectStatus: http.StatusNotFound,
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
},
{
name: "do not allow directory traversal (slash - unix separator)",
givenPrefix: "/",
givenRoot: "../_fixture/",
whenURL: `/group/../middleware/basic_auth.go`,
expectStatus: http.StatusNotFound,
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := echo.New()
group := "/group"
if tc.givenGroup != "" {
group = tc.givenGroup
}
g := e.Group(group)
g.Static(tc.givenPrefix, tc.givenRoot)
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, tc.expectStatus, rec.Code)
body := rec.Body.String()
if tc.expectBodyStartsWith != "" {
assert.True(t, strings.HasPrefix(body, tc.expectBodyStartsWith))
} else {
assert.Equal(t, "", body)
}
if tc.expectHeaderLocation != "" {
assert.Equal(t, tc.expectHeaderLocation, rec.Header().Get(echo.HeaderLocation))
} else {
_, ok := rec.Result().Header[echo.HeaderLocation]
assert.False(t, ok)
}
})
}
}