--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { isWebUri } from 'valid-url';
+
+export default function(url: string): boolean {
+ return /^(\/|scm:)/.test(url) || !!isWebUri(url);
+}
import * as React from 'react';
import { getLinkName } from '../../projectLinks/utils';
import ProjectLinkIcon from '../../../components/icons-components/ProjectLinkIcon';
+import isValidUri from '../../../app/utils/isValidUri';
+import ClearIcon from '../../../components/icons-components/ClearIcon';
interface Props {
link: T.ProjectLink;
}
-export default function MetaLink({ link }: Props) {
- return (
- <li>
- <a className="link-with-icon" href={link.url} rel="nofollow" target="_blank">
- <ProjectLinkIcon className="little-spacer-right" type={link.type} />
- {getLinkName(link)}
- </a>
- </li>
- );
+interface State {
+ expanded: boolean;
+}
+
+export default class MetaLink extends React.PureComponent<Props, State> {
+ state = {
+ expanded: false
+ };
+
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.setState(s => ({ expanded: !s.expanded }));
+ };
+
+ render() {
+ const { link } = this.props;
+ return (
+ <li>
+ <a
+ className="link-with-icon"
+ href={link.url}
+ onClick={!isValidUri(link.url) ? this.handleClick : undefined}
+ rel="nofollow noreferrer noopener"
+ target="_blank">
+ <ProjectLinkIcon className="little-spacer-right" type={link.type} />
+ {getLinkName(link)}
+ </a>
+ {this.state.expanded && (
+ <div className="little-spacer-top copy-paste-link">
+ <input
+ className="overview-key"
+ onClick={(event: React.MouseEvent<HTMLInputElement>) => event.currentTarget.select()}
+ readOnly={true}
+ type="text"
+ value={link.url}
+ />
+ <a className="close" href="#" onClick={this.handleClick}>
+ <ClearIcon />
+ </a>
+ </div>
+ )}
+ </li>
+ );
+ }
}
expect(shallow(<MetaLink link={link} />)).toMatchSnapshot();
});
+it('should render dangerous links as plaintext', () => {
+ const link = {
+ id: '1',
+ name: 'Dangerous',
+ url: 'javascript:alert("hi")',
+ type: 'dangerous'
+ };
+
+ expect(shallow(<MetaLink link={link} />)).toMatchSnapshot();
+});
+
it('should expand and collapse link', () => {
const link = {
id: '1',
<a
className="link-with-icon"
href="scm:git:git@github.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
<a
className="link-with-icon"
href="scm:git:git@github.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
<a
className="link-with-icon"
href="scm:git:git@github.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
<a
className="link-with-icon"
href="http://example.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
<ProjectLinkIcon
</a>
</li>
`;
+
+exports[`should render dangerous links as plaintext 1`] = `
+<li>
+ <a
+ className="link-with-icon"
+ href="javascript:alert(\\"hi\\")"
+ onClick={[Function]}
+ rel="nofollow noreferrer noopener"
+ target="_blank"
+ >
+ <ProjectLinkIcon
+ className="little-spacer-right"
+ type="dangerous"
+ />
+ Dangerous
+ </a>
+</li>
+`;
background-color: transparent !important;
}
+.copy-paste-link .overview-key {
+ width: 90%;
+}
+
+.copy-paste-link .close {
+ color: black;
+ border-bottom: 0;
+ height: 100%;
+ display: inline-block;
+ margin-left: 5px;
+ box-sizing: border-box;
+}
+
+.copy-paste-link .close svg {
+ vertical-align: sub;
+}
+
.overview-deleted-profile,
.overview-deprecated-rules {
margin: 4px -6px 4px;
import ProjectLinkIcon from '../../components/icons-components/ProjectLinkIcon';
import { Button } from '../../components/ui/buttons';
import { translate, translateWithParameters } from '../../helpers/l10n';
+import isValidUri from '../../app/utils/isValidUri';
interface Props {
link: T.ProjectLink;
<tr data-name={link.name}>
<td className="nowrap">{this.renderName(link)}</td>
<td className="nowrap js-url">
- <a href={link.url} rel="nofollow" target="_blank">
- {link.url}
- </a>
+ {isValidUri(link.url) ? (
+ <a href={link.url} rel="nofollow noreferrer noopener" target="_blank">
+ {link.url}
+ </a>
+ ) : (
+ link.url
+ )}
</td>
<td className="thin nowrap">{this.renderDeleteButton(link)}</td>
</tr>
)
).toMatchSnapshot();
});
+
+it('should render dangerous code as plain text', () => {
+ expect(
+ shallow(
+ <LinkRow
+ link={{ id: '12', name: 'dangerous', type: 'dangerous', url: 'javascript:alert("Hello")' }}
+ onDelete={jest.fn()}
+ />
+ )
+ ).toMatchSnapshot();
+});
>
<a
href="http://example.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
http://example.com
</tr>
`;
+exports[`should render dangerous code as plain text 1`] = `
+<tr
+ data-name="dangerous"
+>
+ <td
+ className="nowrap"
+ >
+ <div>
+ <ProjectLinkIcon
+ className="little-spacer-right"
+ type="dangerous"
+ />
+ <div
+ className="display-inline-block text-top"
+ >
+ <span
+ className="js-name"
+ >
+ dangerous
+ </span>
+ </div>
+ </div>
+ </td>
+ <td
+ className="nowrap js-url"
+ >
+ javascript:alert("Hello")
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <ConfirmButton
+ confirmButtonText="delete"
+ confirmData="12"
+ isDestructive={true}
+ modalBody="project_links.are_you_sure_to_delete_x_link.dangerous"
+ modalHeader="project_links.delete_project_link"
+ onConfirm={[MockFunction]}
+ >
+ <Component />
+ </ConfirmButton>
+ </td>
+</tr>
+`;
+
exports[`should render provided link 1`] = `
<tr>
<td
>
<a
href="http://example.com"
- rel="nofollow"
+ rel="nofollow noreferrer noopener"
target="_blank"
>
http://example.com