Commits (15)
......@@ -4,6 +4,11 @@ include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
variables:
FORCE_DEPLOY_DOCS:
value: "false"
description: 'Set to "true" to force docs to be redeployed. Ensure no commits references documentation about non-released features. Only relevant on "master"'
stages:
- prepare
- build
......@@ -44,6 +49,7 @@ pages:
- public
rules:
- if: '$CI_COMMIT_REF_NAME == "master" && $CI_COMMIT_MESSAGE =~ /^chore\(release\):/'
- if: '$CI_COMMIT_REF_NAME == "master" && $FORCE_DEPLOY_DOCS == "true"'
script:
- npm run --if-present build
- npm run build:docs
......
# html-validate changelog
## [6.1.0](https://gitlab.com/html-validate/html-validate/compare/v6.0.2...v6.1.0) (2021-10-02)
### Features
- **rules:** `allowed-links` support `include` and `exclude` ([a3f7b6a](https://gitlab.com/html-validate/html-validate/commit/a3f7b6aa2414ab41239ec1aed6d463502575bb13))
### Bug Fixes
- handle multiline templating strings ([cddf7d5](https://gitlab.com/html-validate/html-validate/commit/cddf7d5e66ad3ce3f49b32b5dea873b04f9b6f7d)), closes [#134](https://gitlab.com/html-validate/html-validate/issues/134)
### [6.0.2](https://gitlab.com/html-validate/html-validate/compare/v6.0.1...v6.0.2) (2021-09-27)
### Bug Fixes
......
......@@ -54,6 +54,35 @@ Array [
exports[`docs/rules/allowed-links.md inline validation: base-valid 1`] = `Array []`;
exports[`docs/rules/allowed-links.md inline validation: external-include 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 10,
"context": "external",
"line": 5,
"message": "External link to this destination is not allowed by current configuration",
"offset": 77,
"ruleId": "allowed-links",
"ruleUrl": "https://html-validate.org/rules/allowed-links.html",
"selector": "a > a",
"severity": 2,
"size": 17,
},
],
"source": "<!-- allowed -->
<a href=\\"//foo.example.net\\">
<!-- not allowed -->
<a href=\\"//bar.example.net\\">",
"warningCount": 0,
},
]
`;
exports[`docs/rules/allowed-links.md inline validation: external-invalid 1`] = `
Array [
Object {
......
......@@ -9,6 +9,11 @@ markup["absolute-invalid"] = `<a href="/foo">`;
markup["absolute-valid"] = `<a href="../foo">`;
markup["base-invalid"] = `<a href="foo">`;
markup["base-valid"] = `<a href="./foo">`;
markup["external-include"] = `<!-- allowed -->
<a href="//foo.example.net">
<!-- not allowed -->
<a href="//bar.example.net">`;
describe("docs/rules/allowed-links.md", () => {
it("inline validation: external-invalid", () => {
......@@ -59,4 +64,10 @@ describe("docs/rules/allowed-links.md", () => {
const report = htmlvalidate.validateString(markup["base-valid"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: external-include", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({"rules":{"allowed-links":["error",{"allowExternal":{"include":["^//foo.example.net"]}}]}});
const report = htmlvalidate.validateString(markup["external-include"]);
expect(report.results).toMatchSnapshot();
});
});
......@@ -43,6 +43,7 @@ This rule takes an optional object:
### `allowExternal`
By setting `allowExternal` to `false` any link to a external resource will be disallowed.
This can also be set to an object (see below regarding `include` and `exclude` lists).
<validate name="external-invalid" rules="allowed-links" allowed-links='{"allowExternal": false}'>
<a href="http://example.net/foo">
......@@ -55,6 +56,7 @@ By setting `allowExternal` to `false` any link to a external resource will be di
### `allowRelative`
By setting `allowRelative` to `false` any link with a relative url will be disallowed.
This can also be set to an object (see below regarding `include` and `exclude` lists).
<validate name="relative-invalid" rules="allowed-links" allowed-links='{"allowRelative": false}'>
<a href="../foo">
......@@ -67,6 +69,7 @@ By setting `allowRelative` to `false` any link with a relative url will be disal
### `allowAbsolute`
By setting `allowAbsolute` to `false` any link with a absolute url will be disallowed.
This can also be set to an object (see below regarding `include` and `exclude` lists).
<validate name="absolute-invalid" rules="allowed-links" allowed-links='{"allowAbsolute": false}'>
<a href="/foo">
......@@ -90,3 +93,35 @@ Effectively this also means that links to files in the same folder must use `./t
<validate name="base-valid" rules="allowed-links" allowed-links='{"allowBase": false}'>
<a href="./foo">
</validate>
### Using `include` and `exclude`
In addition to a boolean value `allowExternal`, `allowRelative` and `allowAbsolute` can also be set to an object with the `include` and `exclude` properties for a more fine-grained control of what link destinations should be considered valid.
Each property is a list of regular expressions matched against the link destination.
- When `include` is set each link must match at least one entry to be valid.
- When `exclude` is set each link must not match any entries to be valid.
- The two properties are not mutually exclusive, both can be enabled at the same time.
For instance, `allowExternal.include` can be used to set a whitelist of valid external links while disallowing all others.
In this case external links to `foo.example.net` is valid but any other would yield an error:
```json
{
"allowExternal": {
"include": ["^//foo.example.net"]
}
}
```
<validate name="external-include" rules="allowed-links" allowed-links='{"allowExternal": {"include": ["^//foo.example.net"]}}'>
<!-- allowed -->
<a href="//foo.example.net">
<!-- not allowed -->
<a href="//bar.example.net">
</validate>
## Version history
- 6.1.0 - Added support for `include` and `exclude`.
......@@ -42,6 +42,8 @@ it("should be valid", () => {
## Configuration
### Global configuration (accross all specs)
`html-validate` configuration can be passed in `cypress/plugins/index.js`:
```js
......@@ -51,3 +53,40 @@ htmlvalidate.install(on, {
},
});
```
### Local configuration (for one spec)
`html-validate` configuration can be passed in the function call within a spec:
```js
cy.htmlvalidate({
rules: {
"prefer-native-element": [
"error",
{
exclude: ["button"],
},
],
},
});
```
It is also possible to add both configuration & options in the function call:
```js
cy.htmlvalidate(
{
rules: {
"prefer-native-element": [
"error",
{
exclude: ["button"],
},
],
},
},
{
exclude: ["form"],
}
);
```
This diff is collapsed.
{
"name": "html-validate",
"version": "6.0.2",
"version": "6.1.0",
"description": "Offline html5 validator",
"keywords": [
"html",
......@@ -152,7 +152,7 @@
"devDependencies": {
"@babel/core": "7.15.5",
"@babel/preset-env": "7.15.6",
"@commitlint/cli": "13.1.0",
"@commitlint/cli": "13.2.0",
"@html-validate/commitlint-config": "2.0.0",
"@html-validate/eslint-config": "4.4.8",
"@html-validate/eslint-config-jest": "4.4.2",
......@@ -175,7 +175,7 @@
"@types/node": "12.20.27",
"@types/prompts": "2.0.14",
"@types/semver": "7.3.8",
"autoprefixer": "10.3.5",
"autoprefixer": "10.3.6",
"babar": "0.2.0",
"babelify": "10.0.0",
"bootstrap-sass": "3.4.1",
......@@ -195,8 +195,8 @@
"grunt-sass": "3.1.0",
"highlight.js": "11.2.0",
"husky": "7.0.2",
"jest": "27.2.2",
"jest-diff": "27.2.2",
"jest": "27.2.4",
"jest-diff": "27.2.4",
"jquery": "3.6.0",
"lint-staged": "11.1.2",
"load-grunt-tasks": "5.1.0",
......@@ -205,7 +205,7 @@
"npm-pkg-lint": "1.5.0",
"postcss": "8.3.8",
"prettier": "2.4.1",
"rollup": "2.57.0",
"rollup": "2.58.0",
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-dts": "4.0.0",
"sass": "1.42.1",
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`regression tests test-files/issues/issue-134-multiline-template.html 1`] = `Array []`;
exports[`regression tests test-files/issues/issue27-disable-block.html 1`] = `Array []`;
exports[`regression tests test-files/issues/issue35-dynamic-values.html 1`] = `Array []`;
......
......@@ -786,11 +786,14 @@ describe("lexer", () => {
describe("should not choke on templating", () => {
it.each`
input
${"<% ... %>"}
${"<? ... ?>"}
${"<$ ... $>"}
`("$input", ({ input }) => {
input | description
${"<% ... %>"} | ${"<% ... %>"}
${"<%\n...\n%>"} | ${"<% ... %> (with newlines)"}
${"<? ... ?>"} | ${"<? ... ?>"}
${"<?\n...\n?>"} | ${"<? ... ?> (with newlines)"}
${"<$ ... $>"} | ${"<$ ... $>"}
${"<$\n...\n$>"} | ${"<$ ... $> (with newlines)"}
`("$description", ({ input }) => {
const token = lexer.tokenize(inlineSource(input));
expect(token.next()).toBeToken({
type: TokenType.TEMPLATING,
......
......@@ -15,7 +15,7 @@ const MATCH_XML_TAG = /^<\?xml.*?\?>\s+/;
const MATCH_TAG_OPEN = /^<(\/?)([a-zA-Z0-9\-:]+)/; // https://www.w3.org/TR/html/syntax.html#start-tags
const MATCH_TAG_CLOSE = /^\/?>/;
const MATCH_TEXT = /^[^]*?(?=(?:[ \t]*(?:\r\n|\r|\n)|<[^ ]|$))/;
const MATCH_TEMPLATING = /^(?:<%.*?%>|<\?.*?\?>|<\$.*?\$>)/;
const MATCH_TEMPLATING = /^(?:<%.*?%>|<\?.*?\?>|<\$.*?\$>)/s;
const MATCH_TAG_LOOKAHEAD = /^[^]*?(?=<|$)/;
const MATCH_ATTR_START = /^([^\t\r\n\f \/><"'=]+)/; // https://www.w3.org/TR/html/syntax.html#elements-attributes
const MATCH_ATTR_SINGLE = /^(\s*=\s*)'([^']*?)(')/;
......
import HtmlValidate from "../htmlvalidate";
import "../jest";
import { processAttribute } from "../transform/mocks/attribute";
import { Style } from "./allowed-links";
import { Style, matchList, AllowList } from "./allowed-links";
describe("matchList", () => {
it("should match if no lists are present", () => {
expect.assertions(1);
const list: AllowList<RegExp> = {
include: null,
exclude: null,
};
expect(matchList("foo", list)).toBeTruthy();
});
it('should match if value is allowed by one "allow" regexp', () => {
expect.assertions(5);
const list: AllowList<RegExp> = {
include: [/^foo/, /^bar$/],
exclude: null,
};
expect(matchList("foo", list)).toBeTruthy();
expect(matchList("foobar", list)).toBeTruthy();
expect(matchList("bar", list)).toBeTruthy();
expect(matchList("barfoo", list)).toBeFalsy();
expect(matchList("baz", list)).toBeFalsy();
});
it('should match if value is not disallowed by any "disallow" regexp', () => {
expect.assertions(5);
const list: AllowList<RegExp> = {
include: null,
exclude: [/^foo/, /^bar$/],
};
expect(matchList("foo", list)).toBeFalsy();
expect(matchList("foobar", list)).toBeFalsy();
expect(matchList("bar", list)).toBeFalsy();
expect(matchList("barfoo", list)).toBeTruthy();
expect(matchList("baz", list)).toBeTruthy();
});
it('should match if value if both "allow" and "disallow" matches', () => {
expect.assertions(5);
const list: AllowList<RegExp> = {
include: [/^foo/],
exclude: [/bar$/],
};
expect(matchList("foo", list)).toBeTruthy(); // prefix allowed
expect(matchList("foobar", list)).toBeFalsy(); // suffix disallowd
expect(matchList("foobaz", list)).toBeTruthy(); // prefix allowed
expect(matchList("bar", list)).toBeFalsy(); // prefix not allowed
expect(matchList("barfoo", list)).toBeFalsy(); // prefix not allowed
});
});
describe("rule allowed-links", () => {
let htmlvalidate: HtmlValidate;
......@@ -70,19 +120,19 @@ describe("rule allowed-links", () => {
expect(report).toHaveError("allowed-links", "Link destination must not be external url");
});
it("should not report error link is absolute", () => {
it("should not report error when link is absolute", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="/foo"></a>');
expect(report).toBeValid();
});
it("should not report error link is relative to path", () => {
it("should not report error when link is relative to path", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="./foo"></a>');
expect(report).toBeValid();
});
it("should not report error link is relative to base", () => {
it("should not report error when link is relative to base", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="foo"></a>');
expect(report).toBeValid();
......@@ -109,20 +159,20 @@ describe("rule allowed-links", () => {
expect(report).toBeValid();
});
it("should not report error link is absolute", () => {
it("should not report error when link is absolute", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="/foo"></a>');
expect(report).toBeValid();
});
it("should report error link is relative to path", () => {
it("should report error when link is relative to path", () => {
expect.assertions(2);
const report = htmlvalidate.validateString('<a href="./foo"></a>');
expect(report).toBeInvalid();
expect(report).toHaveError("allowed-links", "Link destination must not be relative url");
});
it("should report error link is relative to base", () => {
it("should report error when link is relative to base", () => {
expect.assertions(2);
const report = htmlvalidate.validateString('<a href="foo"></a>');
expect(report).toBeInvalid();
......@@ -150,19 +200,19 @@ describe("rule allowed-links", () => {
expect(report).toBeValid();
});
it("should not report error link is absolute", () => {
it("should not report error when link is absolute", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="/foo"></a>');
expect(report).toBeValid();
});
it("should not report error link is relative to path", () => {
it("should not report error when link is relative to path", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="./foo"></a>');
expect(report).toBeValid();
});
it("should report error link is relative to base", () => {
it("should report error when link is relative to base", () => {
expect.assertions(2);
const report = htmlvalidate.validateString('<a href="foo"></a>');
expect(report).toBeInvalid();
......@@ -193,26 +243,206 @@ describe("rule allowed-links", () => {
expect(report).toBeValid();
});
it("should report error link is absolute", () => {
it("should report error when link is absolute", () => {
expect.assertions(2);
const report = htmlvalidate.validateString('<a href="/foo"></a>');
expect(report).toBeInvalid();
expect(report).toHaveError("allowed-links", "Link destination must not be absolute url");
});
it("should not report error link is relative to path", () => {
it("should not report error when link is relative to path", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="./foo"></a>');
expect(report).toBeValid();
});
it("should report error link is relative to base", () => {
it("should report error when link is relative to base", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<a href="foo"></a>');
expect(report).toBeValid();
});
});
describe("include", () => {
beforeAll(() => {
htmlvalidate = new HtmlValidate({
root: true,
rules: {
"allowed-links": [
"error",
{
allowExternal: { include: ["^//example.net"] },
allowRelative: { include: ["\\.png$"] },
allowAbsolute: { include: ["^/foobar/"] },
},
],
},
});
});
it("should report error when external link is not allowed", () => {
expect.assertions(2);
const markup = '<a href="//example.org/foo"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"External link to this destination is not allowed by current configuration"
);
});
it("should report error when relative link is not allowed", () => {
expect.assertions(2);
const markup = '<img src="../foo.jpg">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"Relative link to this destination is not allowed by current configuration"
);
});
it("should report error when base relative link is not allowed", () => {
expect.assertions(2);
const markup = '<img src="foo.jpg">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"Relative link to this destination is not allowed by current configuration"
);
});
it("should report error when absolute link is not allowed", () => {
expect.assertions(2);
const markup = '<a href="/folder"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"Absolute link to this destination is not allowed by current configuration"
);
});
it("should not report error when external link is allowed", () => {
expect.assertions(1);
const markup = '<a href="//example.net/foo"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error when relative link is allowed", () => {
expect.assertions(1);
const markup = '<img src="../foo.png">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error when base relative link is allowed", () => {
expect.assertions(1);
const markup = '<img src="foo.png">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error when absolute link is allowed", () => {
expect.assertions(1);
const markup = '<a href="/foobar/baz"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
});
describe("exclude", () => {
beforeAll(() => {
htmlvalidate = new HtmlValidate({
root: true,
rules: {
"allowed-links": [
"error",
{
allowExternal: { exclude: ["^//example.net"] },
allowRelative: { exclude: ["\\.png$"] },
allowAbsolute: { exclude: ["^/foobar/"] },
},
],
},
});
});
it("should report error when external link is not allowed", () => {
expect.assertions(2);
const markup = '<a href="//example.net/foo"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"External link to this destination is not allowed by current configuration"
);
});
it("should report error when relative link is not allowed", () => {
expect.assertions(2);
const markup = '<img src="../foo.png">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"Relative link to this destination is not allowed by current configuration"
);
});
it("should report error when base relative link is not allowed", () => {
expect.assertions(2);
const markup = '<img src="foo.png">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"Relative link to this destination is not allowed by current configuration"
);
});
it("should report error when absolute link is not allowed", () => {
expect.assertions(2);
const markup = '<a href="/foobar/baz"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"allowed-links",
"Absolute link to this destination is not allowed by current configuration"
);
});
it("should not report error when external link is allowed", () => {
expect.assertions(1);
const markup = '<a href="//example.org/foo"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error when relative link is allowed", () => {
expect.assertions(1);
const markup = '<img src="../foo.jpg">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error when base relative link is allowed", () => {
expect.assertions(1);
const markup = '<img src="foo.jpg">';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error when absolute link is allowed", () => {
expect.assertions(1);
const markup = '<a href="/folder"></a>';
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
});
it("should contain documentation", () => {
expect.assertions(1);
htmlvalidate = new HtmlValidate({
......
......@@ -10,10 +10,18 @@ export const enum Style {
ANCHOR = "anchor",
}
/**
* @internal
*/
export interface AllowList<T> {
include: T[] | null;
exclude: T[] | null;
}
interface RuleOptions {
allowExternal: boolean;
allowRelative: boolean;
allowAbsolute: boolean;
allowExternal: boolean | AllowList<string>;
allowRelative: boolean | AllowList<string>;
allowAbsolute: boolean | AllowList<string>;
allowBase: boolean;
}
......@@ -39,25 +47,67 @@ const description: Record<Style, string | null> = {
[Style.ANCHOR]: null,
};
function parseAllow(value: boolean | AllowList<string>): boolean | AllowList<RegExp> {
if (typeof value === "boolean") {
return value;
}
return {
/* eslint-disable security/detect-non-literal-regexp */
include: value.include ? value.include.map((it) => new RegExp(it)) : null,
exclude: value.exclude ? value.exclude.map((it) => new RegExp(it)) : null,
/* eslint-enable security/detect-non-literal-regexp */
};
}
/**
* @internal
*/
export function matchList(value: string, list: AllowList<RegExp>): boolean {
if (list.include && !list.include.some((it) => it.test(value))) {
return false;
}
if (list.exclude && list.exclude.some((it) => it.test(value))) {
return false;
}
return true;
}
export default class AllowedLinks extends Rule<Style, RuleOptions> {
protected allowExternal: boolean | AllowList<RegExp>;
protected allowRelative: boolean | AllowList<RegExp>;
protected allowAbsolute: boolean | AllowList<RegExp>;
public constructor(options: Partial<RuleOptions>) {
super({ ...defaults, ...options });
this.allowExternal = parseAllow(this.options.allowExternal);
this.allowRelative = parseAllow(this.options.allowRelative);
this.allowAbsolute = parseAllow(this.options.allowAbsolute);
}
public static schema(): SchemaObject {
const booleanOrObject = {
anyOf: [
{ type: "boolean" },
{
type: "object",
properties: {
include: {
type: "array",
items: { type: "string" },
},
exclude: {
type: "array",
items: { type: "string" },
},
},
},
],
};
return {
allowAbsolute: {
type: "boolean",
},
allowBase: {
type: "boolean",
},
allowExternal: {
type: "boolean",
},
allowRelative: {
type: "boolean",
},
allowExternal: { ...booleanOrObject },
allowRelative: { ...booleanOrObject },
allowAbsolute: { ...booleanOrObject },
allowBase: { type: "boolean" },
};
}
......@@ -85,19 +135,19 @@ export default class AllowedLinks extends Rule<Style, RuleOptions> {
break;
case Style.ABSOLUTE:
this.handleAbsolute(event, style);
this.handleAbsolute(link, event, style);
break;
case Style.EXTERNAL:
this.handleExternal(event, style);
this.handleExternal(link, event, style);
break;
case Style.RELATIVE_BASE:
this.handleRelativeBase(event, style);
this.handleRelativeBase(link, event, style);
break;
case Style.RELATIVE_PATH:
this.handleRelativePath(event, style);
this.handleRelativePath(link, event, style);
break;
}
});
......@@ -136,51 +186,76 @@ export default class AllowedLinks extends Rule<Style, RuleOptions> {
}
}
protected handleAbsolute(event: AttributeEvent, style: Style): void {
const { allowAbsolute } = this.options;
if (!allowAbsolute) {
protected handleAbsolute(target: string, event: AttributeEvent, style: Style): void {
const { allowAbsolute } = this;
if (allowAbsolute === true) {
return;
} else if (allowAbsolute === false) {
this.report(
event.target,
"Link destination must not be absolute url",
event.valueLocation,
style
);
} else if (!matchList(target, allowAbsolute)) {
this.report(
event.target,
"Absolute link to this destination is not allowed by current configuration",
event.valueLocation,
style
);
}
}
private handleExternal(event: AttributeEvent, style: Style): void {
const { allowExternal } = this.options;
if (!allowExternal) {
protected handleExternal(target: string, event: AttributeEvent, style: Style): void {
const { allowExternal } = this;
if (allowExternal === true) {
return;
} else if (allowExternal === false) {
this.report(
event.target,
"Link destination must not be external url",
event.valueLocation,
style
);
}
}
private handleRelativePath(event: AttributeEvent, style: Style): void {
const { allowRelative } = this.options;
if (!allowRelative) {
} else if (!matchList(target, allowExternal)) {
this.report(
event.target,
"Link destination must not be relative url",
"External link to this destination is not allowed by current configuration",
event.valueLocation,
style
);
}
}
private handleRelativeBase(event: AttributeEvent, style: Style): void {
const { allowRelative, allowBase } = this.options;
if (!allowRelative) {
protected handleRelativePath(target: string, event: AttributeEvent, style: Style): boolean {
const { allowRelative } = this;
if (allowRelative === true) {
return false;
} else if (allowRelative === false) {
this.report(
event.target,
"Link destination must not be relative url",
event.valueLocation,
style
);
return true;
} else if (!matchList(target, allowRelative)) {
this.report(
event.target,
"Relative link to this destination is not allowed by current configuration",
event.valueLocation,
style
);
return true;
}
return false;
}
private handleRelativeBase(target: string, event: AttributeEvent, style: Style): void {
const { allowBase } = this.options;
if (this.handleRelativePath(target, event, style)) {
return;
} else if (!allowBase) {
this.report(
event.target,
......
......@@ -132,11 +132,14 @@ describe("rule no-raw-characters", () => {
describe("should not report templating", () => {
it.each`
input
${"<% ... %>"}
${"<? ... ?>"}
${"<$ ... $>"}
`("$input", ({ input }) => {
input | description
${"<% & %>"} | ${"<% & %>"}
${"<%\n&\n%>"} | ${"<% & %> (with newlines)"}
${"<? & ?>"} | ${"<? & ?>"}
${"<?\n&\n?>"} | ${"<? & ?> (with newlines)"}
${"<$ & $>"} | ${"<$ & $>"}
${"<$\n&\n$>"} | ${"<$ & $> (with newlines)"}
`("$description", ({ input }) => {
expect.assertions(1);
const report = htmlvalidate.validateString(`<p>lorem ${input} ipsum</p>`);
expect(report).toBeValid();
......
......@@ -13,7 +13,7 @@ const defaults: RuleOptions = {
const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
const unquotedAttrRegexp = /([<>"'=`]|&(?![a-zA-Z0-9#]+;))/g;
const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/;
const matchTemplate = /^(<%.*?%>|<\?.*?\?>|<\$.*?\$>)$/s;
const replacementTable: Map<string, string> = new Map([
['"', "&quot;"],
......