• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 13600950642

01 Mar 2025 04:10AM UTC coverage: 74.211% (+3.2%) from 71.055%
13600950642

Pull #2546

github

web-flow
Merge 77df40314 into 60cff2437
Pull Request #2546: ssh: Allow passing `disabled_algorithms` keyword argument from `ssh` to paramiko

3812 of 6380 branches covered (59.75%)

0 of 1 new or added line in 1 file covered. (0.0%)

1243 existing lines in 37 files now uncovered.

13352 of 17992 relevant lines covered (74.21%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.24
/pwnlib/util/fiddling.py
1
# -*- coding: utf-8 -*-
2
from __future__ import absolute_import
1✔
3
from __future__ import division
1✔
4

5
import base64
1✔
6
import binascii
1✔
7
import random
1✔
8
import re
1✔
9
import os
1✔
10
import string
1✔
11

12
from io import BytesIO
1✔
13

14
from pwnlib.context import LocalNoarchContext
1✔
15
from pwnlib.context import context
1✔
16
from pwnlib.log import getLogger
1✔
17
from pwnlib.term import text
1✔
18
from pwnlib.util import iters
1✔
19
from pwnlib.util import lists
1✔
20
from pwnlib.util import packing
1✔
21
from pwnlib.util.cyclic import cyclic
1✔
22
from pwnlib.util.cyclic import de_bruijn
1✔
23
from pwnlib.util.cyclic import cyclic_find
1✔
24

25
log = getLogger(__name__)
1✔
26

27
def unhex(s):
1✔
28
    r"""unhex(s) -> str
29

30
    Hex-decodes a string.
31

32
    Example:
33

34
        >>> unhex("74657374")
35
        b'test'
36
        >>> unhex("F\n")
37
        b'\x0f'
38
        >>> unhex(bytearray(b" F "))
39
        b'\x0f'
40
    """
41
    s = s.strip()
1✔
42
    if len(s) % 2 != 0:
1✔
43
        if isinstance(s, (bytes, bytearray)):
1✔
44
            s = b'0' + s
1✔
45
        else:
46
            s = '0' + s
1✔
47
    return binascii.unhexlify(s)
1✔
48

49
def enhex(x):
1✔
50
    """enhex(x) -> str
51

52
    Hex-encodes a string.
53

54
    Example:
55

56
        >>> enhex(b"test")
57
        '74657374'
58
    """
59
    x = binascii.hexlify(x)
1✔
60
    if not hasattr(x, 'encode'):
1!
61
        x = x.decode('ascii')
1✔
62
    return x
1✔
63

64
def urlencode(s):
1✔
65
    """urlencode(s) -> str
66

67
    URL-encodes a string.
68

69
    Example:
70

71
        >>> urlencode("test")
72
        '%74%65%73%74'
73
    """
74
    return ''.join(['%%%02x' % ord(c) for c in s])
1✔
75

76
def urldecode(s, ignore_invalid = False):
1✔
77
    """urldecode(s, ignore_invalid = False) -> str
78

79
    URL-decodes a string.
80

81
    Example:
82

83
        >>> urldecode("test%20%41")
84
        'test A'
85
        >>> urldecode("%qq")
86
        Traceback (most recent call last):
87
        ...
88
        ValueError: Invalid input to urldecode
89
        >>> urldecode("%qq", ignore_invalid = True)
90
        '%qq'
91
    """
92
    res = ''
1✔
93
    n = 0
1✔
94
    while n < len(s):
1✔
95
        if s[n] != '%':
1✔
96
            res += s[n]
1✔
97
            n += 1
1✔
98
        else:
99
            cur = s[n+1:n+3]
1✔
100
            if re.match('[0-9a-fA-F]{2}', cur):
1✔
101
                res += chr(int(cur, 16))
1✔
102
                n += 3
1✔
103
            elif ignore_invalid:
1✔
104
                res += '%'
1✔
105
                n += 1
1✔
106
            else:
107
                raise ValueError("Invalid input to urldecode")
1✔
108
    return res
1✔
109

110
def bits(s, endian = 'big', zero = 0, one = 1):
1✔
111
    """bits(s, endian = 'big', zero = 0, one = 1) -> list
112

113
    Converts the argument into a list of bits.
114

115
    Arguments:
116
        s: A string or number to be converted into bits.
117
        endian (str): The binary endian, default 'big'.
118
        zero: The representing a 0-bit.
119
        one: The representing a 1-bit.
120

121
    Returns:
122
        A list consisting of the values specified in `zero` and `one`.
123

124
    Examples:
125

126
        >>> bits(511, zero = "+", one = "-")
127
        ['+', '+', '+', '+', '+', '+', '+', '-', '-', '-', '-', '-', '-', '-', '-', '-']
128
        >>> sum(bits(b"test"))
129
        17
130
        >>> bits(0)
131
        [0, 0, 0, 0, 0, 0, 0, 0]
132
    """
133

134
    if endian not in ['little', 'big']:
1!
UNCOV
135
        raise ValueError("bits(): 'endian' must be either 'little' or 'big'")
×
136
    else:
137
        little = endian == 'little'
1✔
138

139
    out = []
1✔
140
    if isinstance(s, bytes):
1✔
141
        for b in bytearray(s):
1✔
142
            byte = []
1✔
143
            for _ in range(8):
1✔
144
                byte.append(one if b & 1 else zero)
1✔
145
                b >>= 1
1✔
146
            if little:
1✔
147
                out += byte
1✔
148
            else:
149
                out += byte[::-1]
1✔
150
    elif isinstance(s, int):
1!
151
        if s < 0:
1!
UNCOV
152
            s = s & ((1<<context.bits)-1)
×
153
        if s == 0:
1✔
154
            out.append(zero)
1✔
155
        while s:
1✔
156
            bit, s = one if s & 1 else zero, s >> 1
1✔
157
            out.append(bit)
1✔
158
        while len(out) % 8:
1✔
159
            out.append(zero)
1✔
160
        if not little:
1✔
161
            out = out[::-1]
1✔
162
    else:
UNCOV
163
        raise ValueError("bits(): 's' must be either a string or a number")
×
164

165
    return out
1✔
166

167
def bits_str(s, endian = 'big', zero = '0', one = '1'):
1✔
168
    """bits_str(s, endian = 'big', zero = '0', one = '1') -> str
169

170
    A wrapper around :func:`bits`, which converts the output into a string.
171

172
    Examples:
173

174
       >>> bits_str(511)
175
       '0000000111111111'
176
       >>> bits_str(b"bits_str", endian = "little")
177
       '0100011010010110001011101100111011111010110011100010111001001110'
178
    """
179
    return ''.join(bits(s, endian, zero, one))
1✔
180

181
def unbits(s, endian = 'big'):
1✔
182
    """unbits(s, endian = 'big') -> str
183

184
    Converts an iterable of bits into a string.
185

186
    Arguments:
187
       s: Iterable of bits
188
       endian (str):  The string "little" or "big", which specifies the bits endianness.
189

190
    Returns:
191
       A string of the decoded bits.
192

193
    Example:
194

195
       >>> unbits([1])
196
       b'\\x80'
197
       >>> unbits([1], endian = 'little')
198
       b'\\x01'
199
       >>> unbits(bits(b'hello'), endian = 'little')
200
       b'\\x16\\xa666\\xf6'
201
    """
202
    if endian == 'little':
1✔
203
        u = lambda s: packing._p8lu(int(s[::-1], 2))
1✔
204
    elif endian == 'big':
1!
205
        u = lambda s: packing._p8lu(int(s, 2))
1✔
206
    else:
UNCOV
207
        raise ValueError("unbits(): 'endian' must be either 'little' or 'big'")
×
208

209
    out = b''
1✔
210
    cur = b''
1✔
211

212
    for c in s:
1✔
213
        if c in ['1', 1, True]:
1✔
214
            cur += b'1'
1✔
215
        elif c in ['0', 0, False]:
1!
216
            cur += b'0'
1✔
217
        else:
UNCOV
218
            raise ValueError("unbits(): cannot decode the value %r into a bit" % c)
×
219

220
        if len(cur) == 8:
1✔
221
            out += u(cur)
1✔
222
            cur = b''
1✔
223
    if cur:
1✔
224
        out += u(cur.ljust(8, b'0'))
1✔
225

226
    return out
1✔
227

228

229
def bitswap(s):
1✔
230
    """bitswap(s) -> str
231

232
    Reverses the bits in every byte of a given string.
233

234
    Example:
235

236
        >>> bitswap(b"1234")
237
        b'\\x8cL\\xcc,'
238
    """
239

240
    out = []
1✔
241

242
    for c in s:
1✔
243
        out.append(unbits(bits_str(c)[::-1]))
1✔
244

245
    return b''.join(out)
1✔
246

247
def bitswap_int(n, width):
1✔
248
    """bitswap_int(n) -> int
249

250
    Reverses the bits of a numbers and returns the result as a new number.
251

252
    Arguments:
253
        n (int): The number to swap.
254
        width (int): The width of the integer
255

256
    Examples:
257

258
        >>> hex(bitswap_int(0x1234, 8))
259
        '0x2c'
260
        >>> hex(bitswap_int(0x1234, 16))
261
        '0x2c48'
262
        >>> hex(bitswap_int(0x1234, 24))
263
        '0x2c4800'
264
        >>> hex(bitswap_int(0x1234, 25))
265
        '0x589000'
266
    """
267
    # Make n fit inside the width
268
    n &= (1 << width) - 1
1✔
269

270
    # Convert into bits
271
    s = bits_str(n, endian = 'little').ljust(width, '0')[:width]
1✔
272

273
    # Convert back
274
    return int(s, 2)
1✔
275

276

277
def b64e(s):
1✔
278
    """b64e(s) -> str
279

280
    Base64 encodes a string
281

282
    Example:
283

284
       >>> b64e(b"test")
285
       'dGVzdA=='
286
       """
287
    x = base64.b64encode(s)
1✔
288
    if not hasattr(x, 'encode'):
1!
289
        x = x.decode('ascii')
1✔
290
    return x
1✔
291

292
def b64d(s):
1✔
293
    """b64d(s) -> str
294

295
    Base64 decodes a string
296

297
    Example:
298

299
       >>> b64d('dGVzdA==')
300
       b'test'
301
    """
302
    return base64.b64decode(s)
1✔
303

304
# misc binary functions
305
def xor(*args, **kwargs):
1✔
306
    """xor(*args, cut = 'max') -> str
307

308
    Flattens its arguments using :func:`pwnlib.util.packing.flat` and
309
    then xors them together. If the end of a string is reached, it wraps
310
    around in the string.
311

312
    Arguments:
313
       args: The arguments to be xor'ed together.
314
       cut: How long a string should be returned.
315
            Can be either 'min'/'max'/'left'/'right' or a number.
316

317
    Returns:
318
       The string of the arguments xor'ed together.
319

320
    Example:
321

322
       >>> xor(b'lol', b'hello', 42)
323
       b'. ***'
324
       >>> xor(cut = 'min', other = '')
325
       Traceback (most recent call last):
326
         ...
327
       TypeError: xor() got an unexpected keyword argument 'other'
328
    """
329

330
    cut = kwargs.pop('cut', 'max')
1✔
331

332
    if kwargs != {}:
1✔
333
        raise TypeError("xor() got an unexpected keyword argument '%s'" % kwargs.popitem()[0])
1✔
334

335
    if len(args) == 0:
1!
UNCOV
336
        raise ValueError("Must have something to xor")
×
337

338
    strs = [packing.flat(s, word_size = 8, sign = False, endianness = 'little') for s in args]
1✔
339
    strs = [bytearray(s) for s in strs if s]
1✔
340

341
    if strs == []:
1!
UNCOV
342
        return b''
×
343

344
    if isinstance(cut, int):
1!
UNCOV
345
        cut = cut
×
346
    elif cut == 'left':
1!
347
        cut = len(strs[0])
×
348
    elif cut == 'right':
1!
349
        cut = len(strs[-1])
×
350
    elif cut == 'min':
1!
351
        cut = min(len(s) for s in strs)
×
352
    elif cut == 'max':
1!
353
        cut = max(len(s) for s in strs)
1✔
354
    else:
UNCOV
355
        raise ValueError("Not a valid argument for 'cut'")
×
356

357
    def get(n):
1✔
358
        rv = 0
1✔
359
        for s in strs: rv ^= s[n%len(s)]
1✔
360
        return packing._p8lu(rv)
1✔
361

362
    return b''.join(map(get, range(cut)))
1✔
363

364
def xor_pair(data, avoid = b'\x00\n'):
1✔
365
    """xor_pair(data, avoid = '\\x00\\n') -> None or (str, str)
366

367
    Finds two strings that will xor into a given string, while only
368
    using a given alphabet.
369

370
    Arguments:
371
        data (str): The desired string.
372
        avoid: The list of disallowed characters. Defaults to nulls and newlines.
373

374
    Returns:
375
        Two strings which will xor to the given string. If no such two strings exist, then None is returned.
376

377
    Example:
378

379
        >>> xor_pair(b"test")
380
        (b'\\x01\\x01\\x01\\x01', b'udru')
381
    """
382

383
    if isinstance(data, int):
1!
UNCOV
384
        data = packing.pack(data)
×
385

386
    if not isinstance(avoid, (bytes, bytearray)):
1✔
387
        avoid = avoid.encode('utf-8')
1✔
388

389
    avoid = bytearray(avoid)
1✔
390
    alphabet = list(packing._p8lu(n) for n in range(256) if n not in avoid)
1✔
391

392
    res1 = b''
1✔
393
    res2 = b''
1✔
394

395
    for c1 in bytearray(data):
1✔
396
        if context.randomize:
1!
UNCOV
397
            random.shuffle(alphabet)
×
398
        for c2 in alphabet:
1!
399
            c3 = packing._p8lu(c1 ^ packing.u8(c2))
1✔
400
            if c3 in alphabet:
1✔
401
                res1 += c2
1✔
402
                res2 += c3
1✔
403
                break
1✔
404
        else:
UNCOV
405
            return None
×
406

407
    return res1, res2
1✔
408

409
def xor_key(data, avoid=b'\x00\n', size=None):
1✔
410
    r"""xor_key(data, size=None, avoid='\x00\n') -> None or (int, str)
411

412
    Finds a ``size``-width value that can be XORed with a string
413
    to produce ``data``, while neither the XOR value or XOR string
414
    contain any bytes in ``avoid``.
415

416
    Arguments:
417
        data (str): The desired string.
418
        avoid: The list of disallowed characters. Defaults to nulls and newlines.
419
        size (int): Size of the desired output value, default is word size.
420

421
    Returns:
422
        A tuple containing two strings; the XOR key and the XOR string.
423
        If no such pair exists, None is returned.
424

425
    Example:
426

427
        >>> xor_key(b"Hello, world")
428
        (b'\x01\x01\x01\x01', b'Idmmn-!vnsme')
429
    """
430
    size = size or context.bytes
1✔
431

432
    if len(data) % size:
1!
UNCOV
433
        log.error("Data must be padded to size for xor_key")
×
434

435
    words    = lists.group(size, data)
1✔
436
    columns  = [b''] * size
1✔
437
    for word in words:
1✔
438
        for i,byte in enumerate(bytearray(word)):
1✔
439
            columns[i] += bytearray((byte,))
1✔
440

441
    avoid = bytearray(avoid)
1✔
442
    alphabet = bytearray(n for n in range(256) if n not in avoid)
1✔
443

444
    result = b''
1✔
445

446
    for column in columns:
1✔
447
        if context.randomize:
1!
UNCOV
448
            random.shuffle(alphabet)
×
449
        for c2 in alphabet:
1!
450
            if all(c^c2 in alphabet for c in column):
1✔
451
                result += packing._p8lu(c2)
1✔
452
                break
1✔
453
        else:
UNCOV
454
            return None
×
455

456
    return result, xor(data, result)
1✔
457

458
def randoms(count, alphabet = string.ascii_lowercase):
1✔
459
    """randoms(count, alphabet = string.ascii_lowercase) -> str
460

461
    Returns a random string of a given length using only the specified alphabet.
462

463
    Arguments:
464
        count (int): The length of the desired string.
465
        alphabet: The alphabet of allowed characters. Defaults to all lowercase characters.
466

467
    Returns:
468
        A random string.
469

470
    Example:
471

472
        >>> randoms(10) #doctest: +SKIP
473
        'evafjilupm'
474
    """
475

476
    return ''.join(random.choice(alphabet) for _ in range(count))
1✔
477

478

479
def rol(n, k, word_size = None):
1✔
480
    """Returns a rotation by `k` of `n`.
481

482
    When `n` is a number, then means ``((n << k) | (n >> (word_size - k)))`` truncated to `word_size` bits.
483

484
    When `n` is a list, tuple or string, this is ``n[k % len(n):] + n[:k % len(n)]``.
485

486
    Arguments:
487
        n: The value to rotate.
488
        k(int): The rotation amount. Can be a positive or negative number.
489
        word_size(int): If `n` is a number, then this is the assumed bitsize of `n`.  Defaults to :data:`pwnlib.context.word_size` if `None` .
490

491
    Example:
492

493
        >>> rol('abcdefg', 2)
494
        'cdefgab'
495
        >>> rol('abcdefg', -2)
496
        'fgabcde'
497
        >>> hex(rol(0x86, 3, 8))
498
        '0x34'
499
        >>> hex(rol(0x86, -3, 8))
500
        '0xd0'
501
    """
502

503
    word_size = word_size or context.word_size
1✔
504

505
    if not isinstance(word_size, int) or word_size <= 0:
1!
UNCOV
506
        raise ValueError("rol(): 'word_size' must be a strictly positive integer")
×
507

508
    if not isinstance(k, int):
1!
UNCOV
509
        raise ValueError("rol(): 'k' must be an integer")
×
510

511
    if isinstance(n, (bytes, str, list, tuple)):
1✔
512
        return n[k % len(n):] + n[:k % len(n)]
1✔
513
    elif isinstance(n, int):
1!
514
        k = k % word_size
1✔
515
        n = (n << k) | (n >> (word_size - k))
1✔
516
        n &= (1 << word_size) - 1
1✔
517

518
        return n
1✔
519
    else:
UNCOV
520
        raise ValueError("rol(): 'n' must be an integer, string, list or tuple")
×
521

522
def ror(n, k, word_size = None):
1✔
523
    """A simple wrapper around :func:`rol`, which negates the values of `k`."""
524

UNCOV
525
    return rol(n, -k, word_size)
×
526

527
def naf(n):
1✔
528
    """naf(int) -> int generator
529

530
    Returns a generator for the non-adjacent form (NAF[1]) of a number, `n`.  If
531
    `naf(n)` generates `z_0, z_1, ...`, then `n == z_0 + z_1 * 2 + z_2 * 2**2,
532
    ...`.
533

534
    [1] https://en.wikipedia.org/wiki/Non-adjacent_form
535

536
    Example:
537

538
      >>> n = 45
539
      >>> m = 0
540
      >>> x = 1
541
      >>> for z in naf(n):
542
      ...     m += x * z
543
      ...     x *= 2
544
      >>> n == m
545
      True
546

547
    """
548
    while n:
1✔
549
        z = 2 - n % 4 if n & 1 else 0
1✔
550
        n = (n - z) // 2
1✔
551
        yield z
1✔
552

553
def isprint(c):
1✔
554
    """isprint(c) -> bool
555

556
    Return True if a character is printable"""
557
    if isinstance(c, str):
1!
UNCOV
558
        c = ord(c)
×
559
    t = bytearray(string.ascii_letters + string.digits + string.punctuation + ' ', 'ascii')
1✔
560
    return c in t
1✔
561

562

563
def hexii(s, width = 16, skip = True):
1✔
564
    """hexii(s, width = 16, skip = True) -> str
565

566
    Return a HEXII-dump of a string.
567

568
    Arguments:
569
        s(str): The string to dump
570
        width(int): The number of characters per line
571
        skip(bool): Should repeated lines be replaced by a "*"
572

573
    Returns:
574
        A HEXII-dump in the form of a string.
575
    """
576

577
    return hexdump(s, width, skip, True)
1✔
578

579
def _hexiichar(c):
1✔
580
    HEXII = bytearray((string.punctuation + string.digits + string.ascii_letters).encode())
1✔
581
    if c in HEXII:
1✔
582
        return ".%c " % c
1✔
583
    elif c == 0:
1✔
584
        return "   "
1✔
585
    elif c == 0xff:
1✔
586
        return "## "
1✔
587
    else:
588
        return "%02x " % c
1✔
589

590
default_style = {
1✔
591
    'marker':       text.gray if text.has_gray else text.blue,
592
    'nonprintable': text.gray if text.has_gray else text.blue,
593
    '00':           text.red,
594
    '0a':           text.red,
595
    'ff':           text.green,
596
}
597

598
cyclic_pregen = b''
1✔
599
de_bruijn_gen = de_bruijn()
1✔
600

601
def sequential_lines(a,b):
1✔
602
    return (a+b) in cyclic_pregen
1✔
603

604
def update_cyclic_pregenerated(size):
1✔
605
    global cyclic_pregen
606
    while size > len(cyclic_pregen):
1✔
607
        cyclic_pregen += packing._p8lu(next(de_bruijn_gen))
1✔
608

609
def hexdump_iter(fd, width=16, skip=True, hexii=False, begin=0, style=None,
1✔
610
                 highlight=None, cyclic=False, groupsize=4, total=True):
611
    r"""hexdump_iter(s, width = 16, skip = True, hexii = False, begin = 0, style = None,
612
                    highlight = None, cyclic = False, groupsize=4, total = True) -> str generator
613

614
    Return a hexdump-dump of a string as a generator of lines.  Unless you have
615
    massive amounts of data you probably want to use :meth:`hexdump`.
616

617
    Arguments:
618
        fd(file): File object to dump.  Use :meth:`StringIO.StringIO` or :meth:`hexdump` to dump a string.
619
        width(int): The number of characters per line
620
        groupsize(int): The number of characters per group
621
        skip(bool): Set to True, if repeated lines should be replaced by a "*"
622
        hexii(bool): Set to True, if a hexii-dump should be returned instead of a hexdump.
623
        begin(int):  Offset of the first byte to print in the left column
624
        style(dict): Color scheme to use.
625
        highlight(iterable): Byte values to highlight.
626
        cyclic(bool): Attempt to skip consecutive, unmodified cyclic lines
627
        total(bool): Set to True, if total bytes should be printed
628

629
    Returns:
630
        A generator producing the hexdump-dump one line at a time.
631

632
    Example:
633

634
        >>> tmp = tempfile.NamedTemporaryFile()
635
        >>> _ = tmp.write(b'XXXXHELLO, WORLD')
636
        >>> tmp.flush()
637
        >>> _ = tmp.seek(4)
638
        >>> print('\n'.join(hexdump_iter(tmp)))
639
        00000000  48 45 4c 4c  4f 2c 20 57  4f 52 4c 44               │HELL│O, W│ORLD│
640
        0000000c
641

642
        >>> t = tube()
643
        >>> t.unrecv(b'I know kung fu')
644
        >>> print('\n'.join(hexdump_iter(t)))
645
        00000000  49 20 6b 6e  6f 77 20 6b  75 6e 67 20  66 75        │I kn│ow k│ung │fu│
646
        0000000e
647
    """
648
    style     = style or {}
1✔
649
    highlight = highlight or []
1✔
650

651
    if groupsize < 1:
1✔
652
        groupsize = width
1✔
653

654
    for b in highlight:
1✔
655
        if isinstance(b, str):
1!
UNCOV
656
            b = ord(b)
×
657
        style['%02x' % b] = text.white_on_red
1✔
658
    _style = style
1✔
659
    style = default_style.copy()
1✔
660
    style.update(_style)
1✔
661

662
    skipping    = False
1✔
663
    lines       = []
1✔
664
    last_unique = ''
1✔
665
    byte_width  = len('00 ')
1✔
666
    spacer      = ' '
1✔
667
    marker      = (style.get('marker') or (lambda s:s))('│')
1✔
668

669
    if not hexii:
1✔
670
        def style_byte(by):
1✔
671
            hbyte = '%02x' % by
1✔
672
            b = packing._p8lu(by)
1✔
673
            abyte = chr(by) if isprint(b) else '·'
1✔
674
            if hbyte in style:
1✔
675
                st = style[hbyte]
1✔
676
            elif isprint(b):
1✔
677
                st = style.get('printable')
1✔
678
            else:
679
                st = style.get('nonprintable')
1✔
680
            if st:
1✔
681
                hbyte = st(hbyte)
1✔
682
                abyte = st(abyte)
1✔
683
            return hbyte, abyte
1✔
684
        cache = [style_byte(b) for b in range(256)]
1✔
685

686
    numb = 0
1✔
687
    while True:
1✔
688
        offset = begin + numb
1✔
689

690
        # If a tube is passed in as fd, it will raise EOFError when it runs
691
        # out of data, unlike a file or StringIO object, which return an empty
692
        # string.
693
        try:
1✔
694
            chunk = fd.read(width)
1✔
695
        except EOFError:
1✔
696
            chunk = b''
1✔
697

698
        # We have run out of data, exit the loop
699
        if chunk == b'':
1✔
700
            break
1✔
701

702
        # Advance the cursor by the number of bytes we actually read
703
        numb += len(chunk)
1✔
704

705
        # Update the cyclic pattern in case
706
        if cyclic:
1✔
707
            update_cyclic_pregenerated(numb)
1✔
708

709
        # If this chunk is the same as the last unique chunk,
710
        # use a '*' instead.
711
        if skip and last_unique:
1✔
712
            same_as_last_line = (last_unique == chunk)
1✔
713
            lines_are_sequential = (cyclic and sequential_lines(last_unique, chunk))
1✔
714
            last_unique = chunk
1✔
715

716
            if same_as_last_line or lines_are_sequential:
1✔
717

718
                # If we have not already printed a "*", do so
719
                if not skipping:
1✔
720
                    yield '*'
1✔
721
                    skipping = True
1✔
722

723
                # Move on to the next chunk
724
                continue
1✔
725

726
        # Chunk is unique, no longer skipping
727
        skipping = False
1✔
728
        last_unique = chunk
1✔
729

730
        # Generate contents for line
731
        hexbytes = ''
1✔
732
        printable = ''
1✔
733
        color_chars = 0
1✔
734
        abyte = abyte_previous = ''
1✔
735
        for i, b in enumerate(bytearray(chunk)):
1✔
736
            if not hexii:
1✔
737
                abyte_previous = abyte
1✔
738
                hbyte, abyte = cache[b]
1✔
739
                color_chars += len(hbyte) - 2
1✔
740
            else:
741
                hbyte, abyte = _hexiichar(b), ''
1✔
742

743
            if (i + 1) % groupsize == 0 and i < width - 1:
1✔
744
                hbyte += spacer
1✔
745
                abyte_previous += abyte
1✔
746
                abyte = marker
1✔
747

748
            hexbytes += hbyte + ' '
1✔
749
            printable += abyte_previous
1✔
750

751
        if abyte != marker:
1✔
752
            printable += abyte
1✔
753

754
        dividers_per_line = (width // groupsize)
1✔
755
        if width % groupsize == 0:
1✔
756
            dividers_per_line -= 1
1✔
757

758
        if hexii:
1✔
759
            line_fmt = '%%(offset)08x  %%(hexbytes)-%is│' % (width*byte_width)
1✔
760
        else:
761
            line_fmt = '%%(offset)08x  %%(hexbytes)-%is │%%(printable)s│' % (
1✔
762
                 (width * byte_width)
763
                + color_chars
764
                + dividers_per_line )
765

766
        line = line_fmt % {'offset': offset, 'hexbytes': hexbytes, 'printable': printable}
1✔
767
        yield line
1✔
768

769
    if total:
1✔
770
        line = "%08x" % (begin + numb)
1✔
771
        yield line
1✔
772

773
def hexdump(s, width=16, skip=True, hexii=False, begin=0, style=None,
1✔
774
            highlight=None, cyclic=False, groupsize=4, total=True):
775
    r"""hexdump(s, width = 16, skip = True, hexii = False, begin = 0, style = None,
776
                highlight = None, cyclic = False, groupsize=4, total = True) -> str
777

778
    Return a hexdump-dump of a string.
779

780
    Arguments:
781
        s(bytes): The data to hexdump.
782
        width(int): The number of characters per line
783
        groupsize(int): The number of characters per group
784
        skip(bool): Set to True, if repeated lines should be replaced by a "*"
785
        hexii(bool): Set to True, if a hexii-dump should be returned instead of a hexdump.
786
        begin(int):  Offset of the first byte to print in the left column
787
        style(dict): Color scheme to use.
788
        highlight(iterable): Byte values to highlight.
789
        cyclic(bool): Attempt to skip consecutive, unmodified cyclic lines
790
        total(bool): Set to True, if total bytes should be printed
791

792
    Returns:
793
        A hexdump-dump in the form of a string.
794

795
    Examples:
796

797
        >>> print(hexdump(b"abc"))
798
        00000000  61 62 63                                            │abc│
799
        00000003
800

801
        >>> print(hexdump(b'A'*32))
802
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
803
        *
804
        00000020
805

806
        >>> print(hexdump(b'A'*32, width=8))
807
        00000000  41 41 41 41  41 41 41 41  │AAAA│AAAA│
808
        *
809
        00000020
810

811
        >>> print(hexdump(cyclic(32), width=8, begin=0xdead0000, hexii=True))
812
        dead0000  .a  .a  .a  .a   .b  .a  .a  .a  │
813
        dead0008  .c  .a  .a  .a   .d  .a  .a  .a  │
814
        dead0010  .e  .a  .a  .a   .f  .a  .a  .a  │
815
        dead0018  .g  .a  .a  .a   .h  .a  .a  .a  │
816
        dead0020
817

818
        >>> print(hexdump(bytearray(range(256))))
819
        00000000  00 01 02 03  04 05 06 07  08 09 0a 0b  0c 0d 0e 0f  │····│····│····│····│
820
        00000010  10 11 12 13  14 15 16 17  18 19 1a 1b  1c 1d 1e 1f  │····│····│····│····│
821
        00000020  20 21 22 23  24 25 26 27  28 29 2a 2b  2c 2d 2e 2f  │ !"#│$%&'│()*+│,-./│
822
        00000030  30 31 32 33  34 35 36 37  38 39 3a 3b  3c 3d 3e 3f  │0123│4567│89:;│<=>?│
823
        00000040  40 41 42 43  44 45 46 47  48 49 4a 4b  4c 4d 4e 4f  │@ABC│DEFG│HIJK│LMNO│
824
        00000050  50 51 52 53  54 55 56 57  58 59 5a 5b  5c 5d 5e 5f  │PQRS│TUVW│XYZ[│\]^_│
825
        00000060  60 61 62 63  64 65 66 67  68 69 6a 6b  6c 6d 6e 6f  │`abc│defg│hijk│lmno│
826
        00000070  70 71 72 73  74 75 76 77  78 79 7a 7b  7c 7d 7e 7f  │pqrs│tuvw│xyz{│|}~·│
827
        00000080  80 81 82 83  84 85 86 87  88 89 8a 8b  8c 8d 8e 8f  │····│····│····│····│
828
        00000090  90 91 92 93  94 95 96 97  98 99 9a 9b  9c 9d 9e 9f  │····│····│····│····│
829
        000000a0  a0 a1 a2 a3  a4 a5 a6 a7  a8 a9 aa ab  ac ad ae af  │····│····│····│····│
830
        000000b0  b0 b1 b2 b3  b4 b5 b6 b7  b8 b9 ba bb  bc bd be bf  │····│····│····│····│
831
        000000c0  c0 c1 c2 c3  c4 c5 c6 c7  c8 c9 ca cb  cc cd ce cf  │····│····│····│····│
832
        000000d0  d0 d1 d2 d3  d4 d5 d6 d7  d8 d9 da db  dc dd de df  │····│····│····│····│
833
        000000e0  e0 e1 e2 e3  e4 e5 e6 e7  e8 e9 ea eb  ec ed ee ef  │····│····│····│····│
834
        000000f0  f0 f1 f2 f3  f4 f5 f6 f7  f8 f9 fa fb  fc fd fe ff  │····│····│····│····│
835
        00000100
836

837
        >>> print(hexdump(bytearray(range(256)), hexii=True))
838
        00000000      01  02  03   04  05  06  07   08  09  0a  0b   0c  0d  0e  0f  │
839
        00000010  10  11  12  13   14  15  16  17   18  19  1a  1b   1c  1d  1e  1f  │
840
        00000020  20  .!  ."  .#   .$  .%  .&  .'   .(  .)  .*  .+   .,  .-  ..  ./  │
841
        00000030  .0  .1  .2  .3   .4  .5  .6  .7   .8  .9  .:  .;   .<  .=  .>  .?  │
842
        00000040  .@  .A  .B  .C   .D  .E  .F  .G   .H  .I  .J  .K   .L  .M  .N  .O  │
843
        00000050  .P  .Q  .R  .S   .T  .U  .V  .W   .X  .Y  .Z  .[   .\  .]  .^  ._  │
844
        00000060  .`  .a  .b  .c   .d  .e  .f  .g   .h  .i  .j  .k   .l  .m  .n  .o  │
845
        00000070  .p  .q  .r  .s   .t  .u  .v  .w   .x  .y  .z  .{   .|  .}  .~  7f  │
846
        00000080  80  81  82  83   84  85  86  87   88  89  8a  8b   8c  8d  8e  8f  │
847
        00000090  90  91  92  93   94  95  96  97   98  99  9a  9b   9c  9d  9e  9f  │
848
        000000a0  a0  a1  a2  a3   a4  a5  a6  a7   a8  a9  aa  ab   ac  ad  ae  af  │
849
        000000b0  b0  b1  b2  b3   b4  b5  b6  b7   b8  b9  ba  bb   bc  bd  be  bf  │
850
        000000c0  c0  c1  c2  c3   c4  c5  c6  c7   c8  c9  ca  cb   cc  cd  ce  cf  │
851
        000000d0  d0  d1  d2  d3   d4  d5  d6  d7   d8  d9  da  db   dc  dd  de  df  │
852
        000000e0  e0  e1  e2  e3   e4  e5  e6  e7   e8  e9  ea  eb   ec  ed  ee  ef  │
853
        000000f0  f0  f1  f2  f3   f4  f5  f6  f7   f8  f9  fa  fb   fc  fd  fe  ##  │
854
        00000100
855

856
        >>> print(hexdump(b'X' * 64))
857
        00000000  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
858
        *
859
        00000040
860

861
        >>> print(hexdump(b'X' * 64, skip=False))
862
        00000000  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
863
        00000010  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
864
        00000020  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
865
        00000030  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
866
        00000040
867

868
        >>> print(hexdump(fit({0x10: b'X'*0x20, 0x50-1: b'\xff'*20}, length=0xc0) + b'\x00'*32))
869
        00000000  61 61 61 61  62 61 61 61  63 61 61 61  64 61 61 61  │aaaa│baaa│caaa│daaa│
870
        00000010  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
871
        *
872
        00000030  6d 61 61 61  6e 61 61 61  6f 61 61 61  70 61 61 61  │maaa│naaa│oaaa│paaa│
873
        00000040  71 61 61 61  72 61 61 61  73 61 61 61  74 61 61 ff  │qaaa│raaa│saaa│taa·│
874
        00000050  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  │····│····│····│····│
875
        00000060  ff ff ff 61  7a 61 61 62  62 61 61 62  63 61 61 62  │···a│zaab│baab│caab│
876
        00000070  64 61 61 62  65 61 61 62  66 61 61 62  67 61 61 62  │daab│eaab│faab│gaab│
877
        00000080  68 61 61 62  69 61 61 62  6a 61 61 62  6b 61 61 62  │haab│iaab│jaab│kaab│
878
        00000090  6c 61 61 62  6d 61 61 62  6e 61 61 62  6f 61 61 62  │laab│maab│naab│oaab│
879
        000000a0  70 61 61 62  71 61 61 62  72 61 61 62  73 61 61 62  │paab│qaab│raab│saab│
880
        000000b0  74 61 61 62  75 61 61 62  76 61 61 62  77 61 61 62  │taab│uaab│vaab│waab│
881
        000000c0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
882
        *
883
        000000e0
884

885
        >>> print(hexdump(fit({0x10: b'X'*0x20, 0x50-1: b'\xff'*20}, length=0xc0) + b'\x00'*32, cyclic=1))
886
        00000000  61 61 61 61  62 61 61 61  63 61 61 61  64 61 61 61  │aaaa│baaa│caaa│daaa│
887
        00000010  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
888
        *
889
        00000030  6d 61 61 61  6e 61 61 61  6f 61 61 61  70 61 61 61  │maaa│naaa│oaaa│paaa│
890
        00000040  71 61 61 61  72 61 61 61  73 61 61 61  74 61 61 ff  │qaaa│raaa│saaa│taa·│
891
        00000050  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  │····│····│····│····│
892
        00000060  ff ff ff 61  7a 61 61 62  62 61 61 62  63 61 61 62  │···a│zaab│baab│caab│
893
        00000070  64 61 61 62  65 61 61 62  66 61 61 62  67 61 61 62  │daab│eaab│faab│gaab│
894
        *
895
        000000c0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
896
        *
897
        000000e0
898

899
        >>> print(hexdump(fit({0x10: b'X'*0x20, 0x50-1: b'\xff'*20}, length=0xc0) + b'\x00'*32, cyclic=1, hexii=1))
900
        00000000  .a  .a  .a  .a   .b  .a  .a  .a   .c  .a  .a  .a   .d  .a  .a  .a  │
901
        00000010  .X  .X  .X  .X   .X  .X  .X  .X   .X  .X  .X  .X   .X  .X  .X  .X  │
902
        *
903
        00000030  .m  .a  .a  .a   .n  .a  .a  .a   .o  .a  .a  .a   .p  .a  .a  .a  │
904
        00000040  .q  .a  .a  .a   .r  .a  .a  .a   .s  .a  .a  .a   .t  .a  .a  ##  │
905
        00000050  ##  ##  ##  ##   ##  ##  ##  ##   ##  ##  ##  ##   ##  ##  ##  ##  │
906
        00000060  ##  ##  ##  .a   .z  .a  .a  .b   .b  .a  .a  .b   .c  .a  .a  .b  │
907
        00000070  .d  .a  .a  .b   .e  .a  .a  .b   .f  .a  .a  .b   .g  .a  .a  .b  │
908
        *
909
        000000c0                                                                     │
910
        *
911
        000000e0
912

913
        >>> print(hexdump(b'A'*16, width=9))
914
        00000000  41 41 41 41  41 41 41 41  41  │AAAA│AAAA│A│
915
        00000009  41 41 41 41  41 41 41         │AAAA│AAA│
916
        00000010
917
        >>> print(hexdump(b'A'*16, width=10))
918
        00000000  41 41 41 41  41 41 41 41  41 41  │AAAA│AAAA│AA│
919
        0000000a  41 41 41 41  41 41               │AAAA│AA│
920
        00000010
921
        >>> print(hexdump(b'A'*16, width=11))
922
        00000000  41 41 41 41  41 41 41 41  41 41 41  │AAAA│AAAA│AAA│
923
        0000000b  41 41 41 41  41                     │AAAA│A│
924
        00000010
925
        >>> print(hexdump(b'A'*16, width=12))
926
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│
927
        0000000c  41 41 41 41                            │AAAA│
928
        00000010
929
        >>> print(hexdump(b'A'*16, width=13))
930
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41  │AAAA│AAAA│AAAA│A│
931
        0000000d  41 41 41                                   │AAA│
932
        00000010
933
        >>> print(hexdump(b'A'*16, width=14))
934
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41  │AAAA│AAAA│AAAA│AA│
935
        0000000e  41 41                                         │AA│
936
        00000010
937
        >>> print(hexdump(b'A'*16, width=15))
938
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41  │AAAA│AAAA│AAAA│AAA│
939
        0000000f  41                                               │A│
940
        00000010
941

942
        >>> print(hexdump(b'A'*24, width=16, groupsize=8))
943
        00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  │AAAAAAAA│AAAAAAAA│
944
        00000010  41 41 41 41 41 41 41 41                           │AAAAAAAA│
945
        00000018
946
        >>> print(hexdump(b'A'*24, width=16, groupsize=-1))
947
        00000000  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  │AAAAAAAAAAAAAAAA│
948
        00000010  41 41 41 41 41 41 41 41                          │AAAAAAAA│
949
        00000018
950

951
        >>> print(hexdump(b'A'*24, width=16, total=False))
952
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
953
        00000010  41 41 41 41  41 41 41 41                            │AAAA│AAAA│
954
        >>> print(hexdump(b'A'*24, width=16, groupsize=8, total=False))
955
        00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  │AAAAAAAA│AAAAAAAA│
956
        00000010  41 41 41 41 41 41 41 41                           │AAAAAAAA│
957
    """
958
    s = packing.flat(s, stacklevel=1)
1✔
959
    return '\n'.join(hexdump_iter(BytesIO(s),
1✔
960
                                  width,
961
                                  skip,
962
                                  hexii,
963
                                  begin,
964
                                  style,
965
                                  highlight,
966
                                  cyclic,
967
                                  groupsize,
968
                                  total))
969

970
def negate(value, width = None):
1✔
971
    """
972
    Returns the two's complement of 'value'.
973
    """
974
    if width is None:
1!
975
        width = context.bits
1✔
976
    mask = ((1<<width)-1)
1✔
977
    return ((mask+1) - value) & mask
1✔
978

979
def bnot(value, width=None):
1✔
980
    """
981
    Returns the binary inverse of 'value'.
982
    """
983
    if width is None:
1!
984
        width = context.bits
1✔
985
    mask = ((1<<width)-1)
1✔
986
    return mask ^ value
1✔
987

988
@LocalNoarchContext
1✔
989
def js_escape(data, padding=context.cyclic_alphabet[0:1], **kwargs):
1✔
990
    r"""js_escape(data, padding=context.cyclic_alphabet[0:1], endian = None, **kwargs) -> str
991

992
    Pack data as an escaped Unicode string for use in JavaScript's `unescape()` function
993

994
    Arguments:
995
        data (bytes): Bytes to pack
996
        padding (bytes): A single byte to use as padding if data is of uneven length
997
        endian (str): Endianness with which to pack the string ("little"/"big")
998

999
    Returns:
1000
        A string representation of the packed data
1001

1002
    >>> js_escape(b'\xde\xad\xbe\xef')
1003
    '%uadde%uefbe'
1004

1005
    >>> js_escape(b'\xde\xad\xbe\xef', endian='big')
1006
    '%udead%ubeef'
1007

1008
    >>> js_escape(b'\xde\xad\xbe')
1009
    '%uadde%u61be'
1010

1011
    >>> js_escape(b'aaaa')
1012
    '%u6161%u6161'
1013
    """
1014
    data = packing._need_bytes(data)
1✔
1015

1016
    padding = packing._need_bytes(padding)
1✔
1017
    if len(padding) != 1:
1!
UNCOV
1018
        raise ValueError("Padding must be a single byte")
×
1019

1020
    if len(data) % 2:
1✔
1021
        data += padding[0:1]
1✔
1022

1023
    data = bytearray(data)
1✔
1024

1025
    if context.endian == 'little':
1✔
1026
        return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for b, a in iters.group(2, data))
1✔
1027
    else:
1028
        return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for a, b in iters.group(2, data))
1✔
1029

1030
@LocalNoarchContext
1✔
1031
def js_unescape(s, **kwargs):
1✔
1032
    r"""js_unescape(s, endian = None, **kwargs) -> bytes
1033

1034
    Unpack an escaped Unicode string from JavaScript's `escape()` function
1035

1036
    Arguments:
1037
        s (str): Escaped string to unpack
1038
        endian (str): Endianness with which to unpack the string ("little"/"big")
1039

1040
    Returns:
1041
        A bytes representation of the unpacked data
1042

1043
    >>> js_unescape('%uadde%uefbe')
1044
    b'\xde\xad\xbe\xef'
1045

1046
    >>> js_unescape('%udead%ubeef', endian='big')
1047
    b'\xde\xad\xbe\xef'
1048

1049
    >>> js_unescape('abc%u4141123')
1050
    b'a\x00b\x00c\x00AA1\x002\x003\x00'
1051

1052
    >>> data = b'abcdABCD1234!@#$\x00\x01\x02\x03\x80\x81\x82\x83'
1053
    >>> js_unescape(js_escape(data)) == data
1054
    True
1055

1056
    >>> js_unescape('%u4141%u42')
1057
    Traceback (most recent call last):
1058
    ValueError: Incomplete Unicode token: %u42
1059

1060
    >>> js_unescape('%u4141%uwoot%4141')
1061
    Traceback (most recent call last):
1062
    ValueError: Failed to decode token: %uwoot
1063

1064
    >>> js_unescape('%u4141%E4%F6%FC%u4141')
1065
    Traceback (most recent call last):
1066
    NotImplementedError: Non-Unicode % tokens are not supported: %E4
1067

1068
    >>> js_unescape('%u4141%zz%u4141')
1069
    Traceback (most recent call last):
1070
    ValueError: Bad % token: %zz
1071
    """
1072
    s = packing._decode(s)
1✔
1073
    res = []
1✔
1074
    p = 0
1✔
1075
    while p < len(s):
1✔
1076
        if s[p] == '%':
1✔
1077
            if s[p+1] == "u":
1✔
1078
                # Decode Unicode token e.g. %u4142
1079
                n = s[p+2:p+6]
1✔
1080
                if len(n) < 4:
1✔
1081
                    raise ValueError('Incomplete Unicode token: %s' % s[p:])
1✔
1082
                try:
1✔
1083
                    n = int(n, 16)
1✔
1084
                except ValueError:
1✔
1085
                    raise ValueError('Failed to decode token: %s' % s[p:p+6])
1✔
1086
                res.append(packing.p16(n))
1✔
1087
                p += 6
1✔
1088
            elif s[p+1] in string.hexdigits and s[p+2] in string.hexdigits:
1✔
1089
                # Decode Non-Unicode token e.g. %E4
1090
                raise NotImplementedError('Non-Unicode %% tokens are not supported: %s' % s[p:p+3])
1✔
1091
            else:
1092
                raise ValueError('Bad %% token: %s' % s[p:p+3])
1✔
1093
        else:
1094
            res.append(packing.p16(ord(s[p])))
1✔
1095
            p += 1
1✔
1096

1097
    return b''.join(res)
1✔
1098

1099
def tty_escape(s, lnext=b'\x16', dangerous=bytes(bytearray(range(0x20)))):
1✔
1100
    r"""tty_escape(s, lnext=b'\x16', dangerous=bytes(bytearray(range(0x20)))) -> bytes
1101

1102
    Escape data for terminal output. This is useful when sending data to a
1103
    terminal that may interpret certain bytes as control characters.
1104

1105
    Check ``stty --all`` for the current settings on your terminal.
1106

1107
    Arguments:
1108
        s (bytes): The data to escape
1109
        lnext (bytes): The byte to prepend to escape the next character. Defaults to ^V.
1110
        dangerous (bytes): The bytes to escape
1111

1112
    Returns:
1113
        The escaped data.
1114

1115
    >>> tty_escape(b'abc\x04d\x18e\x16f')
1116
    b'abc\x16\x04d\x16\x18e\x16\x16f'
1117
    """
1118
    s = s.replace(lnext, lnext * 2)
1✔
1119
    for b in bytearray(dangerous):
1✔
1120
        b = bytes(bytearray([b]))
1✔
1121
        if b in lnext: continue
1✔
1122
        s = s.replace(b, lnext + b)
1✔
1123
    return s
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc