Skip to content

Commit a598310

Browse files
committed
0.2.1
- Added feature to run the validator on arbitrary strings in addition to the direct Express render integration. - Bumped Node support matrix to 16 and 18. - Various dependencies bumped.
1 parent 02cb05e commit a598310

6 files changed

Lines changed: 677 additions & 568 deletions

File tree

.github/workflows/.ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
runs-on: ${{ matrix.os }}
66
strategy:
77
matrix:
8-
node-version: [14.x, 16.x]
8+
node-version: [16.x, 18.x]
99
os: [ubuntu-latest, macos-latest, windows-latest]
1010
steps:
1111
- uses: actions/checkout@v3

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
- Put your changes here...
66

7+
## 0.2.1
8+
9+
- Added feature to run the validator on arbitrary strings in addition to the direct Express render integration.
10+
- Bumped Node support matrix to 16 and 18.
11+
- Various dependencies bumped.
12+
713
## 0.2.0
814

915
- Various dependencies bumped.

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@ app.get('/', (req, res) => {
2929
})
3030
```
3131

32+
You can also run the validator on arbitrary strings outide of the Express context:
33+
34+
```js
35+
const config = {}
36+
const expressValidator = require('express-html-validator')(config)
37+
38+
const someHtml = `<!DOCTYPE html>
39+
<html lang="en">
40+
<head>
41+
<meta charset="UTF-8">
42+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
43+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
44+
<title>Some HTML that will not validate</title>
45+
</head>
46+
<body>
47+
<p>hello world</p></p>
48+
</body>
49+
</html>`
50+
51+
const validationResult = expressValidator(someHtml)
52+
```
53+
54+
Since the example HTML is not valid, if you display the contents of `validationResult` in a browser, you will see validation errors.
55+
3256
## Configuration
3357

3458
Optionally you can pass this module a set of configs:

index.js

Lines changed: 99 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,17 @@ const { HtmlValidate } = require('html-validate')
1010
const validatorErrorPage = fs.readFileSync(path.join(__dirname, 'templates/errorPage.html'))
1111

1212
module.exports = (app, params) => {
13-
params = params || {}
14-
const render = app.response.render
13+
if (app.hasOwnProperty('listen') || typeof app.listen === 'function') {
14+
params = params || {} // two arguments
15+
} else {
16+
params = app // one argument
17+
app = null
18+
}
19+
20+
let render
21+
if (app) {
22+
render = app.response.render
23+
}
1524
let resModel
1625

1726
// sanitize config
@@ -71,91 +80,100 @@ module.exports = (app, params) => {
7180
// instantiate the validator module
7281
const htmlValidate = new HtmlValidate(rules)
7382

74-
// use some method overload trickery to store a usable model reference
75-
app.response.render = function (view, model, callback) {
76-
// store a reference to the model if exceptions are being used and a model was set
77-
if (model && typeof model === 'object') {
78-
resModel = model
79-
}
83+
function validate (body, res) {
84+
// run the validator against the response body
85+
const report = htmlValidate.validateString(body)
8086

81-
render.apply(this, arguments)
82-
}
87+
if (!report.valid) {
88+
const errorMap = new Map()
89+
let parsedErrors = ''
90+
91+
for (const error of report.results[0].messages) {
92+
const message = escapeHtmlEntities(error.message)
93+
94+
// first line is error message
95+
parsedErrors += `${message}\n`
8396

84-
// validate responses under the right conditions
85-
app.use(tamper((req, res) => {
86-
/**
87-
* Skip validation when:
88-
* - HTTP status is not 200 (don't validate error pages)
89-
* - content-type is not text/html (don't validate non-HTML responses)
90-
* - No exception applies
91-
*/
92-
if (res.statusCode === 200 && res.getHeader('Content-Type') && res.getHeader('Content-Type').includes('text/html') && !validatorExceptions(req, res)) {
93-
// start validating the response body
94-
return body => {
95-
// run the validator against the response body
96-
const report = htmlValidate.validateString(body)
97-
98-
if (!report.valid) {
99-
const errorMap = new Map()
100-
let parsedErrors = ''
101-
102-
for (const error of report.results[0].messages) {
103-
const message = escapeHtmlEntities(error.message)
104-
105-
// first line is error message
106-
parsedErrors += `${message}\n`
107-
108-
// next line is line and column numbers
109-
parsedErrors += `At line ${error.line}, column ${error.column}\n\n`
110-
111-
// add error message and line number to map
112-
errorMap.set(error.line, error.message)
113-
}
114-
115-
const errorList = `<h2>Errors:</h2>\n<code class="validatorErrors">${parsedErrors}</code>`
116-
117-
// start building out stylized markup block
118-
let formattedHTML = '<pre class=\'markup\'>\n<code class="language-html">\n'
119-
const markupArray = body.split('\n')
120-
121-
// add line number highlighting for detected errors
122-
for (let i = 0; i < markupArray.length; i++) {
123-
const markupLine = markupArray[i]
124-
if (errorMap.has(i + 1)) {
125-
formattedHTML += `<span title='${errorMap.get(i + 1)}' class='line-numbers error'>`
126-
formattedHTML += Prism.highlight(`${markupLine}`, Prism.languages.markup)
127-
formattedHTML += '</span>'
128-
} else {
129-
formattedHTML += '<span class=\'line-numbers\'>'
130-
formattedHTML += Prism.highlight(`${markupLine}`, Prism.languages.markup)
131-
formattedHTML += '</span>'
132-
}
133-
}
134-
135-
// cap off the stylized markup blocks
136-
formattedHTML += '</code>\n</pre>'
137-
formattedHTML = `<h2>Markup used:</h2>\n${formattedHTML}`
138-
139-
// use 500 status for the validation error
140-
res.status(500)
141-
142-
// build a model that includes error data, markup, and styling
143-
const model = {
144-
prismStyle: prismStyleSheet.toString(),
145-
preWidth: markupArray.length.toString().length * 8,
146-
errors: errorList,
147-
markup: formattedHTML,
148-
rawMarkup: body
149-
}
150-
151-
// parse error page template and replace response body with it
152-
body = template(validatorErrorPage, model)
97+
// next line is line and column numbers
98+
parsedErrors += `At line ${error.line}, column ${error.column}\n\n`
99+
100+
// add error message and line number to map
101+
errorMap.set(error.line, error.message)
102+
}
103+
104+
const errorList = `<h2>Errors:</h2>\n<code class="validatorErrors">${parsedErrors}</code>`
105+
106+
// start building out stylized markup block
107+
let formattedHTML = '<pre class=\'markup\'>\n<code class="language-html">\n'
108+
const markupArray = body.split('\n')
109+
110+
// add line number highlighting for detected errors
111+
for (let i = 0; i < markupArray.length; i++) {
112+
const markupLine = markupArray[i]
113+
if (errorMap.has(i + 1)) {
114+
formattedHTML += `<span title='${errorMap.get(i + 1)}' class='line-numbers error'>`
115+
formattedHTML += Prism.highlight(`${markupLine}`, Prism.languages.markup)
116+
formattedHTML += '</span>'
117+
} else {
118+
formattedHTML += '<span class=\'line-numbers\'>'
119+
formattedHTML += Prism.highlight(`${markupLine}`, Prism.languages.markup)
120+
formattedHTML += '</span>'
153121
}
122+
}
123+
124+
// cap off the stylized markup blocks
125+
formattedHTML += '</code>\n</pre>'
126+
formattedHTML = `<h2>Markup used:</h2>\n${formattedHTML}`
127+
128+
// use 500 status for the validation error
129+
if (res) {
130+
res.status(500)
131+
}
154132

155-
return body
133+
// build a model that includes error data, markup, and styling
134+
const model = {
135+
prismStyle: prismStyleSheet.toString(),
136+
preWidth: markupArray.length.toString().length * 8,
137+
errors: errorList,
138+
markup: formattedHTML,
139+
rawMarkup: body
156140
}
141+
142+
// parse error page template and replace response body with it
143+
body = template(validatorErrorPage, model)
157144
}
158-
}))
145+
146+
return body
147+
}
148+
149+
if (app) {
150+
// use some method overload trickery to store a usable model reference
151+
app.response.render = function (view, model, callback) {
152+
// store a reference to the model if exceptions are being used and a model was set
153+
if (model && typeof model === 'object') {
154+
resModel = model
155+
}
156+
157+
render.apply(this, arguments)
158+
}
159+
160+
// validate responses under the right conditions
161+
app.use(tamper((req, res) => {
162+
/**
163+
* Skip validation when:
164+
* - HTTP status is not 200 (don't validate error pages)
165+
* - content-type is not text/html (don't validate non-HTML responses)
166+
* - No exception applies
167+
*/
168+
if (res.statusCode === 200 && res.getHeader('Content-Type') && res.getHeader('Content-Type').includes('text/html') && !validatorExceptions(req, res)) {
169+
return (body) => {
170+
return validate(body, res)
171+
}
172+
}
173+
}))
174+
}
175+
176+
return validate // export validate function for general use
159177
}
160178

161179
/*

0 commit comments

Comments
 (0)