8000 chore: parse app status link (#18439) · coder/coder@05f6d69 · GitHub
[go: up one dir, main page]

Skip to content 8000

Commit 05f6d69

Browse files
authored
chore: parse app status link (#18439)
No actual exploit here as far as I can tell, but doing a string check without parsing was flagged by a scanner.
1 parent d5e3419 commit 05f6d69

File tree

3 files changed

+139
-46
lines changed

3 files changed

+139
-46
lines changed

site/src/pages/TaskPage/TaskSidebar.tsx

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import GitHub from "@mui/icons-material/GitHub";
21
import type { WorkspaceApp } from "api/typesGenerated";
32
import { Button } from "components/Button/Button";
43
import {
@@ -14,19 +13,13 @@ import {
1413
TooltipProvider,
1514
TooltipTrigger,
1615
} from "components/Tooltip/Tooltip";
17-
import {
18-
ArrowLeftIcon,
19-
BugIcon,
20-
EllipsisVerticalIcon,
21-
ExternalLinkIcon,
22-
GitPullRequestArrowIcon,
23-
} from "lucide-react";
16+
import { ArrowLeftIcon, EllipsisVerticalIcon } from "lucide-react";
2417
import type { Task } from "modules/tasks/tasks";
2518
import type { FC } from "react";
2619
import { Link as RouterLink } from "react-router-dom";
2720
import { cn } from "utils/cn";
28-
import { truncateURI } from "utils/uri";
2921
import { TaskAppIFrame } from "./TaskAppIframe";
22+
import { TaskStatusLink } from "./TaskStatusLink";
3023

3124
type TaskSidebarProps = {
3225
task: Task;
@@ -179,40 +172,3 @@ export const TaskSidebar: FC<TaskSidebarProps> = ({ task }) => {
179172
</aside>
180173
);
181174
};
182-
183-
type TaskStatusLinkProps = {
184-
uri: string;
185-
};
186-
187-
const TaskStatusLink: FC<TaskStatusLinkProps> = ({ uri }) => {
188-
let icon = <ExternalLinkIcon />;
189-
let label = truncateURI(uri);
190-
191-
if (uri.startsWith("https://github.com")) {
192-
const issueNumber = uri.split("/").pop();
193-
const [org, repo] = uri.split("/").slice(3, 5);
194-
const prefix = `${org}/${repo}`;
195-
196-
if (uri.includes("pull/")) {
197-
icon = <GitPullRequestArrowIcon />;
198-
label = issueNumber
199-
? `${prefix}#${issueNumber}`
200-
: `${prefix} Pull Request`;
201-
} else if (uri.includes("issues/")) {
202-
icon = <BugIcon />;
203-
label = issueNumber ? `${prefix}#${issueNumber}` : `${prefix} Issue`;
204-
} else {
205-
icon = <GitHub />;
206-
label = `${org}/${repo}`;
207-
}
208-
}
209-
210-
return (
211-
<Button asChild variant="outline" size="sm" className="min-w-0">
212-
<a href={uri} target="_blank" rel="noreferrer">
213-
{icon}
214-
{label}
215-
</a>
216-
</Button>
217-
);
218-
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { TaskStatusLink } from "./TaskStatusLink";
3+
4+
const meta: Meta<typeof TaskStatusLink> = {
5+
title: "pages/TaskPage/TaskStatusLink",
6+
component: TaskStatusLink,
7+
// Add a wrapper to test truncation.
8+
decorators: [
9+
(Story) => (
10+
<div style={{ display: "flex", width: "200px" }}>
11+
<Story />
12+
</div>
13+
),
14+
],
15+
};
16+
17+
export default meta;
18+
type Story = StoryObj<typeof TaskStatusLink>;
19+
20+
export const GithubPRNumber: Story = {
21+
args: {
22+
uri: "https://github.com/org/repo/pull/1234",
23+
},
24+
};
25+
26+
export const GitHubPRNoNumber: Story = {
27+
args: {
28+
uri: "https://github.com/org/repo/pull",
29+
},
30+
};
31+
32+
export const GithubIssueNumber: Story = {
33+
args: {
34+
uri: "https://github.com/org/repo/issues/4321",
35+
},
36+
};
37+
38+
export const GithubIssueNoNumber: Story = {
39+
args: {
40+
uri: "https://github.com/org/repo/issues",
41+
},
42+
};
43+
44+
export const GithubOrgRepo: Story = {
45+
args: {
46+
uri: "https://github.com/org/repo",
47+
},
48+
};
49+
50+
export const GithubOrg: Story = {
51+
args: {
52+
uri: "https://github.com/org",
53+
},
54+
};
55+
56+
export const Github: Story = {
57+
args: {
58+
uri: "https://github.com",
59+
},
60+
};
61+
62+
export const File: Story = {
63+
args: {
64+
uri: "file:///path/to/file",
65+
},
66+
};
67+
68+
export const Long: Story = {
69+
args: {
70+
uri: "https://dev.coder.com/this-is-a/long-url/to-test/how-the-truncation/looks",
71+
},
72+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import GitHub from "@mui/icons-material/GitHub";
2+
import { Button } from "components/Button/Button";
3+
import {
4+
BugIcon,
5+
ExternalLinkIcon,
6+
GitPullRequestArrowIcon,
7+
} from "lucide-react";
8+
import type { FC } from "react";
9+
10+
type TaskStatusLinkProps = {
11+
uri: string;
12+
};
13+
14+
export const TaskStatusLink: FC<TaskStatusLinkProps> = ({ uri }) => {
15+
let icon = <ExternalLinkIcon />;
16+
let label = uri;
17+
18+
try {
19+
const parsed = new URL(uri);
20+
switch (parsed.protocol) {
21+
// For file URIs, strip off the `file://`.
22+
case "file:":
23+
label = uri.replace(/^file:\/\//, "");
24+
break;
25+
case "http:":
26+
case "https:":
27+
// For GitHub URIs, use a short representation.
28+
if (parsed.host === "github.com") {
29+
const [_, org, repo, type, number] = parsed.pathname.split("/");
30+
switch (type) {
31+
case "pull":
32+
icon = <GitPullRequestArrowIcon />;
33+
label = number
34+
? `${org}/${repo}#${number}`
35+
: `${org}/${repo} pull request`;
36+
break;
37+
case "issues":
38+
icon = <BugIcon />;
39+
label = number
40+
? `${org}/${repo}#${number}`
41+
: `${org}/${repo} issue`;
42+
break;
43+
default:
44+
icon = <GitHub />;
45+
if (org && repo) {
46+
label = `${org}/${repo}`;
47+
}
48+
break;
49+
}
50+
}
51+
break;
52+
}
53+
} catch (error) {
54+
// Invalid URL, probably.
55+
}
56+
57+
return (
58+
<Button asChild variant="outline" size="sm" className="min-w-0">
59+
<a href={uri} target="_blank" rel="noreferrer">
60+
{icon}
61+
<span className="truncate">{label}</span>
62+
</a>
63+
</Button>
64+
);
65+
};

0 commit comments

Comments
 (0)
0