Skip to content

router.StaticFS calls fs with invalid file paths #4451

@hedgehog125

Description

@hedgehog125

Description

Hi. I encountered some issues trying to serve nested HTML files like tutorials/making-gin/index.html with gin-contrib/static and after some digging, I think the root cause is an issue in Gin itself.

The problem is that when you try to visit example.com/tutorials/making-gin/ (if there's no trailing slash, you get redirected), http.FS (which converts the newer fs.FS to the older http.FileSystem that Gin uses) will correctly remove the prefixed slash before calling the fs.FS. However, it's still called with invalid paths like tutorials/making-gin/. Many fs.FS implementations will reject the trailing slash like fs.Sub, which is used by gin-contrib/static, because fs.ValidPath considers it to be invalid.

Go's standard library also has a reference implementation of a handler which uses http.FileSystem, which is http.FileServer. It calls path.Clean before ultimately calling fs.Open and given that Gin's createStaticHandler uses http.FileServer once it's checked if the file exists, I think the handler should be responsible for cleaning the path before calling an http.FileSystem.

Also, the reason why router.Static doesn't have this issue is because it uses http.Dir (via a wrapper) which seems to be more leniant than a lot of implementations. It calls path.Clean before calling os.Open. I think it's still a mistake in router.Static that it doesn't clean the paths itself though.

PR: #4452

Thanks in advance.

Gin Version

v1.11.0

Can you reproduce the bug?

Yes

Source Code

My PR has what would be a failing test without the fix. Without the fix, there's a 404.

//go:embed testdata/embed
var embeddedFolder embed.FS

const embeddedPath = "testdata/embed"

func TestRouteStaticFSCleansPath(t *testing.T) {
	router := New()
	subFS, err := fs.Sub(embeddedFolder, embeddedPath)
	require.NoError(t, err)
	fs := &OnlyFilesFS{
		FileSystem: http.FS(subFS),
	}
	router.StaticFS("/", fs)
	router.NoRoute(func(c *Context) {
		c.String(http.StatusNotFound, "non existent")
	})

	w := PerformRequest(router, http.MethodGet, "/tutorials/making-gin/")
	assert.Contains(t, w.Body.String(), "This is another simple embedded page.")
}

Go Version

1.25.0

Operating System

Windows 11

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugFound something you weren't expecting? Report it here!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions