Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datasources): Properly handle datasources and other URLs beginning with '../' #2255

Merged
merged 1 commit into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions internal/datafs/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ func (d *dsReader) readFileContent(ctx context.Context, u *url.URL, hdr http.Hea
return &content{contentType: mimeType, b: data}, nil
}

// COPIED FROM /data/datasource.go
//
// resolveURL parses the relative URL rel against base, and returns the
// resolved URL. Differs from url.ResolveReference in that query parameters are
// added. In case of duplicates, params from rel are used.
Expand Down Expand Up @@ -232,9 +230,18 @@ func resolveURL(base *url.URL, rel string) (*url.URL, error) {
// URL.ResolveReference requires (or assumes, at least) that the base is
// absolute. We want to support relative URLs too though, so we need to
// correct for that.
out := base.ResolveReference(relURL)
if out.Scheme == "" && out.Path[0] == '/' {
var out *url.URL
switch {
case rel == "":
out = base
case base.IsAbs():
out = base.ResolveReference(relURL)
case base.Scheme == "" && base.Path[0] == '/':
// absolute path, no scheme or volume
out = base.ResolveReference(relURL)
out.Path = out.Path[1:]
default:
out = resolveRelativeURL(base, relURL)
}

if base.RawQuery != "" {
Expand All @@ -248,3 +255,22 @@ func resolveURL(base *url.URL, rel string) (*url.URL, error) {

return out, nil
}

// relative path, might even involve .. or . in the path
func resolveRelativeURL(base, relURL *url.URL) *url.URL {
// first find the difference between base and what base would be if it
// were absolute
emptyURL, _ := url.Parse("")
absBase := base.ResolveReference(emptyURL)
absBase.Path = strings.TrimPrefix(absBase.Path, "/")

diff := strings.TrimSuffix(base.Path, absBase.Path)
diff = strings.TrimSuffix(diff, "/")

out := base.ResolveReference(relURL)

// now correct the path by adding the prefix back in
out.Path = diff + out.Path

return out
}
16 changes: 15 additions & 1 deletion internal/datafs/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,24 @@ func TestResolveURL(t *testing.T) {
_, err = resolveURL(mustParseURL("git+ssh://[email protected]/foo//bar"), "baz//myfile")
require.Error(t, err)

// relative urls must remain relative
// relative base URLs must remain relative
out, err = resolveURL(mustParseURL("tmp/foo.json"), "")
require.NoError(t, err)
assert.Equal(t, "tmp/foo.json", out.String())

// relative implicit file URLs without volume or scheme are OK
out, err = resolveURL(mustParseURL("/tmp/"), "foo.json")
require.NoError(t, err)
assert.Equal(t, "tmp/foo.json", out.String())

// relative base URLs in parent directories are OK
out, err = resolveURL(mustParseURL("../../tmp/foo.json"), "")
require.NoError(t, err)
assert.Equal(t, "../../tmp/foo.json", out.String())

out, err = resolveURL(mustParseURL("../../tmp/"), "sub/foo.json")
require.NoError(t, err)
assert.Equal(t, "../../tmp/sub/foo.json", out.String())
}

func TestReadFileContent(t *testing.T) {
Expand Down
33 changes: 33 additions & 0 deletions internal/tests/integration/datasources_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,36 @@ func TestDatasources_File_Directory(t *testing.T) {
withDir(tmpDir.Path()).run()
assertSuccess(t, o, e, err, "core.yaml root key: cloud")
}

func TestDatsources_File_RelativePath(t *testing.T) {
// regression test for #2230
tmpDir := fs.NewDir(t, "gomplate-inttests",
fs.WithDir("root",
fs.WithDir("foo",
fs.WithDir("bar",
fs.WithFiles(map[string]string{
".gomplate.yaml": `
context:
qux:
url: "../../baz/qux.yaml"
`,
}),
),
),
fs.WithDir("baz",
fs.WithFiles(map[string]string{
"qux.yaml": `values:
- value1
- value2
`,
}),
),
),
)
t.Cleanup(tmpDir.Remove)

o, e, err := cmd(t, "--config", ".gomplate.yaml", "-i", "{{ .qux.values }}").
withDir(tmpDir.Join("root", "foo", "bar")).run()

assertSuccess(t, o, e, err, "[value1 value2]")
}