Skip to content

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy