Repository: mozilla/bleach

Issue:
Open angle bracket '<' with few words after cleaned up if there's no closing bracket
**Describe the bug**

After https://github.com/mozilla/bleach/issues/544 being fixed it seems the issue is still persist. But it reproducible in another way

 - Python Version: 3.8.13
 - Bleach Version: 6.0.0

**To Reproduce**

Steps to reproduce the behavior:

```python
# Fixed!
In [2]: bleach.clean("<random")
Out[2]: '&lt;random'

# Fixed!
In [3]: bleach.clean("random<text")
Out[3]: 'random&lt;text'

# Problem!
In [4]: bleach.clean("<random text")
Out[4]: ''
```

**Expected behavior**

```python
In [4]: bleach.clean("<random text")
Out[4]: '&lt;random text'
```

**Additional context**

Previously it was fixed by https://github.com/mozilla/bleach/pull/667, so that `<` without `>` considered as `eof-in-tag-name`, but in the case above it's considered as EOF in the attribute name  -- `'eof-in-attribute-name'`:

```python
392  	        if last_error_token:
393 B->	            if last_error_token["data"] == "eof-in-tag-name":
394  	                # Handle the case where the text being parsed ends with <
395  	                # followed by a series of characters. It's treated as a tag
396  	                # name that abruptly ends, but we should treat that like
397  	                # character data
398  	                yield {
(Pdb) 
399  	                    "type": TAG_TOKEN_TYPE_CHARACTERS,
400  	                    "data": "<" + self.currentToken["name"],
401  	                }
402  	            else:
403  	                yield last_error_token
404  	
405  	    def consumeEntity(self, allowedChar=None, fromAttribute=False):
406  	        # If this tokenizer is set to consume entities, then we can let the
407  	        # superclass do its thing.
408  	        if self.consume_entities:
409  	            return super().consumeEntity(allowedChar, fromAttribute)
(Pdb) last_error_token
{'type': 7, 'data': 'eof-in-attribute-name'}
```


Discussion:

Patch:
diff --git a/bleach/html5lib_shim.py b/bleach/html5lib_shim.py
index aa5189b..ca1cc8c 100644
--- a/bleach/html5lib_shim.py
+++ b/bleach/html5lib_shim.py
@@ -395,10 +395,17 @@ class BleachHTMLTokenizer(HTMLTokenizer):
                 # followed by a series of characters. It's treated as a tag
                 # name that abruptly ends, but we should treat that like
                 # character data
-                yield {
-                    "type": TAG_TOKEN_TYPE_CHARACTERS,
-                    "data": "<" + self.currentToken["name"],
-                }
+                yield {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": self.stream.get_tag()}
+            elif last_error_token["data"] in (
+                "eof-in-attribute-name",
+                "eof-in-attribute-value-no-quotes",
+            ):
+                # Handle the case where the text being parsed ends with <
+                # followed by a series of characters and then space and then
+                # more characters. It's treated as a tag name followed by an
+                # attribute that abruptly ends, but we should treat that like
+                # character data.
+                yield {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": self.stream.get_tag()}
             else:
                 yield last_error_token
 


Test patch:
diff --git a/tests/test_clean.py b/tests/test_clean.py
index 73946a1..dc129d0 100644
--- a/tests/test_clean.py
+++ b/tests/test_clean.py
@@ -163,6 +163,10 @@ def test_bare_entities_get_escaped_correctly(text, expected):
         ("<y", "&lt;y"),
         ("x < y", "x &lt; y"),
         ("<y>", "&lt;y&gt;"),
+        # this is an eof-in-attribute-name parser error
+        ("<some thing", "&lt;some thing"),
+        # this is an eof-in-attribute-value-no-quotes parser error
+        ("<some thing=foo", "&lt;some thing=foo"),
     ],
 )
 def test_lessthan_escaping(text, expected):