Skip to content

Commit a8d54d0

Browse files
fix(web): validate browse paths on the server
1 parent 0012be8 commit a8d54d0

4 files changed

Lines changed: 94 additions & 83 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { getConfiguredLanguageModelsInfo } from "@/features/chat/utils.server";
2+
import { notFound } from "next/navigation";
3+
import { getBrowseParamsFromPathParam } from "../hooks/utils";
4+
import { LayoutClient } from "../layoutClient";
5+
6+
interface LayoutProps {
7+
children: React.ReactNode;
8+
params: Promise<{
9+
path: string[];
10+
}>;
11+
}
12+
13+
export default async function Layout({
14+
children,
15+
params,
16+
}: LayoutProps) {
17+
const { path } = await params;
18+
const browseParams = getBrowseParamsFromPathParam(path.join('/'));
19+
if (!browseParams) {
20+
notFound();
21+
}
22+
23+
const languageModels = await getConfiguredLanguageModelsInfo();
24+
return (
25+
<LayoutClient
26+
browseParams={browseParams}
27+
isSearchAssistSupported={languageModels.length > 0}
28+
>
29+
{children}
30+
</LayoutClient>
31+
)
32+
}
Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
1-
import { notFound, usePathname } from "next/navigation";
2-
import { useMemo } from "react";
3-
import { getBrowseParamsFromPathParam } from "./utils";
1+
import { createContext, useContext } from "react";
2+
import type { BrowseProps } from "./utils";
43

5-
export const useBrowseParams = () => {
6-
const pathname = usePathname();
7-
8-
return useMemo(() => {
9-
const startIndex = pathname.indexOf('/browse/');
10-
if (startIndex === -1) {
11-
notFound();
12-
}
4+
export const BrowseParamsContext = createContext<BrowseProps | null>(null);
135

14-
const rawPath = pathname.substring(startIndex + '/browse/'.length);
15-
const browseParams = getBrowseParamsFromPathParam(rawPath);
16-
if (!browseParams) {
17-
notFound();
18-
}
6+
export const useBrowseParams = () => {
7+
const browseParams = useContext(BrowseParamsContext);
8+
if (!browseParams) {
9+
throw new Error('useBrowseParams must be used within a BrowseParamsContext provider');
10+
}
1911

20-
return browseParams;
21-
}, [pathname]);
12+
return browseParams;
2213
}

packages/web/src/app/(app)/browse/layout.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/web/src/app/(app)/browse/layoutClient.tsx

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,78 @@ import { BottomPanel } from "./components/bottomPanel";
55
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
66
import { BrowseStateProvider } from "./browseStateProvider";
77
import { FileTreePanel } from "./components/fileTreePanel";
8-
import { useBrowseParams } from "./hooks/useBrowseParams";
8+
import { BrowseParamsContext } from "./hooks/useBrowseParams";
9+
import type { BrowseProps } from "./hooks/utils";
910
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
1011
import { SearchBar } from "../components/searchBar";
1112
import escapeStringRegexp from "escape-string-regexp";
1213
import { Separator } from "@/components/ui/separator";
1314

1415
interface LayoutProps {
1516
children: React.ReactNode;
17+
browseParams: BrowseProps;
1618
isSearchAssistSupported: boolean;
1719
}
1820

1921
export function LayoutClient({
2022
children,
23+
browseParams,
2124
isSearchAssistSupported,
2225
}: LayoutProps) {
23-
const { repoName, revisionName, pathType } = useBrowseParams();
26+
const { repoName, revisionName, pathType } = browseParams;
2427
return (
25-
<BrowseStateProvider>
26-
<div className="flex flex-col h-full">
27-
<div className='sticky top-0 left-0 right-0 z-10'>
28-
<div className="py-1.5 px-3">
29-
<SearchBar
30-
size="sm"
31-
defaults={{
32-
query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
33-
}}
34-
className="w-full"
35-
isSearchAssistSupported={isSearchAssistSupported}
36-
/>
28+
<BrowseParamsContext.Provider value={browseParams}>
29+
<BrowseStateProvider>
30+
<div className="flex flex-col h-full">
31+
<div className='sticky top-0 left-0 right-0 z-10'>
32+
<div className="py-1.5 px-3">
33+
<SearchBar
34+
size="sm"
35+
defaults={{
36+
query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
37+
}}
38+
className="w-full"
39+
isSearchAssistSupported={isSearchAssistSupported}
40+
/>
41+
</div>
42+
<Separator />
3743
</div>
38-
<Separator />
39-
</div>
40-
<ResizablePanelGroup
41-
direction="horizontal"
42-
>
43-
<FileTreePanel order={1} />
44+
<ResizablePanelGroup
45+
direction="horizontal"
46+
>
47+
<FileTreePanel order={1} />
4448

45-
<AnimatedResizableHandle />
49+
<AnimatedResizableHandle />
4650

47-
<ResizablePanel
48-
order={2}
49-
minSize={10}
50-
defaultSize={80}
51-
id="code-preview-panel-container"
52-
>
53-
<ResizablePanelGroup
54-
direction="vertical"
51+
<ResizablePanel
52+
order={2}
53+
minSize={10}
54+
defaultSize={80}
55+
id="code-preview-panel-container"
5556
>
56-
<ResizablePanel
57-
order={1}
58-
id="code-preview-panel"
57+
<ResizablePanelGroup
58+
direction="vertical"
5959
>
60-
{children}
61-
</ResizablePanel>
62-
{(pathType === 'blob' || pathType === 'tree') && (
63-
<>
64-
<AnimatedResizableHandle />
65-
<BottomPanel
66-
order={2}
67-
/>
68-
</>
69-
)}
70-
</ResizablePanelGroup>
71-
</ResizablePanel>
72-
</ResizablePanelGroup>
73-
</div>
74-
<FileSearchCommandDialog />
75-
</BrowseStateProvider>
60+
<ResizablePanel
61+
order={1}
62+
id="code-preview-panel"
63+
>
64+
{children}
65+
</ResizablePanel>
66+
{(pathType === 'blob' || pathType === 'tree') && (
67+
<>
68+
<AnimatedResizableHandle />
69+
<BottomPanel
70+
order={2}
71+
/>
72+
</>
73+
)}
74+
</ResizablePanelGroup>
75+
</ResizablePanel>
76+
</ResizablePanelGroup>
77+
</div>
78+
<FileSearchCommandDialog />
79+
</BrowseStateProvider>
80+
</BrowseParamsContext.Provider>
7681
);
7782
}

0 commit comments

Comments
 (0)