diff --git a/.changeset/small-windows-beam.md b/.changeset/small-windows-beam.md new file mode 100644 index 0000000..a3176c2 --- /dev/null +++ b/.changeset/small-windows-beam.md @@ -0,0 +1,5 @@ +--- +"neogrok": patch +--- + +Improve URL encoding of repository URLs diff --git a/src/lib/url-templates.test.ts b/src/lib/url-templates.test.ts index 5946959..1c02d98 100644 --- a/src/lib/url-templates.test.ts +++ b/src/lib/url-templates.test.ts @@ -20,6 +20,17 @@ describe("evaluateFileUrlTemplate", () => { "ignored", ), ).toEqual("https://svn.future/r/12345/nopath"); + expect( + evaluateFileUrlTemplate( + "https://github.com/hash/enjoyer/blob/{{.Version}}/{{.Path}}", + "m^ster", + "langs/a++/$$$/🚔/c#.md", + ), + ).toEqual( + // We cannot confidently apply any URL encodings to an old-style template + // because we do not know where in the URL the variables appear. + "https://github.com/hash/enjoyer/blob/m^ster/langs/a++/$$$/🚔/c#.md", + ); }); it("evaluates new templates", () => { @@ -30,6 +41,15 @@ describe("evaluateFileUrlTemplate", () => { "genversion.sh", ), ).toEqual("https://github.com/hanwen/go-fuse/blob/notify/genversion.sh"); + expect( + evaluateFileUrlTemplate( + '{{URLJoinPath "https://github.com/hash/enjoyer/blob" .Version .Path}}', + "m^ster", + "langs/a++/$$$/🚔/c#.md", + ), + ).toEqual( + "https://github.com/hash/enjoyer/blob/m%5Ester/langs/a%2B%2B/%24%24%24/%F0%9F%9A%94/c%23.md", + ); expect( evaluateFileUrlTemplate( '{{ URLJoinPath "https://github.com/hanwen/go-fuse/blob" .Version .Path }}', @@ -58,6 +78,14 @@ describe("evaluateCommitUrlTemplate", () => { expect( evaluateCommitUrlTemplate("https://svn.future/r/{{.Version}}", "12345"), ).toEqual("https://svn.future/r/12345"); + expect( + evaluateCommitUrlTemplate( + "https://github.com/hash/enjoyer/commit/{{.Version}}", + "m^ster", + ), + // We cannot confidently apply any URL encodings to an old-style template + // because we do not know where in the URL the variables appear. + ).toEqual("https://github.com/hash/enjoyer/commit/m^ster"); }); it("evaluates new templates", () => { @@ -79,5 +107,11 @@ describe("evaluateCommitUrlTemplate", () => { "12345", ), ).toEqual("https://svn.future/r/12345"); + expect( + evaluateCommitUrlTemplate( + '{{URLJoinPath "https://github.com/hash/enjoyer/commit" .Version}}', + "m^ster", + ), + ).toEqual("https://github.com/hash/enjoyer/commit/m%5Ester"); }); }); diff --git a/src/lib/url-templates.ts b/src/lib/url-templates.ts index 2b0901e..3753e0f 100644 --- a/src/lib/url-templates.ts +++ b/src/lib/url-templates.ts @@ -17,9 +17,9 @@ export const evaluateFileUrlTemplate = ( .split(/\s+/) .map((s) => { if (s === ".Version") { - return version; + return version.split("/").map(encodeURIComponent).join("/"); } else if (s === ".Path") { - return path; + return path.split("/").map(encodeURIComponent).join("/"); } else { // It's a quoted string: https://pkg.go.dev/strconv#Quote. return JSON.parse(s); @@ -27,9 +27,14 @@ export const evaluateFileUrlTemplate = ( }) .join("/"); } else { - return template - .replaceAll("{{.Version}}", version) - .replaceAll("{{.Path}}", path); + return ( + template + // We use the function version of replaceAll because it interprets a + // variety of characters in strings specially. Only functions guarantee + // literal replacement. + .replaceAll("{{.Version}}", () => version) + .replaceAll("{{.Path}}", () => path) + ); } }; @@ -44,7 +49,7 @@ export const evaluateCommitUrlTemplate = ( .split(/\s+/) .map((s) => { if (s === ".Version") { - return version; + return version.split("/").map(encodeURIComponent).join("/"); } else { // It's a quoted string: https://pkg.go.dev/strconv#Quote. return JSON.parse(s); @@ -52,6 +57,9 @@ export const evaluateCommitUrlTemplate = ( }) .join("/"); } else { - return template.replaceAll("{{.Version}}", version); + // We use the function version of replaceAll because it interprets a + // variety of characters in strings specially. Only functions guarantee + // literal replacement. + return template.replaceAll("{{.Version}}", () => version); } };