Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the TrustedFS type, representing an fs.FS in a safe way. The only ways to create a TrustedFS is from an embed.FS, or from a TrustedSource. This ensures that a TrustedFS is always under application control. This new safehtml API requires Go 1.16 or higher, since that is when fs.FS was introduced. That seems reasonable, since the Go team supports only the last two versions, and we are now on 1.17. PiperOrigin-RevId: 405723011 Change-Id: I3f8554b25b74630386956ab240a4d0a70db0aee2
- Loading branch information
Safe HTML Team
authored and
Copybara-Service
committed
Oct 26, 2021
1 parent
2057dd9
commit d6f0e11
Showing
3 changed files
with
127 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Copyright (c) 2021 The Go Authors. All rights reserved. | ||
// | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file or at | ||
// https://developers.google.com/open-source/licenses/bsd | ||
|
||
//go:build go1.16 | ||
// +build go1.16 | ||
|
||
package template | ||
|
||
import ( | ||
"embed" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"path" | ||
) | ||
|
||
// A TrustedFS is an immutable type referencing a filesystem (fs.FS) | ||
// under application control. | ||
// | ||
// In order to ensure that an attacker cannot influence the TrustedFS value, a | ||
// TrustedFS can be instantiated in only two ways. One way is from an embed.FS | ||
// with TrustedFSFromEmbed. It is assumed that embedded filesystems are under | ||
// the programmer's control. The other way is from a TrustedSource using | ||
// TrustedFSFromTrustedSource, in which case the guarantees and caveats of | ||
// TrustedSource apply. | ||
type TrustedFS struct { | ||
fsys fs.FS | ||
} | ||
|
||
// TrustedFSFromEmbed constructs a TrustedFS from an embed.FS. | ||
func TrustedFSFromEmbed(fsys embed.FS) TrustedFS { | ||
return TrustedFS{fsys: fsys} | ||
} | ||
|
||
// TrustedFSFromTrustedSource constructs a TrustedFS from the string in the | ||
// TrustedSource, which should refer to a directory. | ||
func TrustedFSFromTrustedSource(ts TrustedSource) TrustedFS { | ||
return TrustedFS{fsys: os.DirFS(ts.src)} | ||
} | ||
|
||
// ParseFS is like ParseFiles or ParseGlob but reads from the TrustedFS | ||
// instead of the host operating system's file system. | ||
// It accepts a list of glob patterns. | ||
// (Note that most file names serve as glob patterns matching only themselves.) | ||
func ParseFS(tfs TrustedFS, patterns ...string) (*Template, error) { | ||
return parseFS(nil, tfs.fsys, patterns) | ||
} | ||
|
||
// ParseFS is like ParseFiles or ParseGlob but reads from the TrustedFS | ||
// instead of the host operating system's file system. | ||
// It accepts a list of glob patterns. | ||
// (Note that most file names serve as glob patterns matching only themselves.) | ||
func (t *Template) ParseFS(tfs TrustedFS, patterns ...string) (*Template, error) { | ||
return parseFS(t, tfs.fsys, patterns) | ||
} | ||
|
||
// Copied from | ||
// https://go.googlesource.com/go/+/refs/tags/go1.17.1/src/text/template/helper.go. | ||
func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) { | ||
var filenames []string | ||
for _, pattern := range patterns { | ||
list, err := fs.Glob(fsys, pattern) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(list) == 0 { | ||
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) | ||
} | ||
filenames = append(filenames, list...) | ||
} | ||
return parseFiles(t, readFileFS(fsys), filenames...) | ||
} | ||
|
||
// Copied with minor changes from | ||
// https://go.googlesource.com/go/+/refs/tags/go1.17.1/src/text/template/helper.go. | ||
func readFileFS(fsys fs.FS) func(string) (string, []byte, error) { | ||
return func(file string) (string, []byte, error) { | ||
name := path.Base(file) | ||
b, err := fs.ReadFile(fsys, file) | ||
return name, b, err | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) 2021 The Go Authors. All rights reserved. | ||
// | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file or at | ||
// https://developers.google.com/open-source/licenses/bsd | ||
|
||
//go:build go1.16 | ||
// +build go1.16 | ||
|
||
package template | ||
|
||
import ( | ||
"embed" | ||
"testing" | ||
) | ||
|
||
//go:embed testdata | ||
var testFS embed.FS | ||
|
||
func TestParseFS(t *testing.T) { | ||
tmpl := New("root") | ||
parsedTmpl := Must(tmpl.ParseFS(TrustedFSFromEmbed(testFS), "testdata/glob_*.tmpl")) | ||
if parsedTmpl != tmpl { | ||
t.Errorf("expected ParseEmbedFS to update template") | ||
} | ||
} |