Repository: secdev/scapy

Issue:
scapy.contrib.http2.HPackHdrTable.parse_txt_hdrs should convert string arg to bytes
### Brief description

The code to reproduce this is taken directly from the [HTTP/2 example](https://github.com/secdev/scapy/blob/master/doc/notebooks/HTTP_2_Tuto.ipynb) (which does not work as-is, but is a *fantastic* starting point).

```python
http2.HPackHdrTable().parse_txt_hdrs(
    """:method GET
:path /
:authority www.google.com
:scheme https
accept-encoding: gzip, deflate
accept-language: en-US
accept: text/html
user-agent: Scapy HTTP/2 Module
""",
    stream_id=1,
    max_frm_sz=1 << 14,
    max_hdr_lst_sz=1 << 16,
    is_sensitive=lambda hdr_name, hdr_val: hdr_name in ["cookie"],
    should_index=lambda x: x
    in [
        "x-requested-with",
        "user-agent",
        "accept-language",
        ":authority",
        "accept",
    ],
)
```

gives

```
Traceback (most recent call last):
  File "/redacted/src/main.py", line 122, in <module>
    qry_frontpage = tblhdr.parse_txt_hdrs(
                    ^^^^^^^^^^^^^^^^^^^^^^
  File "/redacted/.venv/lib/python3.11/site-packages/scapy/contrib/http2.py", line 2663, in parse_txt_hdrs
    sio = BytesIO(s)
          ^^^^^^^^^^
TypeError: a bytes-like object is required, not 'str'
```

this line can be found here

https://github.com/secdev/scapy/blob/4b386fffbbdfcc46f41433e7f96ca00488ac30c6/scapy/contrib/http2.py#L2667

Despite the type hint of string, it appears this really wants bytes as it currently is. Converting seems to solve the problem.

```python
""":method GET
:path /
:authority www.google.com
:scheme https
accept-encoding: gzip, deflate
accept-language: en-US
accept: text/html
user-agent: Scapy HTTP/2 Module
"""
```

to 

```python
bytes(
    """:method GET
:path /
:authority www.google.com
:scheme https
accept-encoding: gzip, deflate
accept-language: en-US
accept: text/html
user-agent: Scapy HTTP/2 Module
""",
    "UTF-8"
)
```

Either correct the type hint or do the conversion? Also, I am just doing some hacking and do not know what encoding the fix should use. I just decided to use the ubiquitous UTF-8.

### Scapy version

2.5.0

### Python version

3.11.5

### Operating system

Debian 12 on WSL2

### Additional environment information

_No response_

### How to reproduce

Use the method with a string

### Actual result

TypeError

### Expected result

Conversion from string to byte is done inside the method, if it is necessary. String arguments should work.

### Related resources

_No response_


Discussion:

Resolving patch:
diff --git a/scapy/contrib/http2.py b/scapy/contrib/http2.py
--- a/scapy/contrib/http2.py
+++ b/scapy/contrib/http2.py
@@ -2618,7 +2618,7 @@ def _parse_header_line(self, line):
         return plain_str(hdr_name.lower()), plain_str(grp.group(3))
 
     def parse_txt_hdrs(self,
-                       s,  # type: str
+                       s,  # type: Union[bytes, str]
                        stream_id=1,  # type: int
                        body=None,  # type: Optional[str]
                        max_frm_sz=4096,  # type: int
@@ -2664,7 +2664,7 @@ def parse_txt_hdrs(self,
         :raises: Exception
         """
 
-        sio = BytesIO(s)
+        sio = BytesIO(s.encode() if isinstance(s, str) else s)
 
         base_frm_len = len(raw(H2Frame()))
 


Test patch:
diff --git a/test/contrib/http2.uts b/test/contrib/http2.uts
--- a/test/contrib/http2.uts
+++ b/test/contrib/http2.uts
@@ -1693,7 +1693,7 @@ user-agent: Mozilla/5.0 Generated by hand
 x-generated-by: Me
 x-generation-date: 2016-08-11
 x-generation-software: scapy
-'''.format(len(body)).encode()
+'''.format(len(body))
 
 h = h2.HPackHdrTable()
 h.register(h2.HPackLitHdrFldWithIncrIndexing(
@@ -1789,6 +1789,101 @@ assert isinstance(p.payload, h2.H2DataFrame)
 pay = p[h2.H2DataFrame]
 assert pay.data == body
 
+# now with bytes
+h = h2.HPackHdrTable()
+h.register(h2.HPackLitHdrFldWithIncrIndexing(
+        hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')),
+        hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11'))
+))
+seq = h.parse_txt_hdrs(
+    hdrs.encode(),
+    stream_id=1,
+    body=body,
+    should_index=lambda name: name in ['user-agent', 'x-generation-software'],
+    is_sensitive=lambda name, value: name in ['x-generated-by', ':path']
+)
+assert isinstance(seq, h2.H2Seq)
+assert len(seq.frames) == 2
+p = seq.frames[0]
+assert isinstance(p, h2.H2Frame)
+assert p.type == 1
+assert len(p.flags) == 1
+assert 'EH' in p.flags
+assert p.stream_id == 1
+assert isinstance(p.payload, h2.H2HeadersFrame)
+hdrs_frm = p[h2.H2HeadersFrame]
+assert len(p.hdrs) == 9
+hdr = p.hdrs[0]
+assert isinstance(hdr, h2.HPackIndexedHdr)
+assert hdr.magic == 1
+assert hdr.index == 3
+hdr = p.hdrs[1]
+assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)
+assert hdr.magic == 0
+assert hdr.never_index == 1
+assert hdr.index in [4, 5]
+assert hdr.hdr_name is None
+assert isinstance(hdr.hdr_value, h2.HPackHdrString)
+assert hdr.hdr_value.data == 'HPackZString(/login.php)'
+hdr = p.hdrs[2]
+assert isinstance(hdr, h2.HPackIndexedHdr)
+assert hdr.magic == 1
+assert hdr.index == 7
+hdr = p.hdrs[3]
+assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)
+assert hdr.magic == 0
+assert hdr.never_index == 0
+assert hdr.index == 31
+assert hdr.hdr_name is None
+assert isinstance(hdr.hdr_value, h2.HPackHdrString)
+assert hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)'
+hdr = p.hdrs[4]
+assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)
+assert hdr.magic == 0
+assert hdr.never_index == 0
+assert hdr.index == 28
+assert hdr.hdr_name is None
+assert isinstance(hdr.hdr_value, h2.HPackHdrString)
+assert hdr.hdr_value.data == 'HPackLiteralString(22)'
+hdr = p.hdrs[5]
+assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)
+assert hdr.magic == 1
+assert hdr.index == 58
+assert hdr.hdr_name is None
+assert isinstance(hdr.hdr_value, h2.HPackHdrString)
+assert hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)'
+hdr = p.hdrs[6]
+assert isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)
+assert hdr.magic == 0
+assert hdr.never_index == 1
+assert hdr.index == 0
+assert isinstance(hdr.hdr_name, h2.HPackHdrString)
+assert hdr.hdr_name.data == 'HPackZString(x-generated-by)'
+assert isinstance(hdr.hdr_value, h2.HPackHdrString)
+assert hdr.hdr_value.data == 'HPackLiteralString(Me)'
+hdr = p.hdrs[7]
+assert isinstance(hdr, h2.HPackIndexedHdr)
+assert hdr.magic == 1
+assert hdr.index == 63
+hdr = p.hdrs[8]
+assert isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)
+assert hdr.magic == 1
+assert hdr.index == 0
+assert isinstance(hdr.hdr_name, h2.HPackHdrString)
+assert hdr.hdr_name.data == 'HPackZString(x-generation-software)'
+assert isinstance(hdr.hdr_value, h2.HPackHdrString)
+assert hdr.hdr_value.data == 'HPackZString(scapy)'
+
+p = seq.frames[1]
+assert isinstance(p, h2.H2Frame)
+assert p.type == 0
+assert len(p.flags) == 1
+assert 'ES' in p.flags
+assert p.stream_id == 1
+assert isinstance(p.payload, h2.H2DataFrame)
+pay = p[h2.H2DataFrame]
+assert pay.data == body
+
 = HTTP/2 HPackHdrTable : Parsing Textual Representation without body
 ~ http2 hpack hpackhdrtable helpers