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

Gallopsled / pwntools / 8912ca5a8c3a9725c3ba6d30561607150a6faebe-PR-2205

pending completion
8912ca5a8c3a9725c3ba6d30561607150a6faebe-PR-2205

Pull #2205

github-actions

web-flow
Merge 81f463e2c into 8b4cacf8b
Pull Request #2205: Fix stable Python 2 installation from a built wheel

3878 of 6371 branches covered (60.87%)

12199 of 16604 relevant lines covered (73.47%)

0.73 hits per line

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

88.67
/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 six
1✔
11
import string
1✔
12

13
from six import BytesIO
1✔
14
from six.moves import range
1✔
15

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

27
log = getLogger(__name__)
1✔
28

29
def unhex(s):
1✔
30
    r"""unhex(s) -> str
31

32
    Hex-decodes a string.
33

34
    Example:
35

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

46
def enhex(x):
1✔
47
    """enhex(x) -> str
48

49
    Hex-encodes a string.
50

51
    Example:
52

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

61
def urlencode(s):
1✔
62
    """urlencode(s) -> str
63

64
    URL-encodes a string.
65

66
    Example:
67

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

73
def urldecode(s, ignore_invalid = False):
1✔
74
    """urldecode(s, ignore_invalid = False) -> str
75

76
    URL-decodes a string.
77

78
    Example:
79

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

107
def bits(s, endian = 'big', zero = 0, one = 1):
1✔
108
    """bits(s, endian = 'big', zero = 0, one = 1) -> list
109

110
    Converts the argument into a list of bits.
111

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

118
    Returns:
119
        A list consisting of the values specified in `zero` and `one`.
120

121
    Examples:
122

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

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

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

162
    return out
1✔
163

164
def bits_str(s, endian = 'big', zero = '0', one = '1'):
1✔
165
    """bits_str(s, endian = 'big', zero = '0', one = '1') -> str
166

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

169
    Examples:
170

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

178
def unbits(s, endian = 'big'):
1✔
179
    """unbits(s, endian = 'big') -> str
180

181
    Converts an iterable of bits into a string.
182

183
    Arguments:
184
       s: Iterable of bits
185
       endian (str):  The string "little" or "big", which specifies the bits endianness.
186

187
    Returns:
188
       A string of the decoded bits.
189

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

205
    out = b''
1✔
206
    cur = b''
1✔
207

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

216
        if len(cur) == 8:
1✔
217
            out += u(cur)
1✔
218
            cur = b''
1✔
219
    if cur:
1✔
220
        out += u(cur.ljust(8, b'0'))
1✔
221

222
    return out
1✔
223

224

225
def bitswap(s):
1✔
226
    """bitswap(s) -> str
227

228
    Reverses the bits in every byte of a given string.
229

230
    Example:
231
        >>> bitswap(b"1234")
232
        b'\\x8cL\\xcc,'
233
    """
234

235
    out = []
1✔
236

237
    for c in s:
1✔
238
        out.append(unbits(bits_str(c)[::-1]))
1✔
239

240
    return b''.join(out)
1✔
241

242
def bitswap_int(n, width):
1✔
243
    """bitswap_int(n) -> int
244

245
    Reverses the bits of a numbers and returns the result as a new number.
246

247
    Arguments:
248
        n (int): The number to swap.
249
        width (int): The width of the integer
250

251
    Examples:
252
        >>> hex(bitswap_int(0x1234, 8))
253
        '0x2c'
254
        >>> hex(bitswap_int(0x1234, 16))
255
        '0x2c48'
256
        >>> hex(bitswap_int(0x1234, 24))
257
        '0x2c4800'
258
        >>> hex(bitswap_int(0x1234, 25))
259
        '0x589000'
260
    """
261
    # Make n fit inside the width
262
    n &= (1 << width) - 1
1✔
263

264
    # Convert into bits
265
    s = bits_str(n, endian = 'little').ljust(width, '0')[:width]
1✔
266

267
    # Convert back
268
    return int(s, 2)
1✔
269

270

271
def b64e(s):
1✔
272
    """b64e(s) -> str
273

274
    Base64 encodes a string
275

276
    Example:
277

278
       >>> b64e(b"test")
279
       'dGVzdA=='
280
       """
281
    x = base64.b64encode(s)
1✔
282
    if not hasattr(x, 'encode'):
1!
283
        x = x.decode('ascii')
×
284
    return x
1✔
285

286
def b64d(s):
1✔
287
    """b64d(s) -> str
288

289
    Base64 decodes a string
290

291
    Example:
292

293
       >>> b64d('dGVzdA==')
294
       b'test'
295
    """
296
    return base64.b64decode(s)
1✔
297

298
# misc binary functions
299
def xor(*args, **kwargs):
1✔
300
    """xor(*args, cut = 'max') -> str
301

302
    Flattens its arguments using :func:`pwnlib.util.packing.flat` and
303
    then xors them together. If the end of a string is reached, it wraps
304
    around in the string.
305

306
    Arguments:
307
       args: The arguments to be xor'ed together.
308
       cut: How long a string should be returned.
309
            Can be either 'min'/'max'/'left'/'right' or a number.
310

311
    Returns:
312
       The string of the arguments xor'ed together.
313

314
    Example:
315
       >>> xor(b'lol', b'hello', 42)
316
       b'. ***'
317
    """
318

319
    cut = kwargs.pop('cut', 'max')
1✔
320

321
    if kwargs != {}:
1!
322
        raise TypeError("xor() got an unexpected keyword argument '%s'" % kwargs.pop()[0])
×
323

324
    if len(args) == 0:
1!
325
        raise ValueError("Must have something to xor")
×
326

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

330
    if strs == []:
1!
331
        return b''
×
332

333
    if isinstance(cut, six.integer_types):
1!
334
        cut = cut
×
335
    elif cut == 'left':
1!
336
        cut = len(strs[0])
×
337
    elif cut == 'right':
1!
338
        cut = len(strs[-1])
×
339
    elif cut == 'min':
1!
340
        cut = min(len(s) for s in strs)
×
341
    elif cut == 'max':
1!
342
        cut = max(len(s) for s in strs)
1✔
343
    else:
344
        raise ValueError("Not a valid argument for 'cut'")
×
345

346
    def get(n):
1✔
347
        rv = 0
1✔
348
        for s in strs: rv ^= s[n%len(s)]
1✔
349
        return packing._p8lu(rv)
1✔
350

351
    return b''.join(map(get, range(cut)))
1✔
352

353
def xor_pair(data, avoid = b'\x00\n'):
1✔
354
    """xor_pair(data, avoid = '\\x00\\n') -> None or (str, str)
355

356
    Finds two strings that will xor into a given string, while only
357
    using a given alphabet.
358

359
    Arguments:
360
        data (str): The desired string.
361
        avoid: The list of disallowed characters. Defaults to nulls and newlines.
362

363
    Returns:
364
        Two strings which will xor to the given string. If no such two strings exist, then None is returned.
365

366
    Example:
367

368
        >>> xor_pair(b"test")
369
        (b'\\x01\\x01\\x01\\x01', b'udru')
370
    """
371

372
    if isinstance(data, six.integer_types):
1!
373
        data = packing.pack(data)
×
374

375
    if not isinstance(avoid, (bytes, bytearray)):
1!
376
        avoid = avoid.encode('utf-8')
×
377

378
    avoid = bytearray(avoid)
1✔
379
    alphabet = list(packing._p8lu(n) for n in range(256) if n not in avoid)
1✔
380

381
    res1 = b''
1✔
382
    res2 = b''
1✔
383

384
    for c1 in bytearray(data):
1✔
385
        if context.randomize:
1!
386
            random.shuffle(alphabet)
×
387
        for c2 in alphabet:
1!
388
            c3 = packing._p8lu(c1 ^ packing.u8(c2))
1✔
389
            if c3 in alphabet:
1✔
390
                res1 += c2
1✔
391
                res2 += c3
1✔
392
                break
1✔
393
        else:
394
            return None
×
395

396
    return res1, res2
1✔
397

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

401
    Finds a ``size``-width value that can be XORed with a string
402
    to produce ``data``, while neither the XOR value or XOR string
403
    contain any bytes in ``avoid``.
404

405
    Arguments:
406
        data (str): The desired string.
407
        avoid: The list of disallowed characters. Defaults to nulls and newlines.
408
        size (int): Size of the desired output value, default is word size.
409

410
    Returns:
411
        A tuple containing two strings; the XOR key and the XOR string.
412
        If no such pair exists, None is returned.
413

414
    Example:
415

416
        >>> xor_key(b"Hello, world")
417
        (b'\x01\x01\x01\x01', b'Idmmn-!vnsme')
418
    """
419
    size = size or context.bytes
1✔
420

421
    if len(data) % size:
1!
422
        log.error("Data must be padded to size for xor_key")
×
423

424
    words    = lists.group(size, data)
1✔
425
    columns  = [b''] * size
1✔
426
    for word in words:
1✔
427
        for i,byte in enumerate(bytearray(word)):
1✔
428
            columns[i] += bytearray((byte,))
1✔
429

430
    avoid = bytearray(avoid)
1✔
431
    alphabet = bytearray(n for n in range(256) if n not in avoid)
1✔
432

433
    result = b''
1✔
434

435
    for column in columns:
1✔
436
        if context.randomize:
1!
437
            random.shuffle(alphabet)
×
438
        for c2 in alphabet:
1!
439
            if all(c^c2 in alphabet for c in column):
1✔
440
                result += packing._p8lu(c2)
1✔
441
                break
1✔
442
        else:
443
            return None
×
444

445
    return result, xor(data, result)
1✔
446

447
def randoms(count, alphabet = string.ascii_lowercase):
1✔
448
    """randoms(count, alphabet = string.ascii_lowercase) -> str
449

450
    Returns a random string of a given length using only the specified alphabet.
451

452
    Arguments:
453
        count (int): The length of the desired string.
454
        alphabet: The alphabet of allowed characters. Defaults to all lowercase characters.
455

456
    Returns:
457
        A random string.
458

459
    Example:
460

461
        >>> randoms(10) #doctest: +SKIP
462
        'evafjilupm'
463
    """
464

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

467

468
def rol(n, k, word_size = None):
1✔
469
    """Returns a rotation by `k` of `n`.
470

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

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

475
    Arguments:
476
        n: The value to rotate.
477
        k(int): The rotation amount. Can be a positive or negative number.
478
        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` .
479

480
    Example:
481

482
        >>> rol('abcdefg', 2)
483
        'cdefgab'
484
        >>> rol('abcdefg', -2)
485
        'fgabcde'
486
        >>> hex(rol(0x86, 3, 8))
487
        '0x34'
488
        >>> hex(rol(0x86, -3, 8))
489
        '0xd0'
490
    """
491

492
    word_size = word_size or context.word_size
1✔
493

494
    if not isinstance(word_size, six.integer_types) or word_size <= 0:
1!
495
        raise ValueError("rol(): 'word_size' must be a strictly positive integer")
×
496

497
    if not isinstance(k, six.integer_types):
1!
498
        raise ValueError("rol(): 'k' must be an integer")
×
499

500
    if isinstance(n, (bytes, six.text_type, list, tuple)):
1✔
501
        return n[k % len(n):] + n[:k % len(n)]
1✔
502
    elif isinstance(n, six.integer_types):
1!
503
        k = k % word_size
1✔
504
        n = (n << k) | (n >> (word_size - k))
1✔
505
        n &= (1 << word_size) - 1
1✔
506

507
        return n
1✔
508
    else:
509
        raise ValueError("rol(): 'n' must be an integer, string, list or tuple")
×
510

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

514
    return rol(n, -k, word_size)
×
515

516
def naf(n):
1✔
517
    """naf(int) -> int generator
518

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

523
    [1] https://en.wikipedia.org/wiki/Non-adjacent_form
524

525
    Example:
526

527
      >>> n = 45
528
      >>> m = 0
529
      >>> x = 1
530
      >>> for z in naf(n):
531
      ...     m += x * z
532
      ...     x *= 2
533
      >>> n == m
534
      True
535

536
    """
537
    while n:
1✔
538
        z = 2 - n % 4 if n & 1 else 0
1✔
539
        n = (n - z) // 2
1✔
540
        yield z
1✔
541

542
def isprint(c):
1✔
543
    """isprint(c) -> bool
544

545
    Return True if a character is printable"""
546
    if isinstance(c, six.text_type):
1!
547
        c = ord(c)
×
548
    t = bytearray(string.ascii_letters + string.digits + string.punctuation + ' ', 'ascii')
1✔
549
    return c in t
1✔
550

551

552
def hexii(s, width = 16, skip = True):
1✔
553
    """hexii(s, width = 16, skip = True) -> str
554

555
    Return a HEXII-dump of a string.
556

557
    Arguments:
558
        s(str): The string to dump
559
        width(int): The number of characters per line
560
        skip(bool): Should repeated lines be replaced by a "*"
561

562
    Returns:
563
        A HEXII-dump in the form of a string.
564
    """
565

566
    return hexdump(s, width, skip, True)
1✔
567

568
def _hexiichar(c):
1✔
569
    HEXII = bytearray((string.punctuation + string.digits + string.ascii_letters).encode())
1✔
570
    if c in HEXII:
1✔
571
        return ".%c " % c
1✔
572
    elif c == 0:
1✔
573
        return "   "
1✔
574
    elif c == 0xff:
1✔
575
        return "## "
1✔
576
    else:
577
        return "%02x " % c
1✔
578

579
default_style = {
1✔
580
    'marker':       text.gray if text.has_gray else text.blue,
581
    'nonprintable': text.gray if text.has_gray else text.blue,
582
    '00':           text.red,
583
    '0a':           text.red,
584
    'ff':           text.green,
585
}
586

587
cyclic_pregen = b''
1✔
588
de_bruijn_gen = de_bruijn()
1✔
589

590
def sequential_lines(a,b):
1✔
591
    return (a+b) in cyclic_pregen
1✔
592

593
def update_cyclic_pregenerated(size):
1✔
594
    global cyclic_pregen
595
    while size > len(cyclic_pregen):
1✔
596
        cyclic_pregen += packing._p8lu(next(de_bruijn_gen))
1✔
597

598
def hexdump_iter(fd, width=16, skip=True, hexii=False, begin=0, style=None,
1✔
599
                 highlight=None, cyclic=False, groupsize=4, total=True):
600
    r"""hexdump_iter(s, width = 16, skip = True, hexii = False, begin = 0, style = None,
601
                    highlight = None, cyclic = False, groupsize=4, total = True) -> str generator
602

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

606
    Arguments:
607
        fd(file): File object to dump.  Use :meth:`StringIO.StringIO` or :meth:`hexdump` to dump a string.
608
        width(int): The number of characters per line
609
        groupsize(int): The number of characters per group
610
        skip(bool): Set to True, if repeated lines should be replaced by a "*"
611
        hexii(bool): Set to True, if a hexii-dump should be returned instead of a hexdump.
612
        begin(int):  Offset of the first byte to print in the left column
613
        style(dict): Color scheme to use.
614
        highlight(iterable): Byte values to highlight.
615
        cyclic(bool): Attempt to skip consecutive, unmodified cyclic lines
616
        total(bool): Set to True, if total bytes should be printed
617

618
    Returns:
619
        A generator producing the hexdump-dump one line at a time.
620

621
    Example:
622

623
        >>> tmp = tempfile.NamedTemporaryFile()
624
        >>> _ = tmp.write(b'XXXXHELLO, WORLD')
625
        >>> tmp.flush()
626
        >>> _ = tmp.seek(4)
627
        >>> print('\n'.join(hexdump_iter(tmp)))
628
        00000000  48 45 4c 4c  4f 2c 20 57  4f 52 4c 44               │HELL│O, W│ORLD│
629
        0000000c
630

631
        >>> t = tube()
632
        >>> t.unrecv(b'I know kung fu')
633
        >>> print('\n'.join(hexdump_iter(t)))
634
        00000000  49 20 6b 6e  6f 77 20 6b  75 6e 67 20  66 75        │I kn│ow k│ung │fu│
635
        0000000e
636
    """
637
    style     = style or {}
1✔
638
    highlight = highlight or []
1✔
639

640
    if groupsize < 1:
1✔
641
        groupsize = width
1✔
642

643
    for b in highlight:
1✔
644
        if isinstance(b, str):
1!
645
            b = ord(b)
×
646
        style['%02x' % b] = text.white_on_red
1✔
647
    _style = style
1✔
648
    style = default_style.copy()
1✔
649
    style.update(_style)
1✔
650

651
    skipping    = False
1✔
652
    lines       = []
1✔
653
    last_unique = ''
1✔
654
    byte_width  = len('00 ')
1✔
655
    spacer      = ' '
1✔
656
    marker      = (style.get('marker') or (lambda s:s))('│')
1✔
657

658
    if not hexii:
1✔
659
        def style_byte(by):
1✔
660
            hbyte = '%02x' % by
1✔
661
            b = packing._p8lu(by)
1✔
662
            abyte = chr(by) if isprint(b) else '·'
1✔
663
            if hbyte in style:
1✔
664
                st = style[hbyte]
1✔
665
            elif isprint(b):
1✔
666
                st = style.get('printable')
1✔
667
            else:
668
                st = style.get('nonprintable')
1✔
669
            if st:
1✔
670
                hbyte = st(hbyte)
1✔
671
                abyte = st(abyte)
1✔
672
            return hbyte, abyte
1✔
673
        cache = [style_byte(b) for b in range(256)]
1✔
674

675
    numb = 0
1✔
676
    while True:
1✔
677
        offset = begin + numb
1✔
678

679
        # If a tube is passed in as fd, it will raise EOFError when it runs
680
        # out of data, unlike a file or StringIO object, which return an empty
681
        # string.
682
        try:
1✔
683
            chunk = fd.read(width)
1✔
684
        except EOFError:
1✔
685
            chunk = b''
1✔
686

687
        # We have run out of data, exit the loop
688
        if chunk == b'':
1✔
689
            break
1✔
690

691
        # Advance the cursor by the number of bytes we actually read
692
        numb += len(chunk)
1✔
693

694
        # Update the cyclic pattern in case
695
        if cyclic:
1✔
696
            update_cyclic_pregenerated(numb)
1✔
697

698
        # If this chunk is the same as the last unique chunk,
699
        # use a '*' instead.
700
        if skip and last_unique:
1✔
701
            same_as_last_line = (last_unique == chunk)
1✔
702
            lines_are_sequential = (cyclic and sequential_lines(last_unique, chunk))
1✔
703
            last_unique = chunk
1✔
704

705
            if same_as_last_line or lines_are_sequential:
1✔
706

707
                # If we have not already printed a "*", do so
708
                if not skipping:
1!
709
                    yield '*'
1✔
710
                    skipping = True
1✔
711

712
                # Move on to the next chunk
713
                continue
×
714

715
        # Chunk is unique, no longer skipping
716
        skipping = False
1✔
717
        last_unique = chunk
1✔
718

719
        # Generate contents for line
720
        hexbytes = ''
1✔
721
        printable = ''
1✔
722
        color_chars = 0
1✔
723
        abyte = abyte_previous = ''
1✔
724
        for i, b in enumerate(bytearray(chunk)):
1✔
725
            if not hexii:
1✔
726
                abyte_previous = abyte
1✔
727
                hbyte, abyte = cache[b]
1✔
728
                color_chars += len(hbyte) - 2
1✔
729
            else:
730
                hbyte, abyte = _hexiichar(b), ''
1✔
731

732
            if (i + 1) % groupsize == 0 and i < width - 1:
1✔
733
                hbyte += spacer
1✔
734
                abyte_previous += abyte
1✔
735
                abyte = marker
1✔
736

737
            hexbytes += hbyte + ' '
1✔
738
            printable += abyte_previous
1✔
739

740
        if abyte != marker:
1✔
741
            printable += abyte
1✔
742

743
        dividers_per_line = (width // groupsize)
1✔
744
        if width % groupsize == 0:
1✔
745
            dividers_per_line -= 1
1✔
746

747
        if hexii:
1✔
748
            line_fmt = '%%(offset)08x  %%(hexbytes)-%is│' % (width*byte_width)
1✔
749
        else:
750
            line_fmt = '%%(offset)08x  %%(hexbytes)-%is │%%(printable)s│' % (
1✔
751
                 (width * byte_width)
752
                + color_chars
753
                + dividers_per_line )
754

755
        line = line_fmt % {'offset': offset, 'hexbytes': hexbytes, 'printable': printable}
1✔
756
        yield line
1✔
757

758
    if total:
1✔
759
        line = "%08x" % (begin + numb)
1✔
760
        yield line
1✔
761

762
def hexdump(s, width=16, skip=True, hexii=False, begin=0, style=None,
1✔
763
            highlight=None, cyclic=False, groupsize=4, total=True):
764
    r"""hexdump(s, width = 16, skip = True, hexii = False, begin = 0, style = None,
765
                highlight = None, cyclic = False, groupsize=4, total = True) -> str
766

767
    Return a hexdump-dump of a string.
768

769
    Arguments:
770
        s(bytes): The data to hexdump.
771
        width(int): The number of characters per line
772
        groupsize(int): The number of characters per group
773
        skip(bool): Set to True, if repeated lines should be replaced by a "*"
774
        hexii(bool): Set to True, if a hexii-dump should be returned instead of a hexdump.
775
        begin(int):  Offset of the first byte to print in the left column
776
        style(dict): Color scheme to use.
777
        highlight(iterable): Byte values to highlight.
778
        cyclic(bool): Attempt to skip consecutive, unmodified cyclic lines
779
        total(bool): Set to True, if total bytes should be printed
780

781
    Returns:
782
        A hexdump-dump in the form of a string.
783

784
    Examples:
785

786
        >>> print(hexdump(b"abc"))
787
        00000000  61 62 63                                            │abc│
788
        00000003
789

790
        >>> print(hexdump(b'A'*32))
791
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
792
        *
793
        00000020
794

795
        >>> print(hexdump(b'A'*32, width=8))
796
        00000000  41 41 41 41  41 41 41 41  │AAAA│AAAA│
797
        *
798
        00000020
799

800
        >>> print(hexdump(cyclic(32), width=8, begin=0xdead0000, hexii=True))
801
        dead0000  .a  .a  .a  .a   .b  .a  .a  .a  │
802
        dead0008  .c  .a  .a  .a   .d  .a  .a  .a  │
803
        dead0010  .e  .a  .a  .a   .f  .a  .a  .a  │
804
        dead0018  .g  .a  .a  .a   .h  .a  .a  .a  │
805
        dead0020
806

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

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

845
        >>> print(hexdump(b'X' * 64))
846
        00000000  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
847
        *
848
        00000040
849

850
        >>> print(hexdump(b'X' * 64, skip=False))
851
        00000000  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
852
        00000010  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
853
        00000020  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
854
        00000030  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
855
        00000040
856

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

874
        >>> print(hexdump(fit({0x10: b'X'*0x20, 0x50-1: b'\xff'*20}, length=0xc0) + b'\x00'*32, cyclic=1))
875
        00000000  61 61 61 61  62 61 61 61  63 61 61 61  64 61 61 61  │aaaa│baaa│caaa│daaa│
876
        00000010  58 58 58 58  58 58 58 58  58 58 58 58  58 58 58 58  │XXXX│XXXX│XXXX│XXXX│
877
        *
878
        00000030  6d 61 61 61  6e 61 61 61  6f 61 61 61  70 61 61 61  │maaa│naaa│oaaa│paaa│
879
        00000040  71 61 61 61  72 61 61 61  73 61 61 61  74 61 61 ff  │qaaa│raaa│saaa│taa·│
880
        00000050  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  │····│····│····│····│
881
        00000060  ff ff ff 61  7a 61 61 62  62 61 61 62  63 61 61 62  │···a│zaab│baab│caab│
882
        00000070  64 61 61 62  65 61 61 62  66 61 61 62  67 61 61 62  │daab│eaab│faab│gaab│
883
        *
884
        000000c0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
885
        *
886
        000000e0
887

888
        >>> print(hexdump(fit({0x10: b'X'*0x20, 0x50-1: b'\xff'*20}, length=0xc0) + b'\x00'*32, cyclic=1, hexii=1))
889
        00000000  .a  .a  .a  .a   .b  .a  .a  .a   .c  .a  .a  .a   .d  .a  .a  .a  │
890
        00000010  .X  .X  .X  .X   .X  .X  .X  .X   .X  .X  .X  .X   .X  .X  .X  .X  │
891
        *
892
        00000030  .m  .a  .a  .a   .n  .a  .a  .a   .o  .a  .a  .a   .p  .a  .a  .a  │
893
        00000040  .q  .a  .a  .a   .r  .a  .a  .a   .s  .a  .a  .a   .t  .a  .a  ##  │
894
        00000050  ##  ##  ##  ##   ##  ##  ##  ##   ##  ##  ##  ##   ##  ##  ##  ##  │
895
        00000060  ##  ##  ##  .a   .z  .a  .a  .b   .b  .a  .a  .b   .c  .a  .a  .b  │
896
        00000070  .d  .a  .a  .b   .e  .a  .a  .b   .f  .a  .a  .b   .g  .a  .a  .b  │
897
        *
898
        000000c0                                                                     │
899
        *
900
        000000e0
901

902
        >>> print(hexdump(b'A'*16, width=9))
903
        00000000  41 41 41 41  41 41 41 41  41  │AAAA│AAAA│A│
904
        00000009  41 41 41 41  41 41 41         │AAAA│AAA│
905
        00000010
906
        >>> print(hexdump(b'A'*16, width=10))
907
        00000000  41 41 41 41  41 41 41 41  41 41  │AAAA│AAAA│AA│
908
        0000000a  41 41 41 41  41 41               │AAAA│AA│
909
        00000010
910
        >>> print(hexdump(b'A'*16, width=11))
911
        00000000  41 41 41 41  41 41 41 41  41 41 41  │AAAA│AAAA│AAA│
912
        0000000b  41 41 41 41  41                     │AAAA│A│
913
        00000010
914
        >>> print(hexdump(b'A'*16, width=12))
915
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│
916
        0000000c  41 41 41 41                            │AAAA│
917
        00000010
918
        >>> print(hexdump(b'A'*16, width=13))
919
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41  │AAAA│AAAA│AAAA│A│
920
        0000000d  41 41 41                                   │AAA│
921
        00000010
922
        >>> print(hexdump(b'A'*16, width=14))
923
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41  │AAAA│AAAA│AAAA│AA│
924
        0000000e  41 41                                         │AA│
925
        00000010
926
        >>> print(hexdump(b'A'*16, width=15))
927
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41  │AAAA│AAAA│AAAA│AAA│
928
        0000000f  41                                               │A│
929
        00000010
930

931
        >>> print(hexdump(b'A'*24, width=16, groupsize=8))
932
        00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  │AAAAAAAA│AAAAAAAA│
933
        00000010  41 41 41 41 41 41 41 41                           │AAAAAAAA│
934
        00000018
935
        >>> print(hexdump(b'A'*24, width=16, groupsize=-1))
936
        00000000  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  │AAAAAAAAAAAAAAAA│
937
        00000010  41 41 41 41 41 41 41 41                          │AAAAAAAA│
938
        00000018
939

940
        >>> print(hexdump(b'A'*24, width=16, total=False))
941
        00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
942
        00000010  41 41 41 41  41 41 41 41                            │AAAA│AAAA│
943
        >>> print(hexdump(b'A'*24, width=16, groupsize=8, total=False))
944
        00000000  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  │AAAAAAAA│AAAAAAAA│
945
        00000010  41 41 41 41 41 41 41 41                           │AAAAAAAA│
946
    """
947
    s = packing.flat(s, stacklevel=1)
1✔
948
    return '\n'.join(hexdump_iter(BytesIO(s),
1✔
949
                                  width,
950
                                  skip,
951
                                  hexii,
952
                                  begin,
953
                                  style,
954
                                  highlight,
955
                                  cyclic,
956
                                  groupsize,
957
                                  total))
958

959
def negate(value, width = None):
1✔
960
    """
961
    Returns the two's complement of 'value'.
962
    """
963
    if width is None:
1!
964
        width = context.bits
1✔
965
    mask = ((1<<width)-1)
1✔
966
    return ((mask+1) - value) & mask
1✔
967

968
def bnot(value, width=None):
1✔
969
    """
970
    Returns the binary inverse of 'value'.
971
    """
972
    if width is None:
1!
973
        width = context.bits
1✔
974
    mask = ((1<<width)-1)
1✔
975
    return mask ^ value
1✔
976

977
@LocalNoarchContext
1✔
978
def js_escape(data, padding=context.cyclic_alphabet[0:1], **kwargs):
1✔
979
    r"""js_escape(data, padding=context.cyclic_alphabet[0:1], endian = None, **kwargs) -> str
980

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

983
    Arguments:
984
        data (bytes): Bytes to pack
985
        padding (bytes): A single byte to use as padding if data is of uneven length
986
        endian (str): Endianness with which to pack the string ("little"/"big")
987

988
    Returns:
989
        A string representation of the packed data
990

991
    >>> js_escape(b'\xde\xad\xbe\xef')
992
    '%uadde%uefbe'
993

994
    >>> js_escape(b'\xde\xad\xbe\xef', endian='big')
995
    '%udead%ubeef'
996

997
    >>> js_escape(b'\xde\xad\xbe')
998
    '%uadde%u61be'
999

1000
    >>> js_escape(b'aaaa')
1001
    '%u6161%u6161'
1002
    """
1003
    data = packing._need_bytes(data)
1✔
1004

1005
    padding = packing._need_bytes(padding)
1✔
1006
    if len(padding) != 1:
1!
1007
        raise ValueError("Padding must be a single byte")
×
1008

1009
    if len(data) % 2:
1✔
1010
        data += padding[0:1]
1✔
1011

1012
    data = bytearray(data)
1✔
1013

1014
    if context.endian == 'little':
1✔
1015
        return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for b, a in iters.group(2, data))
1✔
1016
    else:
1017
        return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for a, b in iters.group(2, data))
1✔
1018

1019
@LocalNoarchContext
1✔
1020
def js_unescape(s, **kwargs):
1021
    r"""js_unescape(s, endian = None, **kwargs) -> bytes
1022

1023
    Unpack an escaped Unicode string from JavaScript's `escape()` function
1024

1025
    Arguments:
1026
        s (str): Escaped string to unpack
1027
        endian (str): Endianness with which to unpack the string ("little"/"big")
1028

1029
    Returns:
1030
        A bytes representation of the unpacked data
1031

1032
    >>> js_unescape('%uadde%uefbe')
1033
    b'\xde\xad\xbe\xef'
1034

1035
    >>> js_unescape('%udead%ubeef', endian='big')
1036
    b'\xde\xad\xbe\xef'
1037

1038
    >>> js_unescape('abc%u4141123')
1039
    b'a\x00b\x00c\x00AA1\x002\x003\x00'
1040

1041
    >>> data = b'abcdABCD1234!@#$\x00\x01\x02\x03\x80\x81\x82\x83'
1042
    >>> js_unescape(js_escape(data)) == data
1043
    True
1044

1045
    >>> js_unescape('%u4141%u42')
1046
    Traceback (most recent call last):
1047
    ValueError: Incomplete Unicode token: %u42
1048

1049
    >>> js_unescape('%u4141%uwoot%4141')
1050
    Traceback (most recent call last):
1051
    ValueError: Failed to decode token: %uwoot
1052

1053
    >>> js_unescape('%u4141%E4%F6%FC%u4141')
1054
    Traceback (most recent call last):
1055
    NotImplementedError: Non-Unicode % tokens are not supported: %E4
1056

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

1086
    return b''.join(res)
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

© 2025 Coveralls, Inc