Enforce HTTPS / Prevent Reverse Tabnabbing (#773)
* working version of test custom rule config * setting no imports to false so tests will pass * adding anchor blank noopener rule, rule currently off to allow tests to pass * removing copied code from tslint-microsoft-contrib * adding tslint-microsoft-contrib to dev deps * extending tslint for external http rule * locking tslint-microsoft-contrib version and turning on target blank noopener rule * final fixes for pull #663 * add noopener noreferrer as needed * fixing false positives for a tags without href * really fix linting errors * fix imports * remove accidently(?) added LedgerNano duplicate file
This commit is contained in:
parent
6e2b74c79a
commit
26619e28cc
|
@ -93,14 +93,22 @@ export default class AccountInfo extends React.Component<Props, State> {
|
||||||
<ul className="AccountInfo-list">
|
<ul className="AccountInfo-list">
|
||||||
{!!blockExplorer && (
|
{!!blockExplorer && (
|
||||||
<li className="AccountInfo-list-item">
|
<li className="AccountInfo-list-item">
|
||||||
<a href={blockExplorer.address(address)} target="_blank">
|
<a
|
||||||
|
href={blockExplorer.address(address)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{`${network.name} (${blockExplorer.name})`}
|
{`${network.name} (${blockExplorer.name})`}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{!!tokenExplorer && (
|
{!!tokenExplorer && (
|
||||||
<li className="AccountInfo-list-item">
|
<li className="AccountInfo-list-item">
|
||||||
<a href={tokenExplorer.address(address)} target="_blank">
|
<a
|
||||||
|
href={tokenExplorer.address(address)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{`Tokens (${tokenExplorer.name})`}
|
{`Tokens (${tokenExplorer.name})`}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -58,6 +58,7 @@ export default class Promos extends React.Component<{}, State> {
|
||||||
className="Promos-promo"
|
className="Promos-promo"
|
||||||
key={promo.href}
|
key={promo.href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
href={promo.href}
|
href={promo.href}
|
||||||
style={{ backgroundColor: promo.color }}
|
style={{ backgroundColor: promo.color }}
|
||||||
>
|
>
|
||||||
|
|
|
@ -19,7 +19,7 @@ const ErrorScreen: React.SFC<Props> = ({ error }) => {
|
||||||
Please contact{' '}
|
Please contact{' '}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
href={`mailto:support@myetherwallet.com?Subject=${SUBJECT}&body=${DESCRIPTION}`}
|
href={`mailto:support@myetherwallet.com?Subject=${SUBJECT}&body=${DESCRIPTION}`}
|
||||||
>
|
>
|
||||||
support@myetherwallet.com
|
support@myetherwallet.com
|
||||||
|
|
|
@ -14,7 +14,12 @@ const TransactionSucceeded = ({ txHash, blockExplorer }: TransactionSucceededPro
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{translateRaw('SUCCESS_3') + txHash}</p>
|
<p>{translateRaw('SUCCESS_3') + txHash}</p>
|
||||||
<a className="btn btn-xs btn-info string" href={txHashLink} target="_blank" rel="noopener">
|
<a
|
||||||
|
className="btn btn-xs btn-info string"
|
||||||
|
href={txHashLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
Verify Transaction
|
Verify Transaction
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -61,7 +61,11 @@ export default class GasPriceDropdown extends Component<Props> {
|
||||||
<code>21 GWEI</code>.
|
<code>21 GWEI</code>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href={`${knowledgeBaseURL}/gas/what-is-gas-ethereum`} target="_blank">
|
<a
|
||||||
|
href={`${knowledgeBaseURL}/gas/what-is-gas-ethereum`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
Read more
|
Read more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -35,7 +35,13 @@ class NavigationLink extends React.Component<Props, {}> {
|
||||||
|
|
||||||
const linkEl =
|
const linkEl =
|
||||||
link.external || !link.to ? (
|
link.external || !link.to ? (
|
||||||
<a className={linkClasses} href={link.to} aria-label={linkLabel} target="_blank">
|
<a
|
||||||
|
className={linkClasses}
|
||||||
|
href={link.to}
|
||||||
|
aria-label={linkLabel}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{translate(link.name)}
|
{translate(link.name)}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -292,7 +292,11 @@ class DeterministicWalletsModalClass extends React.Component<Props, State> {
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a target="_blank" href={`https://ethplorer.io/address/${wallet.address}`}>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href={`https://ethplorer.io/address/${wallet.address}`}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<i className="DWModal-addresses-table-more" />
|
<i className="DWModal-addresses-table-more" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -310,7 +314,9 @@ function mapStateToProps(state: AppState) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeterministicWalletsModal = connect(mapStateToProps, {
|
const DeterministicWalletsModal = connect(mapStateToProps, {
|
||||||
getDeterministicWallets,
|
getDeterministicWallets,
|
||||||
setDesiredToken
|
setDesiredToken
|
||||||
})(DeterministicWalletsModalClass);
|
})(DeterministicWalletsModalClass);
|
||||||
|
|
||||||
|
export default DeterministicWalletsModal;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import './LedgerNano.scss';
|
import './LedgerNano.scss';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate, { translateRaw } from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
import { DeterministicWalletsModal } from './DeterministicWalletsModal';
|
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||||
import { LedgerWallet } from 'libs/wallet';
|
import { LedgerWallet } from 'libs/wallet';
|
||||||
import Ledger3 from 'vendor/ledger3';
|
import Ledger3 from 'vendor/ledger3';
|
||||||
import LedgerEth from 'vendor/ledger-eth';
|
import LedgerEth from 'vendor/ledger-eth';
|
||||||
|
@ -81,7 +81,7 @@ export class LedgerNanoSDecrypt extends Component<Props, State> {
|
||||||
className="LedgerDecrypt-buy btn btn-sm btn-default"
|
className="LedgerDecrypt-buy btn btn-sm btn-default"
|
||||||
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
|
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{translate('Don’t have a Ledger? Order one now!')}
|
{translate('Don’t have a Ledger? Order one now!')}
|
||||||
</a>
|
</a>
|
||||||
|
@ -92,9 +92,9 @@ export class LedgerNanoSDecrypt extends Component<Props, State> {
|
||||||
Guides:
|
Guides:
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href="http://support.ledgerwallet.com/knowledge_base/topics/how-to-use-myetherwallet-with-ledger"
|
href="https://support.ledgerwallet.com/knowledge_base/topics/how-to-use-myetherwallet-with-ledger"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
How to use MyEtherWallet with your Nano S
|
How to use MyEtherWallet with your Nano S
|
||||||
</a>
|
</a>
|
||||||
|
@ -103,7 +103,7 @@ export class LedgerNanoSDecrypt extends Component<Props, State> {
|
||||||
<a
|
<a
|
||||||
href="https://ledger.groovehq.com/knowledge_base/topics/how-to-secure-your-eth-tokens-augur-rep-dot-dot-dot-with-your-nano-s"
|
href="https://ledger.groovehq.com/knowledge_base/topics/how-to-secure-your-eth-tokens-augur-rep-dot-dot-dot-with-your-nano-s"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
How to secure your tokens with your Nano S
|
How to secure your tokens with your Nano S
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { mnemonicToSeed, validateMnemonic } from 'bip39';
|
||||||
import DPATHS from 'config/dpaths';
|
import DPATHS from 'config/dpaths';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate, { translateRaw } from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
import { DeterministicWalletsModal } from './DeterministicWalletsModal';
|
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||||
import { formatMnemonic } from 'utils/formatters';
|
import { formatMnemonic } from 'utils/formatters';
|
||||||
|
|
||||||
const DEFAULT_PATH = DPATHS.MNEMONIC[0].value;
|
const DEFAULT_PATH = DPATHS.MNEMONIC[0].value;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TrezorWallet } from 'libs/wallet';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate, { translateRaw } from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
import TrezorConnect from 'vendor/trezor-connect';
|
import TrezorConnect from 'vendor/trezor-connect';
|
||||||
import { DeterministicWalletsModal } from './DeterministicWalletsModal';
|
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||||
import './Trezor.scss';
|
import './Trezor.scss';
|
||||||
import { Spinner } from 'components/ui';
|
import { Spinner } from 'components/ui';
|
||||||
const DEFAULT_PATH = DPATHS.TREZOR[0].value;
|
const DEFAULT_PATH = DPATHS.TREZOR[0].value;
|
||||||
|
@ -53,7 +53,7 @@ export class TrezorDecrypt extends Component<Props, State> {
|
||||||
className="TrezorDecrypt-buy btn btn-sm btn-default"
|
className="TrezorDecrypt-buy btn btn-sm btn-default"
|
||||||
href="https://trezor.io/?a=myetherwallet.com"
|
href="https://trezor.io/?a=myetherwallet.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{translate('Don’t have a TREZOR? Order one now!')}
|
{translate('Don’t have a TREZOR? Order one now!')}
|
||||||
</a>
|
</a>
|
||||||
|
@ -65,7 +65,7 @@ export class TrezorDecrypt extends Component<Props, State> {
|
||||||
<a
|
<a
|
||||||
href="https://blog.trezor.io/trezor-integration-with-myetherwallet-3e217a652e08"
|
href="https://blog.trezor.io/trezor-integration-with-myetherwallet-3e217a652e08"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
How to use TREZOR with MyEtherWallet
|
How to use TREZOR with MyEtherWallet
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface Props {
|
||||||
|
|
||||||
const Help = ({ size = 'x1', link }: Props) => {
|
const Help = ({ size = 'x1', link }: Props) => {
|
||||||
return (
|
return (
|
||||||
<a href={link} className={`Help Help-${size}`} target={'_blank'}>
|
<a href={link} className={`Help Help-${size}`} target="_blank" rel="noopener noreferrer">
|
||||||
<img src={icon} />
|
<img src={icon} />
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,7 +36,7 @@ interface NewTabLinkProps extends AAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
const NewTabLink = ({ content, children, ...rest }: NewTabLinkProps) => (
|
const NewTabLink = ({ content, children, ...rest }: NewTabLinkProps) => (
|
||||||
<a target="_blank" rel="noopener" {...rest}>
|
<a target="_blank" rel="noopener noreferrer" {...rest}>
|
||||||
{content || children}
|
{content || children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,6 +17,7 @@ const Help = () => (
|
||||||
<a
|
<a
|
||||||
href="https://www.reddit.com/r/ethereum/comments/47nkoi/psa_check_your_ethaddressorg_wallets_and_any/d0eo45o"
|
href="https://www.reddit.com/r/ethereum/comments/47nkoi/psa_check_your_ethaddressorg_wallets_and_any/d0eo45o"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span className="text-danger">{translate('HELP_Warning')}</span>
|
<span className="text-danger">{translate('HELP_Warning')}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -25,7 +26,7 @@ const Help = () => (
|
||||||
<li>
|
<li>
|
||||||
<h3>
|
<h3>
|
||||||
This page is deprecated. Please check out our more up-to-date and searchable{' '}
|
This page is deprecated. Please check out our more up-to-date and searchable{' '}
|
||||||
<a href={knowledgeBaseURL} target="_blank">
|
<a href={knowledgeBaseURL} target="_blank" rel="noopener noreferrer">
|
||||||
Knowledge Base.{' '}
|
Knowledge Base.{' '}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default class BitcoinQR extends Component<Props, {}> {
|
||||||
Orders that take too long will have to be processed manually & and may delay the
|
Orders that take too long will have to be processed manually & and may delay the
|
||||||
amount of time it takes to receive your coins.
|
amount of time it takes to receive your coins.
|
||||||
<br />
|
<br />
|
||||||
<a href="https://shapeshift.io/#/btcfee" target="_blank" rel="noopener">
|
<a href="https://shapeshift.io/#/btcfee" target="_blank" rel="noopener noreferrer">
|
||||||
Please use the recommended TX fees seen here.
|
Please use the recommended TX fees seen here.
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -88,7 +88,12 @@ export default class CurrentRates extends Component<Props> {
|
||||||
|
|
||||||
<section className="SwapRates-panel row">
|
<section className="SwapRates-panel row">
|
||||||
{children}
|
{children}
|
||||||
<a className="SwapRates-panel-logo" href={providerURL} target="_blank">
|
<a
|
||||||
|
className="SwapRates-panel-logo"
|
||||||
|
href={providerURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<img src={providerLogo} width={120} height={49} />
|
<img src={providerLogo} width={120} height={49} />
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -27,7 +27,12 @@ export default class SwapInfoHeaderTitle extends Component<SwapInfoHeaderTitlePr
|
||||||
<h3 className="SwapInfo-top-title">{translate('SWAP_information')}</h3>
|
<h3 className="SwapInfo-top-title">{translate('SWAP_information')}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-xs-3">
|
<div className="col-xs-3">
|
||||||
<a className="SwapInfo-top-logo" href={bityReferralURL} target="_blank" rel="noopener">
|
<a
|
||||||
|
className="SwapInfo-top-logo"
|
||||||
|
href={bityReferralURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<img className="SwapInfo-top-logo-img" src={logoToRender} />
|
<img className="SwapInfo-top-logo-img" src={logoToRender} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default class SwapProgress extends Component<Props, State> {
|
||||||
if (destinationId !== 'BTC') {
|
if (destinationId !== 'BTC') {
|
||||||
link = bityConfig.ETHTxExplorer(outputTx);
|
link = bityConfig.ETHTxExplorer(outputTx);
|
||||||
linkElement = (
|
linkElement = (
|
||||||
<a href={link} target="_blank" rel="noopener">
|
<a href={link} target="_blank" rel="noopener noreferrer">
|
||||||
{notificationMessage}
|
{notificationMessage}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -57,7 +57,7 @@ export default class SwapProgress extends Component<Props, State> {
|
||||||
} else {
|
} else {
|
||||||
link = bityConfig.BTCTxExplorer(outputTx);
|
link = bityConfig.BTCTxExplorer(outputTx);
|
||||||
linkElement = (
|
linkElement = (
|
||||||
<a href={link} target="_blank" rel="noopener">
|
<a href={link} target="_blank" rel="noopener noreferrer">
|
||||||
{notificationMessage}
|
{notificationMessage}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you are not sure why you are seeing this message, or are unsure of how to enable Javascript, please visit
|
If you are not sure why you are seeing this message, or are unsure of how to enable Javascript, please visit
|
||||||
<a href="https://www.enable-javascript.com/" rel="noopener" target="_blank">enable-javascript.com</a>
|
<a href="https://www.enable-javascript.com/" rel="noopener noreferrer" target="_blank">enable-javascript.com</a>
|
||||||
to learn more.
|
to learn more.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,18 +41,18 @@
|
||||||
to a laptop or computer to continue using MyEtherWallet.
|
to a laptop or computer to continue using MyEtherWallet.
|
||||||
</p>
|
</p>
|
||||||
<div class="BadBrowser-content-browsers is-desktop">
|
<div class="BadBrowser-content-browsers is-desktop">
|
||||||
<a class="BadBrowser-content-browsers-browser firefox" href="https://www.mozilla.org/en-US/firefox/new/" rel="noopener" target="_blank">
|
<a class="BadBrowser-content-browsers-browser firefox" href="https://www.mozilla.org/en-US/firefox/new/" rel="noopener noreferrer" target="_blank">
|
||||||
<span class="BadBrowser-content-browsers-browser-name">
|
<span class="BadBrowser-content-browsers-browser-name">
|
||||||
Firefox
|
Firefox
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="BadBrowser-content-browsers-browser chrome" href="https://www.google.com/chrome/browser/desktop/index.html" rel="noopener"
|
<a class="BadBrowser-content-browsers-browser chrome" href="https://www.google.com/chrome/browser/desktop/index.html" rel="noopener noreferrer"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<span class="BadBrowser-content-browsers-browser-name">
|
<span class="BadBrowser-content-browsers-browser-name">
|
||||||
Chrome
|
Chrome
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="BadBrowser-content-browsers-browser opera" href="http://www.opera.com/" rel="noopener" target="_blank">
|
<a class="BadBrowser-content-browsers-browser opera" href="http://www.opera.com/" rel="noopener noreferrer" target="_blank">
|
||||||
<span class="BadBrowser-content-browsers-browser-name">
|
<span class="BadBrowser-content-browsers-browser-name">
|
||||||
Opera
|
Opera
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
"use strict";
|
||||||
|
var __extends = (this && this.__extends) || (function () {
|
||||||
|
var extendStatics = Object.setPrototypeOf ||
|
||||||
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||||
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||||
|
return function (d, b) {
|
||||||
|
extendStatics(d, b);
|
||||||
|
function __() { this.constructor = d; }
|
||||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
exports.__esModule = true;
|
||||||
|
var ts = require("typescript");
|
||||||
|
var Lint = require("tslint");
|
||||||
|
var ErrorTolerantWalker_1 = require("../node_modules/tslint-microsoft-contrib/utils/ErrorTolerantWalker");
|
||||||
|
var JsxAttribute_1 = require("../node_modules/tslint-microsoft-contrib/utils/JsxAttribute");
|
||||||
|
var FAILURE_STRING = 'Anchor tags with an external link must use https';
|
||||||
|
/**
|
||||||
|
* Implementation of the no-external-http-link rule.
|
||||||
|
*/
|
||||||
|
var Rule = /** @class */ (function (_super) {
|
||||||
|
__extends(Rule, _super);
|
||||||
|
function Rule() {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
Rule.prototype.apply = function (sourceFile) {
|
||||||
|
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
|
||||||
|
return this.applyWithWalker(new NoExternalHttpLinkRuleWalker(sourceFile, this.getOptions()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Rule.metadata = {
|
||||||
|
ruleName: 'tno-external-http-link',
|
||||||
|
type: 'functionality',
|
||||||
|
description: 'Anchor tags with an external link must use https',
|
||||||
|
options: null,
|
||||||
|
optionsDescription: '',
|
||||||
|
typescriptOnly: true,
|
||||||
|
issueClass: 'SDL',
|
||||||
|
issueType: 'Error',
|
||||||
|
severity: 'Critical',
|
||||||
|
level: 'Mandatory',
|
||||||
|
group: 'Security',
|
||||||
|
commonWeaknessEnumeration: '242,676'
|
||||||
|
};
|
||||||
|
return Rule;
|
||||||
|
}(Lint.Rules.AbstractRule));
|
||||||
|
exports.Rule = Rule;
|
||||||
|
var NoExternalHttpLinkRuleWalker = /** @class */ (function (_super) {
|
||||||
|
__extends(NoExternalHttpLinkRuleWalker, _super);
|
||||||
|
function NoExternalHttpLinkRuleWalker() {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
NoExternalHttpLinkRuleWalker.prototype.visitJsxElement = function (node) {
|
||||||
|
var openingElement = node.openingElement;
|
||||||
|
this.validateOpeningElement(openingElement);
|
||||||
|
_super.prototype.visitJsxElement.call(this, node);
|
||||||
|
};
|
||||||
|
NoExternalHttpLinkRuleWalker.prototype.visitJsxSelfClosingElement = function (node) {
|
||||||
|
this.validateOpeningElement(node);
|
||||||
|
_super.prototype.visitJsxSelfClosingElement.call(this, node);
|
||||||
|
};
|
||||||
|
NoExternalHttpLinkRuleWalker.prototype.validateOpeningElement = function (openingElement) {
|
||||||
|
if (openingElement.tagName.getText() === 'a') {
|
||||||
|
var allAttributes = JsxAttribute_1.getJsxAttributesFromJsxElement(openingElement);
|
||||||
|
var href = allAttributes.href;
|
||||||
|
if (href !== null && !isSafeHrefAttributeValue(href) && JsxAttribute_1.getStringLiteral(href) !== 'undefined') {
|
||||||
|
this.addFailureAt(openingElement.getStart(), openingElement.getWidth(), FAILURE_STRING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return NoExternalHttpLinkRuleWalker;
|
||||||
|
}(ErrorTolerantWalker_1.ErrorTolerantWalker));
|
||||||
|
function isSafeHrefAttributeValue(attribute) {
|
||||||
|
if (JsxAttribute_1.isEmpty(attribute)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (attribute.initializer.kind === ts.SyntaxKind.JsxExpression) {
|
||||||
|
var expression = attribute.initializer;
|
||||||
|
if (expression.expression !== null &&
|
||||||
|
expression.expression.kind !== ts.SyntaxKind.StringLiteral) {
|
||||||
|
return true; // attribute value is not a string literal, so do not validate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stringValue = JsxAttribute_1.getStringLiteral(attribute);
|
||||||
|
if (stringValue === '#') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (stringValue === null || stringValue.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return stringValue.indexOf('https://') >= 0;
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import * as Lint from 'tslint';
|
||||||
|
|
||||||
|
import { ErrorTolerantWalker } from '../node_modules/tslint-microsoft-contrib/utils/ErrorTolerantWalker';
|
||||||
|
import { ExtendedMetadata } from '../node_modules/tslint-microsoft-contrib/utils/ExtendedMetadata';
|
||||||
|
import { Utils } from '../node_modules/tslint-microsoft-contrib/utils/Utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getJsxAttributesFromJsxElement,
|
||||||
|
getStringLiteral,
|
||||||
|
isEmpty
|
||||||
|
} from '../node_modules/tslint-microsoft-contrib/utils/JsxAttribute';
|
||||||
|
|
||||||
|
const FAILURE_STRING = 'Anchor tags with an external link must use https';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the no-external-http-link rule.
|
||||||
|
*/
|
||||||
|
export class Rule extends Lint.Rules.AbstractRule {
|
||||||
|
public static metadata: ExtendedMetadata = {
|
||||||
|
ruleName: 'tno-external-http-link',
|
||||||
|
type: 'functionality',
|
||||||
|
description: 'Anchor tags with an external link must use https',
|
||||||
|
options: null,
|
||||||
|
optionsDescription: '',
|
||||||
|
typescriptOnly: true,
|
||||||
|
issueClass: 'SDL',
|
||||||
|
issueType: 'Error',
|
||||||
|
severity: 'Critical',
|
||||||
|
level: 'Mandatory',
|
||||||
|
group: 'Security',
|
||||||
|
commonWeaknessEnumeration: '242,676'
|
||||||
|
};
|
||||||
|
|
||||||
|
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
||||||
|
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
|
||||||
|
return this.applyWithWalker(new NoExternalHttpLinkRuleWalker(sourceFile, this.getOptions()));
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoExternalHttpLinkRuleWalker extends ErrorTolerantWalker {
|
||||||
|
protected visitJsxElement(node: ts.JsxElement): void {
|
||||||
|
const openingElement: ts.JsxOpeningElement = node.openingElement;
|
||||||
|
this.validateOpeningElement(openingElement);
|
||||||
|
super.visitJsxElement(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
|
||||||
|
this.validateOpeningElement(node);
|
||||||
|
super.visitJsxSelfClosingElement(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateOpeningElement(openingElement: ts.JsxOpeningLikeElement): void {
|
||||||
|
if (openingElement.tagName.getText() === 'a') {
|
||||||
|
const allAttributes: { [propName: string]: ts.JsxAttribute } = getJsxAttributesFromJsxElement(
|
||||||
|
openingElement
|
||||||
|
);
|
||||||
|
const href: ts.JsxAttribute = allAttributes.href;
|
||||||
|
if (
|
||||||
|
href !== null &&
|
||||||
|
!isSafeHrefAttributeValue(href) &&
|
||||||
|
getStringLiteral(href) !== 'undefined'
|
||||||
|
) {
|
||||||
|
this.addFailureAt(openingElement.getStart(), openingElement.getWidth(), FAILURE_STRING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSafeHrefAttributeValue(attribute: ts.JsxAttribute): boolean {
|
||||||
|
if (isEmpty(attribute)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.initializer.kind === ts.SyntaxKind.JsxExpression) {
|
||||||
|
const expression: ts.JsxExpression = <ts.JsxExpression>attribute.initializer;
|
||||||
|
if (
|
||||||
|
expression.expression !== null &&
|
||||||
|
expression.expression.kind !== ts.SyntaxKind.StringLiteral
|
||||||
|
) {
|
||||||
|
return true; // attribute value is not a string literal, so do not validate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringValue = getStringLiteral(attribute);
|
||||||
|
if (stringValue === '#') {
|
||||||
|
return true;
|
||||||
|
} else if (stringValue === null || stringValue.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringValue.indexOf('https://') >= 0;
|
||||||
|
}
|
|
@ -109,6 +109,7 @@
|
||||||
"ts-loader": "3.2.0",
|
"ts-loader": "3.2.0",
|
||||||
"tslint": "5.8.0",
|
"tslint": "5.8.0",
|
||||||
"tslint-config-prettier": "1.6.0",
|
"tslint-config-prettier": "1.6.0",
|
||||||
|
"tslint-microsoft-contrib": "5.0.1",
|
||||||
"tslint-react": "3.3.3",
|
"tslint-react": "3.3.3",
|
||||||
"types-rlp": "0.0.1",
|
"types-rlp": "0.0.1",
|
||||||
"typescript": "2.6.2",
|
"typescript": "2.6.2",
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
"no-var-requires": false,
|
"no-var-requires": false,
|
||||||
"jsx-wrap-multiline": false,
|
"jsx-wrap-multiline": false,
|
||||||
"comment-format": false,
|
"comment-format": false,
|
||||||
"ordered-imports": false
|
"ordered-imports": false,
|
||||||
|
"react-anchor-blank-noopener": true,
|
||||||
|
"no-external-http-link": true
|
||||||
},
|
},
|
||||||
"rulesDirectory": []
|
"rulesDirectory": ["node_modules/tslint-microsoft-contrib", "custom_linting_rules"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue