diff --git a/gradle/changelog/square_brackets_in_filename.yaml b/gradle/changelog/square_brackets_in_filename.yaml new file mode 100644 index 0000000000..82834ff095 --- /dev/null +++ b/gradle/changelog/square_brackets_in_filename.yaml @@ -0,0 +1,2 @@ +- type: fixed + description: Handle missing encoding of square brackets in filenames ([#2117](https://github.com/scm-manager/scm-manager/pull/2117)) diff --git a/scm-ui/ui-api/src/sources.test.ts b/scm-ui/ui-api/src/sources.test.ts index 0af56ad761..ba61af1426 100644 --- a/scm-ui/ui-api/src/sources.test.ts +++ b/scm-ui/ui-api/src/sources.test.ts @@ -124,6 +124,21 @@ describe("Test sources hooks", () => { }; describe("useSources tests", () => { + const testPath = async (path: string, expectedPath: string) => { + const queryClient = createInfiniteCachingClient(); + fetchMock.getOnce(`/api/v2/src/abc/${expectedPath}?collapse=true`, readmeMd); + const { result, waitFor } = renderHook(() => useSources(puzzle42, { revision: "abc", path }), { + wrapper: createWrapper(undefined, queryClient), + }); + await waitFor(() => !!result.current.data); + expect(result.current.data).toEqual(readmeMd); + }; + + it("should return file from url with revision and path", () => testPath("README.md", "README.md")); + it("should encode square brackets in path", () => + testPath("[...nextauth][...other].ts", "%5B...nextauth%5D%5B...other%5D.ts")); + it("should not double-encode path", () => testPath("%7BDatei%7D#42.txt", "%7BDatei%7D#42.txt")); + it("should return root directory", async () => { const queryClient = createInfiniteCachingClient(); fetchMock.getOnce("/api/v2/src?collapse=true", rootDirectory); @@ -134,16 +149,6 @@ describe("Test sources hooks", () => { expect(result.current.data).toEqual(rootDirectory); }); - it("should return file from url with revision and path", async () => { - const queryClient = createInfiniteCachingClient(); - fetchMock.getOnce("/api/v2/src/abc/README.md?collapse=true", readmeMd); - const { result, waitFor } = renderHook(() => useSources(puzzle42, { revision: "abc", path: "README.md" }), { - wrapper: createWrapper(undefined, queryClient), - }); - await waitFor(() => !!result.current.data); - expect(result.current.data).toEqual(readmeMd); - }); - it("should fetch next page", async () => { const queryClient = createInfiniteCachingClient(); fetchMock.getOnce("/api/v2/src?collapse=true", mainDirectoryTruncated); diff --git a/scm-ui/ui-api/src/sources.ts b/scm-ui/ui-api/src/sources.ts index 8095406326..5972168944 100644 --- a/scm-ui/ui-api/src/sources.ts +++ b/scm-ui/ui-api/src/sources.ts @@ -93,7 +93,7 @@ const createSourcesLink = (repository: Repository, options: UseSourcesOptions) = link = urls.concat(link, encodeURIComponent(options.revision)); if (options.path) { - link = urls.concat(link, options.path); + link = urls.concat(link, encodeInvalidCharacters(options.path)); } } if (options.collapse) { @@ -102,6 +102,8 @@ const createSourcesLink = (repository: Repository, options: UseSourcesOptions) = return link; }; +const encodeInvalidCharacters = (input: string) => input.replace(/\[/g, "%5B").replace(/]/g, "%5D"); + const merge = (files?: File[]): File | undefined => { if (!files || files.length === 0) { return;