Skip to content

Commit 9de0375

Browse files
committed
Add form-method-require rule
1 parent 6814453 commit 9de0375

File tree

8 files changed

+162
-17
lines changed

8 files changed

+162
-17
lines changed

.editorconfig

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ root = true
66

77
# Apply for all files
88
[*]
9-
109
charset = utf-8
11-
1210
indent_style = space
1311
indent_size = 2
14-
1512
end_of_line = lf
1613
insert_final_newline = true
1714
trim_trailing_whitespace = true

.gitignore

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
# Files that might appear in the root of a volume
22
.DocumentRevisions-V100
3-
.fseventsd
43
.Spotlight-V100
54
.TemporaryItems
65
.Trashes
7-
.VolumeIcon.icns
8-
.com.apple.timemachine.donotpresent
96

107
# Editor directories and files
118
.idea
129
*.suo
13-
*.ntvs*
14-
*.njsproj
1510
*.sln
1611

1712
# Generated files
@@ -25,13 +20,9 @@ yarn-debug.log*
2520
yarn-error.log*
2621

2722
# Runtime data
28-
pids
2923
*.pid
3024
*.seed
3125

32-
# Directory for instrumented libs generated by jscoverage/JSCover
33-
lib-cov
34-
3526
# Coverage directory used by tools like nyc and istanbul
3627
.nyc_output
3728
coverage
@@ -44,9 +35,6 @@ build/Release
4435
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
4536
node_modules
4637

47-
# Users Environment Variables
48-
.lock-wscript
49-
5038
# macOS
5139
*.DS_Store
5240
.AppleDouble

dist/core/rules/index.js

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Listener } from '../htmlparser'
2+
import { Rule } from '../types'
3+
4+
export default {
5+
id: 'form-method-require',
6+
description:
7+
'The method attribute of a <form> element must be present with a valid value: "get", "post", or "dialog".',
8+
init(parser, reporter) {
9+
const onTagStart: Listener = (event) => {
10+
const tagName = event.tagName.toLowerCase()
11+
12+
if (tagName === 'form') {
13+
const mapAttrs = parser.getMapAttrs(event.attrs)
14+
const col = event.col + tagName.length + 1
15+
16+
if (mapAttrs.method === undefined) {
17+
reporter.warn(
18+
'The method attribute must be present on <form> elements.',
19+
event.line,
20+
col,
21+
this,
22+
event.raw
23+
)
24+
} else {
25+
const methodValue = mapAttrs.method.toLowerCase()
26+
if (
27+
methodValue !== 'get' &&
28+
methodValue !== 'post' &&
29+
methodValue !== 'dialog'
30+
) {
31+
reporter.warn(
32+
'The method attribute of <form> must have a valid value: "get", "post", or "dialog".',
33+
event.line,
34+
col,
35+
this,
36+
event.raw
37+
)
38+
}
39+
}
40+
}
41+
}
42+
43+
parser.addListener('tagstart', onTagStart)
44+
},
45+
} as Rule

src/core/rules/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export { default as buttonTypeRequire } from './button-type-require'
1313
export { default as doctypeFirst } from './doctype-first'
1414
export { default as doctypeHTML5 } from './doctype-html5'
1515
export { default as emptyTagNotSelfClosed } from './empty-tag-not-self-closed'
16+
export { default as formMethodRequire } from './form-method-require'
1617
export { default as frameTitleRequire } from './frame-title-require'
1718
export { default as h1Require } from './h1-require'
1819
export { default as headScriptDisabled } from './head-script-disabled'

src/core/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface Ruleset {
2222
'doctype-first'?: boolean
2323
'doctype-html5'?: boolean
2424
'empty-tag-not-self-closed'?: boolean
25+
'form-method-require'?: boolean
2526
'head-script-disabled'?: boolean
2627
'href-abs-or-rel'?: 'abs' | 'rel'
2728
'id-class-ad-disabled'?: boolean
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint
2+
3+
const ruleId = 'form-method-require'
4+
const ruleOptions = {}
5+
6+
ruleOptions[ruleId] = true
7+
8+
describe(`Rules: ${ruleId}`, () => {
9+
it('Form with method="get" should not result in an error', () => {
10+
const code = '<form method="get"></form>'
11+
const messages = HTMLHint.verify(code, ruleOptions)
12+
expect(messages.length).toBe(0)
13+
})
14+
15+
it('Form with method="post" should not result in an error', () => {
16+
const code = '<form method="post"></form>'
17+
const messages = HTMLHint.verify(code, ruleOptions)
18+
expect(messages.length).toBe(0)
19+
})
20+
21+
it('Form with method="dialog" should not result in an error', () => {
22+
const code = '<form method="dialog"></form>'
23+
const messages = HTMLHint.verify(code, ruleOptions)
24+
expect(messages.length).toBe(0)
25+
})
26+
27+
it('Form without method attribute should result in an error', () => {
28+
const code = '<form></form>'
29+
const messages = HTMLHint.verify(code, ruleOptions)
30+
expect(messages.length).toBe(1)
31+
expect(messages[0].rule.id).toBe(ruleId)
32+
expect(messages[0].line).toBe(1)
33+
expect(messages[0].col).toBe(6)
34+
expect(messages[0].type).toBe('warning')
35+
expect(messages[0].message).toBe(
36+
'The method attribute must be present on <form> elements.'
37+
)
38+
})
39+
40+
it('Form with invalid method value should result in an error', () => {
41+
const code = '<form method="invalid"></form>'
42+
const messages = HTMLHint.verify(code, ruleOptions)
43+
expect(messages.length).toBe(1)
44+
expect(messages[0].rule.id).toBe(ruleId)
45+
expect(messages[0].line).toBe(1)
46+
expect(messages[0].col).toBe(6)
47+
expect(messages[0].type).toBe('warning')
48+
expect(messages[0].message).toBe(
49+
'The method attribute of <form> must have a valid value: "get", "post", or "dialog".'
50+
)
51+
})
52+
53+
it('Form with uppercase method value should not result in an error', () => {
54+
const code = '<form method="POST"></form>'
55+
const messages = HTMLHint.verify(code, ruleOptions)
56+
expect(messages.length).toBe(0)
57+
})
58+
59+
it('Other elements should not be affected by this rule', () => {
60+
const code = '<div>Not a form</div><input type="text">'
61+
const messages = HTMLHint.verify(code, ruleOptions)
62+
expect(messages.length).toBe(0)
63+
})
64+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
id: form-method-require
3+
title: form-method-require
4+
description: Requires form elements to have a valid method attribute for better security and user experience.
5+
sidebar:
6+
badge: New
7+
pagefind: false
8+
hidden: true
9+
---
10+
11+
import { Badge } from '@astrojs/starlight/components';
12+
13+
The method attribute of a `<form>` element must be present with a valid value: "get", "post", or "dialog".
14+
15+
Level: <Badge text="Warning" variant="caution" />
16+
17+
## Config value
18+
19+
- `true`: enable rule
20+
- `false`: disable rule
21+
22+
### The following patterns are **not** considered rule violations
23+
24+
```html
25+
<form method="get"></form>
26+
<form method="post"></form>
27+
<form method="dialog"></form>
28+
```
29+
30+
### The following patterns are considered rule violations
31+
32+
```html
33+
<form>No method specified</form>
34+
<form method="invalid">Invalid method</form>
35+
```
36+
37+
## Why this rule is important
38+
39+
The absence of the method attribute means the form will use the default `GET` method. With `GET`, form data is included in the URL (e.g., `?username=john&password=secret`), which can expose sensitive information in browser history, logs, or the network request.
40+
41+
The HTML specification requires that form elements have one of three valid methods:
42+
43+
- `get`: Appends form data to the URL (default, but not recommended for sensitive data)
44+
- `post`: Sends form data in the request body (more secure for sensitive data)
45+
- `dialog`: Used for dialog forms (HTML5 feature)
46+
47+
This rule helps ensure that forms have explicit, valid methods for better security and user experience.

0 commit comments

Comments
 (0)