summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2024-11-10 15:50:42 -0500
committerGitHub <noreply@github.com>2024-11-10 20:50:42 +0000
commit53d6ca0ee470cd6d3595c380b55934ee26d8ce9f (patch)
treea29f2e25c1a6660c61e7b724642ea07b1659e4fc
parenta13844c9c0a3d03e0fba4627a51445ca9ae8100b (diff)
fix(datasources): Properly handle datasources and other URLs beginning with '../' (#2255)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
-rw-r--r--internal/datafs/reader.go34
-rw-r--r--internal/datafs/reader_test.go16
-rw-r--r--internal/tests/integration/datasources_file_test.go33
3 files changed, 78 insertions, 5 deletions
diff --git a/internal/datafs/reader.go b/internal/datafs/reader.go
index a7c00d34..68d5d37f 100644
--- a/internal/datafs/reader.go
+++ b/internal/datafs/reader.go
@@ -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.
@@ -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 != "" {
@@ -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
+}
diff --git a/internal/datafs/reader_test.go b/internal/datafs/reader_test.go
index 588592fd..05472642 100644
--- a/internal/datafs/reader_test.go
+++ b/internal/datafs/reader_test.go
@@ -61,10 +61,24 @@ func TestResolveURL(t *testing.T) {
_, err = resolveURL(mustParseURL("git+ssh://git@example.com/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) {
diff --git a/internal/tests/integration/datasources_file_test.go b/internal/tests/integration/datasources_file_test.go
index 37e65a0f..ea4f0259 100644
--- a/internal/tests/integration/datasources_file_test.go
+++ b/internal/tests/integration/datasources_file_test.go
@@ -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]")
+}