Repository: apostrophecms/sanitize-html (js)

Issue:
It is possible to add a disallowed closing tag with valid HTML as well as opening tag with invalid HTML markup.
I found several variants of the library's incorrect behavior. In the examples below, it is possible to add any html tag (closing tag with valid HTML as well as opening tag with invalid HTML) if any tag is allowed.

## Example 1:

```
const sanitizeHtml = require('sanitize-html');

const sanitizedString = sanitizeHtml('<b><div/', {
    allowedTags: ['b'],
});
```
## Expected behavior
> As a result of the execution I expect to see ```<b></b>``` or a empty line. However, I get ```<b></div>```. 

## Example 2:

```
const sanitizeHtml = require('sanitize-html');
const HTMLParser = require('node-html-parser');

const sanitizedString = sanitizeHtml('<b><b<<div/', {
    allowedTags: ['b'],
});

console.log(sanitizedString) // <b></b<<div>

const unespectedDiv = HTMLParser.parse(sanitizedString).querySelector('div');

console.log(unespectedDiv);
```
## Expected behavior
> As a result of the execution I expect to see ```<b></b>``` or a empty line. However, I get ```<b></b<<div>```. The resulting string contains a substring ```<div>```, which is interpreted by some parsers as a valid html tag like node-html-parser (Browsers interpret it correctly).

Pull request description:
Another case of bad markup identified in Issue#549. Closing tags without having opening tags. Test cases also added.
<!-- Please don't delete this template -->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

## Summary

Closes #549 

## What are the specific steps to test this change?
The snippets given in the issues should now work as expected.

## What kind of change does this PR introduce?
*(Check at least one)*

- [x] Bug fix
- [ ] New feature
- [ ] Refactor
- [ ] Documentation
- [ ] Build-related changes
- [ ] Other

## Make sure the PR fulfills these requirements:

- [x] It includes a) the existing issue ID being resolved, b) a convincing reason for adding this feature, or c) a clear description of the bug it resolves
- [x] The changelog is updated
- [x] Related documentation has been updated
- [x] Related tests have been updated

If adding a new feature without an already open issue, it's best to open a **feature request issue** first and wait for approval before working on it.

**Other information:**

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc84443..c362dde 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## Issue #549 fix
+- Fixed allowance of disallowed tags with wrong markup of having closing tag without opening tag as reported on [issue #549](https://github.com/apostrophecms/sanitize-html/issues/549).
+
 ## 2.7.1 (2022-07-20)
 
 - Protocol-relative URLs are properly supported for script tags. Thanks to [paweljq](https://github.com/paweljq).
diff --git a/index.js b/index.js
index 4a4c2d1..ee27a3c 100644
--- a/index.js
+++ b/index.js
@@ -521,6 +521,14 @@ function sanitizeHtml(html, options, _recursing) {
         // Do not crash on bad markup
         return;
       }
+
+      if (frame.tag !== name) {
+        // Another case of bad markup.
+        // Push to stack, so that it will be used in future closing tags.
+        stack.push(frame);
+        return;
+      }
+
       skipText = options.enforceHtmlBoundary ? name === 'html' : false;
       depth--;
       const skip = skipMap[depth];


Test patch:
diff --git a/test/test.js b/test/test.js
index cba4de7..283b2b5 100644
--- a/test/test.js
+++ b/test/test.js
@@ -116,6 +116,23 @@ describe('sanitizeHtml', function() {
   it('should preserve entities as such', function() {
     assert.equal(sanitizeHtml('<a name="&lt;silly&gt;">&lt;Kapow!&gt;</a>'), '<a name="&lt;silly&gt;">&lt;Kapow!&gt;</a>');
   });
+  it('should dump closing tags which do not have any opening tags.', function() {
+    assert.equal(sanitizeHtml('<b><div/', {
+      allowedTags: [ 'b' ]
+    }), '<b></b>');
+
+    assert.equal(sanitizeHtml('<b><b<<div/', {
+      allowedTags: [ 'b' ]
+    }), '<b></b>');
+  });
+  it('should tolerate not closed p tags', function() {
+    assert.equal(sanitizeHtml('<div><p>inner text 1<p>inner text 2<p>inner text 3</div>'), '<div><p>inner text 1</p><p>inner text 2</p><p>inner text 3</p></div>');
+  });
+  it('should escape not closed p tags, if not in allowedTags array', function() {
+    assert.equal(sanitizeHtml('<div><p>inner text 1<p>inner text 2<p>inner text 3</div>', {
+      allowedTags: [ 'div' ]
+    }), '<div>inner text 1inner text 2inner text 3</div>');
+  });
   it('should dump comments', function() {
     assert.equal(sanitizeHtml('<p><!-- Blah blah -->Whee</p>'), '<p>Whee</p>');
   });