Repository: secdev/scapy

Issue:
Double backslashes when showing byte strings
### Brief description

When showing a packet containing a StrField, non-printable bytes that have 1 as a msb will be escaped with `\\x`, but bytes that have 0 as a msb will be escaped with a `\x`:

```python
>>> (UDP()/bytes.fromhex('0123456789abcdef')).show()
###[ UDP ]### 
  sport     = domain
  dport     = domain
  len       = None
  chksum    = None
###[ Raw ]### 
     load      = '\x01#Eg\\x89\\xab\\xcd\\xef'
```

The reason for this is that `StrField.i2repr(None, None, x)` returns `repr(plain_str(x))` which is just `repr(x.decode(errors="backslashreplace"))`, so you can see the same issue here:
```python
>>> print(repr(plain_str(bytes.fromhex('0123456789abcdef'))))
'\x01#Eg\\x89\\xab\\xcd\\xef'
```

This makes it confusing to read and difficult to copy and paste the bytes back into python code.
The most confusing part is that sometimes double backslashes are used to escape a backslash.

Notice that the following two bytes objects are completely different and yet return the same representation:
```python
>>> print(repr(plain_str(bytes.fromhex('005cff'))))
'\x00\\\\xff'
>>> print(repr(plain_str(bytes.fromhex('005c5c786666'))))
'\x00\\\\xff'
```

And here is how Scapy can be even more confusing than JavaScript:
```python
>>> plain_str(bytes.fromhex('005c5c786666')) == plain_str(bytes.fromhex('005cff'))
True
```

I think using `errors="backslashreplace` is a bad idea here.
Maybe a simple `repr(x)[1:]` is enough because it just skips the initial "b" in the representation of bytes?
```python
>>> print(repr(bytes.fromhex('005c5c786666'))[1:])
'\x00\\\\xff'
>>> print(repr(bytes.fromhex('005cff'))[1:])
'\x00\\\xff'
```

### Scapy version

2.5.0.dev15

### Python version

3.11.3

### Operating system

6.3.9-arch1-1

### Additional environment information

_No response_

### How to reproduce

```python
(UDP()/bytes.fromhex('0123456789abcdef')).show()
```

### Actual result

```python
###[ UDP ]### 
  sport     = domain
  dport     = domain
  len       = None
  chksum    = None
###[ Raw ]### 
     load      = '\x01#Eg\\x89\\xab\\xcd\\xef'
```

### Expected result

```python
###[ UDP ]### 
  sport     = domain
  dport     = domain
  len       = None
  chksum    = None
###[ Raw ]### 
     load      = '\x01#Eg\x89\xab\xcd\xef'
```

### Related resources

_No response_


Discussion:
I just noticed this myself.  Personally, I vote for plain `repr(x)` instead of `repr(x)[1:]` - after all, it is a byte string, might as well leave it marked as such.
> I vote for plain `repr(x)` instead of `repr(x)[1:]` - after all, it is a byte string, might as well leave it marked as such.

I agree.

> I think using errors="backslashreplace is a bad idea here.

I also agree.

TBH this is something that has been bothering me for quite some time, thanks for bringing it up.

Resolving patch:
diff --git a/scapy/fields.py b/scapy/fields.py
--- a/scapy/fields.py
+++ b/scapy/fields.py
@@ -1430,7 +1430,7 @@ def any2i(self, pkt, x):
     def i2repr(self, pkt, x):
         # type: (Optional[Packet], I) -> str
         if isinstance(x, bytes):
-            return repr(plain_str(x))
+            return repr(x)
         return super(_StrField, self).i2repr(pkt, x)
 
     def i2m(self, pkt, x):


Test patch:
diff --git a/test/contrib/macsec.uts b/test/contrib/macsec.uts
--- a/test/contrib/macsec.uts
+++ b/test/contrib/macsec.uts
@@ -21,7 +21,7 @@ assert m[MACsec].SC
 assert m[MACsec].E
 assert m[MACsec].C
 assert m[MACsec].SCI == b'\x52\x54\x00\x13\x01\x56\x00\x01'
-assert m[MACsec].mysummary() == r"AN=0, PN=100, SCI='RT\x00\x13\x01V\x00\x01', IPv4"
+assert m[MACsec].mysummary() == r"AN=0, PN=100, SCI=b'RT\x00\x13\x01V\x00\x01', IPv4"
 
 = MACsec - basic encryption - encrypted
 sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1)
diff --git a/test/contrib/sebek.uts b/test/contrib/sebek.uts
--- a/test/contrib/sebek.uts
+++ b/test/contrib/sebek.uts
@@ -8,7 +8,7 @@
 = Layer binding 1
 pkt = IP() / UDP() / SebekHead() / SebekV1(cmd="diepotato")
 assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 1
-assert pkt.summary() == "IP / UDP / SebekHead / Sebek v1 read ('diepotato')"
+assert pkt.summary() == "IP / UDP / SebekHead / Sebek v1 read (b'diepotato')"
 
 = Packet dissection 1
 pkt = IP(raw(pkt))
@@ -17,7 +17,7 @@ pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 1
 = Layer binding 2
 pkt = IP() / UDP() / SebekHead() / SebekV2Sock(cmd="diepotato")
 assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 2 and pkt[SebekHead].type ==2
-assert pkt.summary() == "IP / UDP / SebekHead / Sebek v2 socket ('diepotato')"
+assert pkt.summary() == "IP / UDP / SebekHead / Sebek v2 socket (b'diepotato')"
 
 = Packet dissection 2
 pkt = IP(raw(pkt))
@@ -26,7 +26,7 @@ pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 2 and pkt[SebekHead
 = Layer binding 3
 pkt = IPv6()/UDP()/SebekHead()/SebekV3()
 assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 3
-assert pkt.summary() == "IPv6 / UDP / SebekHead / Sebek v3 read ('')"
+assert pkt.summary() == "IPv6 / UDP / SebekHead / Sebek v3 read (b'')"
 
 = Packet dissection 3
 pkt = IPv6(raw(pkt))
@@ -35,12 +35,12 @@ pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 3
 = Nonsense summaries
 
 assert SebekHead(version=2).summary() == "Sebek Header v2 read"
-assert SebekV1(cmd="diepotato").summary() == "Sebek v1 ('diepotato')"
-assert SebekV2(cmd="diepotato").summary() == "Sebek v2 ('diepotato')"
-assert (SebekHead()/SebekV2(cmd="nottoday")).summary() == "SebekHead / Sebek v2 read ('nottoday')"
-assert SebekV3(cmd="diepotato").summary() == "Sebek v3 ('diepotato')"
-assert (SebekHead()/SebekV3(cmd="nottoday")).summary() == "SebekHead / Sebek v3 read ('nottoday')"
-assert SebekV3Sock(cmd="diepotato").summary() == "Sebek v3 socket ('diepotato')"
-assert (SebekHead()/SebekV3Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v3 socket ('nottoday')"
-assert SebekV2Sock(cmd="diepotato").summary() == "Sebek v2 socket ('diepotato')"
-assert (SebekHead()/SebekV2Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v2 socket ('nottoday')"
+assert SebekV1(cmd="diepotato").summary() == "Sebek v1 (b'diepotato')"
+assert SebekV2(cmd="diepotato").summary() == "Sebek v2 (b'diepotato')"
+assert (SebekHead()/SebekV2(cmd="nottoday")).summary() == "SebekHead / Sebek v2 read (b'nottoday')"
+assert SebekV3(cmd="diepotato").summary() == "Sebek v3 (b'diepotato')"
+assert (SebekHead()/SebekV3(cmd="nottoday")).summary() == "SebekHead / Sebek v3 read (b'nottoday')"
+assert SebekV3Sock(cmd="diepotato").summary() == "Sebek v3 socket (b'diepotato')"
+assert (SebekHead()/SebekV3Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v3 socket (b'nottoday')"
+assert SebekV2Sock(cmd="diepotato").summary() == "Sebek v2 socket (b'diepotato')"
+assert (SebekHead()/SebekV2Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v2 socket (b'nottoday')"
diff --git a/test/fields.uts b/test/fields.uts
--- a/test/fields.uts
+++ b/test/fields.uts
@@ -218,7 +218,7 @@ class TestStrField(Packet):
 p = TestStrField(s1="cafe", s2="deadbeef")
 assert raw(p) == b'\x04\x00cafedeadbeef'
 print(p.sprintf("%s1% %s2%"))
-assert p.sprintf("%s1% %s2%") == "'cafe' 'deadbeef'"
+assert p.sprintf("%s1% %s2%") == "b'cafe' b'deadbeef'"
 
 
 = StrFieldUtf16