Skip to content

Commit

Permalink
feat: add prefer-https rule
Browse files Browse the repository at this point in the history
  • Loading branch information
yeonjuan committed Dec 14, 2024
1 parent 22325ef commit cf062ee
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
23 changes: 23 additions & 0 deletions docs/rules/prefer-https.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# prefer-https

This rule enforces to use `HTTPS` for embedded resources (image, media, style sheet and script).

## How to use

```js,.eslintrc.js
module.exports = {
rules: {
"@html-eslint/prefer-https": "error",
},
};
```

## Rule Details

Examples of **incorrect** code for tis rule:

```html,incorrect
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<img src="http://html-eslint.org/logo.svg">
<link rel="stylesheet" href="http://style.css">
```
2 changes: 2 additions & 0 deletions packages/eslint-plugin/lib/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const noScriptStyleType = require("./no-script-style-type");
const lowercase = require("./lowercase");
const requireOpenGraphProtocol = require("./require-open-graph-protocol");
const sortAttrs = require("./sort-attrs");
const preferHttps = require("./prefer-https");

module.exports = {
"require-lang": requireLang,
Expand Down Expand Up @@ -78,4 +79,5 @@ module.exports = {
lowercase: lowercase,
"require-open-graph-protocol": requireOpenGraphProtocol,
"sort-attrs": sortAttrs,
"prefer-https": preferHttps,
};
106 changes: 106 additions & 0 deletions packages/eslint-plugin/lib/rules/prefer-https.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* @typedef { import("../types").RuleModule } RuleModule
* @typedef { import("../types").Tag } Tag
* @typedef { import("../types").ScriptTag } ScriptTag
* @typedef { import("../types").Attribute } Attribute
* @typedef { import("../types").AttributeValue } AttributeValue
*/

const { RULE_CATEGORY } = require("../constants");
const { findAttr, isScript } = require("./utils/node");
const { createVisitors } = require("./utils/visitors");

const MESSAGE_IDS = {
UNEXPECTED: "unexpected",
};

/**
* @param {string} url
*/
function getProtocol(url) {
try {
return new URL(url).protocol;
} catch (e) {
return null;

Check warning on line 24 in packages/eslint-plugin/lib/rules/prefer-https.js

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/lib/rules/prefer-https.js#L24

Added line #L24 was not covered by tests
}
}

/**
* @param {Tag | ScriptTag} node
* @returns {AttributeValue | undefined}
*/
function getResourceAttributeValue(node) {
/**
* @type {Attribute | undefined}
*/
let attribute;
if (isScript(node)) {
attribute = findAttr(node, "src");

Check warning on line 38 in packages/eslint-plugin/lib/rules/prefer-https.js

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/lib/rules/prefer-https.js#L38

Added line #L38 was not covered by tests
} else {
switch (node.name.toLowerCase()) {
case "img":
case "iframe":
case "audio":
case "video":
case "source":
case "embed": {
attribute = findAttr(node, "src");
break;
}
case "link": {
attribute = findAttr(node, "href");
break;

Check warning on line 52 in packages/eslint-plugin/lib/rules/prefer-https.js

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/lib/rules/prefer-https.js#L50-L52

Added lines #L50 - L52 were not covered by tests
}
case "object": {
attribute = findAttr(node, "data");
break;

Check warning on line 56 in packages/eslint-plugin/lib/rules/prefer-https.js

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/lib/rules/prefer-https.js#L54-L56

Added lines #L54 - L56 were not covered by tests
}
}
}
if (attribute) {
return attribute.value;
}
return undefined;

Check warning on line 63 in packages/eslint-plugin/lib/rules/prefer-https.js

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/lib/rules/prefer-https.js#L63

Added line #L63 was not covered by tests
}

/**
* @type {RuleModule}
*/
module.exports = {
meta: {
type: "code",
docs: {
description: "Prefer to use HTTPS for embedded resources",
recommended: false,
category: RULE_CATEGORY.BEST_PRACTICE,
},
fixable: false,
schema: [],
messages: {
[MESSAGE_IDS.UNEXPECTED]: "Unexpected use of 'http' protocol",
},
},

create(context) {
/**
* @param {Tag | ScriptTag} node
*/
function check(node) {
const attributeValue = getResourceAttributeValue(node);
if (attributeValue && !attributeValue.templates.length) {
const protocol = getProtocol(attributeValue.value);
if (protocol === "http:") {
context.report({
node: attributeValue,
messageId: MESSAGE_IDS.UNEXPECTED,
});
}
}
}

return createVisitors(context, {
ScriptTag: check,
Tag: check,
});
},
};
18 changes: 18 additions & 0 deletions packages/eslint-plugin/lib/rules/utils/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,22 @@ function isTag(node) {
return node.type === NODE_TYPES.Tag;
}

/**
* @param {AnyNode} node
* @returns {node is ScriptTag}
*/
function isScript(node) {
return node.type === NODE_TYPES.ScriptTag;
}

/**
* @param {AnyNode} node
* @returns {node is StyleTag}
*/
function isStyle(node) {
return node.type === NODE_TYPES.StyleTag;

Check warning on line 176 in packages/eslint-plugin/lib/rules/utils/node.js

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/lib/rules/utils/node.js#L175-L176

Added lines #L175 - L176 were not covered by tests
}

/**
* @param {AnyNode} node
* @returns {node is Comment}
Expand Down Expand Up @@ -223,6 +239,8 @@ module.exports = {
isComment,
isText,
isLine,
isScript,
isStyle,
isOverlapWithTemplates,
codeToLines,
isRangesOverlap,
Expand Down
22 changes: 22 additions & 0 deletions packages/eslint-plugin/tests/rules/prefer-https.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const createRuleTester = require("../rule-tester");
const rule = require("../../lib/rules/prefer-https");

const ruleTester = createRuleTester();

ruleTester.run("quotes", rule, {
valid: [
{
code: `<img src="https://image.png">`,
},
],
invalid: [
{
code: `<img src="http://image.png">`,
errors: [
{
messageId: "unexpected",
},
],
},
],
});

0 comments on commit cf062ee

Please sign in to comment.