Handle missing encoding of square brackets in filenames (#2117)

Due to unexpected and largely unchangeable behavior by both react-router and the browser, square brackets are not correctly encoded in the url when clicking a file link in the source view where the filename contains either of these characters. The source view then tries to use the useSources hook to get the file content but fails, because the path param for the file path it gets from the url has unencoded square brackets in them which are illegal in urls except for declaring IPv6 addresses. We have created a catch for exactly this scenario at the latest possible point before the actual http request is fired, which is in the useSources hook. It seems like the square brackets are the only affected special characters so we force encoding on them specifically. Only the path portion of the URL is checked so the host portion of the url may still contain unencoded square brackets which are left untouched.
This commit is contained in:
Konstantin Schaper
2022-09-09 08:19:04 +02:00
committed by GitHub
parent 9ea76073b2
commit 61676c6dd4
3 changed files with 20 additions and 11 deletions

View File

@@ -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);

View File

@@ -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;