polyfile-weave 0.5.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of polyfile-weave might be problematic. Click here for more details.
- polyfile/__init__.py +15 -0
- polyfile/__main__.py +394 -0
- polyfile/arithmetic.py +27 -0
- polyfile/ast.py +114 -0
- polyfile/debugger.py +1039 -0
- polyfile/expressions.py +346 -0
- polyfile/fileutils.py +343 -0
- polyfile/html.py +135 -0
- polyfile/http/__init__.py +1 -0
- polyfile/http/defacto.py +37 -0
- polyfile/http/deprecated.py +51 -0
- polyfile/http/experimental.py +67 -0
- polyfile/http/http_11.py +548 -0
- polyfile/http/matcher.py +37 -0
- polyfile/http/structured_headers.py +48 -0
- polyfile/iterators.py +72 -0
- polyfile/jpeg.py +24 -0
- polyfile/kaitai/__init__.py +0 -0
- polyfile/kaitai/compiler.py +156 -0
- polyfile/kaitai/parser.py +312 -0
- polyfile/kaitai/parsers/__init__.py +0 -0
- polyfile/kaitai/parsers/aix_utmp.py +116 -0
- polyfile/kaitai/parsers/allegro_dat.py +367 -0
- polyfile/kaitai/parsers/andes_firmware.py +64 -0
- polyfile/kaitai/parsers/android_bootldr_asus.py +105 -0
- polyfile/kaitai/parsers/android_bootldr_huawei.py +181 -0
- polyfile/kaitai/parsers/android_bootldr_qcom.py +217 -0
- polyfile/kaitai/parsers/android_dto.py +138 -0
- polyfile/kaitai/parsers/android_img.py +319 -0
- polyfile/kaitai/parsers/android_nanoapp_header.py +83 -0
- polyfile/kaitai/parsers/android_opengl_shaders_cache.py +151 -0
- polyfile/kaitai/parsers/android_sparse.py +237 -0
- polyfile/kaitai/parsers/android_super.py +401 -0
- polyfile/kaitai/parsers/apm_partition_table.py +196 -0
- polyfile/kaitai/parsers/apple_single_double.py +180 -0
- polyfile/kaitai/parsers/asn1_der.py +235 -0
- polyfile/kaitai/parsers/au.py +138 -0
- polyfile/kaitai/parsers/avantes_roh60.py +112 -0
- polyfile/kaitai/parsers/avi.py +296 -0
- polyfile/kaitai/parsers/bcd.py +111 -0
- polyfile/kaitai/parsers/bitcoin_transaction.py +210 -0
- polyfile/kaitai/parsers/blender_blend.py +334 -0
- polyfile/kaitai/parsers/bmp.py +780 -0
- polyfile/kaitai/parsers/bson.py +411 -0
- polyfile/kaitai/parsers/btrfs_stream.py +318 -0
- polyfile/kaitai/parsers/bytes_with_io.py +27 -0
- polyfile/kaitai/parsers/chrome_pak.py +194 -0
- polyfile/kaitai/parsers/code_6502.py +456 -0
- polyfile/kaitai/parsers/compressed_resource.py +217 -0
- polyfile/kaitai/parsers/cpio_old_le.py +154 -0
- polyfile/kaitai/parsers/cramfs.py +344 -0
- polyfile/kaitai/parsers/creative_voice_file.py +342 -0
- polyfile/kaitai/parsers/dbf.py +274 -0
- polyfile/kaitai/parsers/dcmp_0.py +664 -0
- polyfile/kaitai/parsers/dcmp_1.py +422 -0
- polyfile/kaitai/parsers/dcmp_2.py +312 -0
- polyfile/kaitai/parsers/dcmp_variable_length_integer.py +66 -0
- polyfile/kaitai/parsers/dex.py +1086 -0
- polyfile/kaitai/parsers/dicom.py +4370 -0
- polyfile/kaitai/parsers/dime_message.py +201 -0
- polyfile/kaitai/parsers/dns_packet.py +569 -0
- polyfile/kaitai/parsers/doom_wad.py +654 -0
- polyfile/kaitai/parsers/dos_datetime.py +191 -0
- polyfile/kaitai/parsers/dos_mz.py +172 -0
- polyfile/kaitai/parsers/ds_store.py +513 -0
- polyfile/kaitai/parsers/dtb.py +310 -0
- polyfile/kaitai/parsers/dune_2_pak.py +126 -0
- polyfile/kaitai/parsers/edid.py +472 -0
- polyfile/kaitai/parsers/efivar_signature_list.py +331 -0
- polyfile/kaitai/parsers/elf.py +2482 -0
- polyfile/kaitai/parsers/ethernet_frame.py +114 -0
- polyfile/kaitai/parsers/exif.py +723 -0
- polyfile/kaitai/parsers/ext2.py +537 -0
- polyfile/kaitai/parsers/fallout2_dat.py +187 -0
- polyfile/kaitai/parsers/fallout_dat.py +156 -0
- polyfile/kaitai/parsers/fasttracker_xm_module.py +558 -0
- polyfile/kaitai/parsers/ftl_dat.py +90 -0
- polyfile/kaitai/parsers/genmidi_op2.py +161 -0
- polyfile/kaitai/parsers/gettext_mo.py +541 -0
- polyfile/kaitai/parsers/gif.py +492 -0
- polyfile/kaitai/parsers/gimp_brush.py +244 -0
- polyfile/kaitai/parsers/glibc_utmp.py +114 -0
- polyfile/kaitai/parsers/gltf_binary.py +132 -0
- polyfile/kaitai/parsers/google_protobuf.py +151 -0
- polyfile/kaitai/parsers/gpt_partition_table.py +175 -0
- polyfile/kaitai/parsers/gran_turismo_vol.py +140 -0
- polyfile/kaitai/parsers/grub2_font.py +337 -0
- polyfile/kaitai/parsers/gzip.py +232 -0
- polyfile/kaitai/parsers/hashcat_restore.py +60 -0
- polyfile/kaitai/parsers/hccap.py +111 -0
- polyfile/kaitai/parsers/hccapx.py +103 -0
- polyfile/kaitai/parsers/heaps_pak.py +177 -0
- polyfile/kaitai/parsers/heroes_of_might_and_magic_agg.py +116 -0
- polyfile/kaitai/parsers/heroes_of_might_and_magic_bmp.py +34 -0
- polyfile/kaitai/parsers/icmp_packet.py +136 -0
- polyfile/kaitai/parsers/ico.py +129 -0
- polyfile/kaitai/parsers/id3v1_1.py +220 -0
- polyfile/kaitai/parsers/id3v2_3.py +324 -0
- polyfile/kaitai/parsers/id3v2_4.py +423 -0
- polyfile/kaitai/parsers/ines.py +282 -0
- polyfile/kaitai/parsers/ipv4_packet.py +158 -0
- polyfile/kaitai/parsers/ipv6_packet.py +55 -0
- polyfile/kaitai/parsers/iso9660.py +544 -0
- polyfile/kaitai/parsers/java_class.py +1113 -0
- polyfile/kaitai/parsers/jpeg.py +361 -0
- polyfile/kaitai/parsers/luks.py +149 -0
- polyfile/kaitai/parsers/lzh.py +165 -0
- polyfile/kaitai/parsers/mac_os_resource_snd.py +493 -0
- polyfile/kaitai/parsers/mach_o.py +3033 -0
- polyfile/kaitai/parsers/mach_o_fat.py +92 -0
- polyfile/kaitai/parsers/magicavoxel_vox.py +391 -0
- polyfile/kaitai/parsers/manifest.json +1 -0
- polyfile/kaitai/parsers/mbr_partition_table.py +119 -0
- polyfile/kaitai/parsers/mcap.py +1015 -0
- polyfile/kaitai/parsers/microsoft_cfb.py +293 -0
- polyfile/kaitai/parsers/microsoft_network_monitor_v2.py +309 -0
- polyfile/kaitai/parsers/microsoft_pe.py +765 -0
- polyfile/kaitai/parsers/mifare_classic.py +706 -0
- polyfile/kaitai/parsers/minecraft_nbt.py +449 -0
- polyfile/kaitai/parsers/monomakh_sapr_chg.py +69 -0
- polyfile/kaitai/parsers/mozilla_mar.py +239 -0
- polyfile/kaitai/parsers/mp4.py +333 -0
- polyfile/kaitai/parsers/msgpack.py +467 -0
- polyfile/kaitai/parsers/nitf.py +1189 -0
- polyfile/kaitai/parsers/nt_mdt_pal.py +155 -0
- polyfile/kaitai/parsers/ogg.py +118 -0
- polyfile/kaitai/parsers/openpgp_message.py +993 -0
- polyfile/kaitai/parsers/packet_ppi.py +515 -0
- polyfile/kaitai/parsers/pcap.py +344 -0
- polyfile/kaitai/parsers/pcf_font.py +506 -0
- polyfile/kaitai/parsers/pcx.py +195 -0
- polyfile/kaitai/parsers/pcx_dcx.py +79 -0
- polyfile/kaitai/parsers/phar_without_stub.py +399 -0
- polyfile/kaitai/parsers/php_serialized_value.py +505 -0
- polyfile/kaitai/parsers/png.py +721 -0
- polyfile/kaitai/parsers/protocol_body.py +260 -0
- polyfile/kaitai/parsers/psx_tim.py +104 -0
- polyfile/kaitai/parsers/python_pickle.py +718 -0
- polyfile/kaitai/parsers/python_pyc_27.py +510 -0
- polyfile/kaitai/parsers/quake_mdl.py +441 -0
- polyfile/kaitai/parsers/quake_pak.py +112 -0
- polyfile/kaitai/parsers/quicktime_mov.py +634 -0
- polyfile/kaitai/parsers/rar.py +265 -0
- polyfile/kaitai/parsers/regf.py +569 -0
- polyfile/kaitai/parsers/renderware_binary_stream.py +877 -0
- polyfile/kaitai/parsers/resource_fork.py +611 -0
- polyfile/kaitai/parsers/respack.py +57 -0
- polyfile/kaitai/parsers/riff.py +409 -0
- polyfile/kaitai/parsers/rpm.py +964 -0
- polyfile/kaitai/parsers/rtcp_payload.py +579 -0
- polyfile/kaitai/parsers/rtp_packet.py +150 -0
- polyfile/kaitai/parsers/rtpdump.py +115 -0
- polyfile/kaitai/parsers/ruby_marshal.py +423 -0
- polyfile/kaitai/parsers/s3m.py +493 -0
- polyfile/kaitai/parsers/saints_row_2_vpp_pc.py +254 -0
- polyfile/kaitai/parsers/shapefile_index.py +174 -0
- polyfile/kaitai/parsers/shapefile_main.py +893 -0
- polyfile/kaitai/parsers/some_ip.py +209 -0
- polyfile/kaitai/parsers/some_ip_container.py +37 -0
- polyfile/kaitai/parsers/some_ip_sd.py +86 -0
- polyfile/kaitai/parsers/some_ip_sd_entries.py +160 -0
- polyfile/kaitai/parsers/some_ip_sd_options.py +374 -0
- polyfile/kaitai/parsers/specpr.py +404 -0
- polyfile/kaitai/parsers/sqlite3.py +472 -0
- polyfile/kaitai/parsers/ssh_public_key.py +252 -0
- polyfile/kaitai/parsers/standard_midi_file.py +390 -0
- polyfile/kaitai/parsers/stl.py +111 -0
- polyfile/kaitai/parsers/sudoers_ts.py +201 -0
- polyfile/kaitai/parsers/swf.py +406 -0
- polyfile/kaitai/parsers/systemd_journal.py +361 -0
- polyfile/kaitai/parsers/tcp_segment.py +57 -0
- polyfile/kaitai/parsers/tga.py +213 -0
- polyfile/kaitai/parsers/tls_client_hello.py +293 -0
- polyfile/kaitai/parsers/tr_dos_image.py +322 -0
- polyfile/kaitai/parsers/tsm.py +198 -0
- polyfile/kaitai/parsers/ttf.py +1847 -0
- polyfile/kaitai/parsers/udp_datagram.py +42 -0
- polyfile/kaitai/parsers/uefi_te.py +236 -0
- polyfile/kaitai/parsers/uimage.py +198 -0
- polyfile/kaitai/parsers/utf8_string.py +137 -0
- polyfile/kaitai/parsers/vfat.py +410 -0
- polyfile/kaitai/parsers/vlq_base128_be.py +104 -0
- polyfile/kaitai/parsers/vlq_base128_le.py +129 -0
- polyfile/kaitai/parsers/vmware_vmdk.py +167 -0
- polyfile/kaitai/parsers/vp8_ivf.py +112 -0
- polyfile/kaitai/parsers/warcraft_2_pud.py +423 -0
- polyfile/kaitai/parsers/wav.py +1014 -0
- polyfile/kaitai/parsers/websocket.py +167 -0
- polyfile/kaitai/parsers/windows_evt_log.py +304 -0
- polyfile/kaitai/parsers/windows_lnk_file.py +467 -0
- polyfile/kaitai/parsers/windows_minidump.py +575 -0
- polyfile/kaitai/parsers/windows_resource_file.py +243 -0
- polyfile/kaitai/parsers/windows_shell_items.py +190 -0
- polyfile/kaitai/parsers/windows_systemtime.py +52 -0
- polyfile/kaitai/parsers/wmf.py +502 -0
- polyfile/kaitai/parsers/xar.py +181 -0
- polyfile/kaitai/parsers/xwd.py +189 -0
- polyfile/kaitai/parsers/zip.py +685 -0
- polyfile/kaitai/parsers/zisofs.py +158 -0
- polyfile/kaitai/parsers/zx_spectrum_tap.py +184 -0
- polyfile/kaitaimatcher.py +113 -0
- polyfile/languagematcher.py +217 -0
- polyfile/logger.py +135 -0
- polyfile/magic.py +2983 -0
- polyfile/magic_defs/COPYING +29 -0
- polyfile/magic_defs/__init__.py +0 -0
- polyfile/magic_defs/acorn +102 -0
- polyfile/magic_defs/adi +13 -0
- polyfile/magic_defs/adventure +122 -0
- polyfile/magic_defs/aes +29 -0
- polyfile/magic_defs/algol68 +35 -0
- polyfile/magic_defs/allegro +9 -0
- polyfile/magic_defs/alliant +18 -0
- polyfile/magic_defs/alpha +32 -0
- polyfile/magic_defs/amanda +12 -0
- polyfile/magic_defs/amigaos +218 -0
- polyfile/magic_defs/android +259 -0
- polyfile/magic_defs/animation +1197 -0
- polyfile/magic_defs/aout +46 -0
- polyfile/magic_defs/apache +28 -0
- polyfile/magic_defs/apl +7 -0
- polyfile/magic_defs/apple +773 -0
- polyfile/magic_defs/application +7 -0
- polyfile/magic_defs/applix +13 -0
- polyfile/magic_defs/apt +52 -0
- polyfile/magic_defs/archive +2586 -0
- polyfile/magic_defs/aria +38 -0
- polyfile/magic_defs/arm +50 -0
- polyfile/magic_defs/asf +132 -0
- polyfile/magic_defs/assembler +18 -0
- polyfile/magic_defs/asterix +18 -0
- polyfile/magic_defs/att3b +41 -0
- polyfile/magic_defs/audio +1291 -0
- polyfile/magic_defs/avm +33 -0
- polyfile/magic_defs/basis +18 -0
- polyfile/magic_defs/beetle +7 -0
- polyfile/magic_defs/ber +65 -0
- polyfile/magic_defs/bflt +14 -0
- polyfile/magic_defs/bhl +10 -0
- polyfile/magic_defs/bioinformatics +178 -0
- polyfile/magic_defs/biosig +154 -0
- polyfile/magic_defs/blackberry +8 -0
- polyfile/magic_defs/blcr +25 -0
- polyfile/magic_defs/blender +50 -0
- polyfile/magic_defs/blit +24 -0
- polyfile/magic_defs/bm +10 -0
- polyfile/magic_defs/bout +11 -0
- polyfile/magic_defs/bsdi +33 -0
- polyfile/magic_defs/bsi +10 -0
- polyfile/magic_defs/btsnoop +13 -0
- polyfile/magic_defs/burp +7 -0
- polyfile/magic_defs/bytecode +41 -0
- polyfile/magic_defs/c-lang +110 -0
- polyfile/magic_defs/c64 +531 -0
- polyfile/magic_defs/cad +437 -0
- polyfile/magic_defs/cafebabe +107 -0
- polyfile/magic_defs/cbor +21 -0
- polyfile/magic_defs/ccf +14 -0
- polyfile/magic_defs/cddb +12 -0
- polyfile/magic_defs/chord +15 -0
- polyfile/magic_defs/cisco +12 -0
- polyfile/magic_defs/citrus +12 -0
- polyfile/magic_defs/clarion +27 -0
- polyfile/magic_defs/claris +48 -0
- polyfile/magic_defs/clipper +65 -0
- polyfile/magic_defs/clojure +30 -0
- polyfile/magic_defs/coff +98 -0
- polyfile/magic_defs/commands +201 -0
- polyfile/magic_defs/communications +22 -0
- polyfile/magic_defs/compress +461 -0
- polyfile/magic_defs/console +1213 -0
- polyfile/magic_defs/convex +69 -0
- polyfile/magic_defs/coverage +91 -0
- polyfile/magic_defs/cracklib +14 -0
- polyfile/magic_defs/crypto +31 -0
- polyfile/magic_defs/csv +8 -0
- polyfile/magic_defs/ctags +6 -0
- polyfile/magic_defs/ctf +23 -0
- polyfile/magic_defs/cubemap +8 -0
- polyfile/magic_defs/cups +56 -0
- polyfile/magic_defs/dact +11 -0
- polyfile/magic_defs/database +886 -0
- polyfile/magic_defs/dataone +47 -0
- polyfile/magic_defs/dbpf +15 -0
- polyfile/magic_defs/der +146 -0
- polyfile/magic_defs/diamond +12 -0
- polyfile/magic_defs/dif +33 -0
- polyfile/magic_defs/diff +41 -0
- polyfile/magic_defs/digital +59 -0
- polyfile/magic_defs/dolby +69 -0
- polyfile/magic_defs/dsf +25 -0
- polyfile/magic_defs/dump +96 -0
- polyfile/magic_defs/dwarfs +45 -0
- polyfile/magic_defs/dyadic +61 -0
- polyfile/magic_defs/ebml +8 -0
- polyfile/magic_defs/edid +11 -0
- polyfile/magic_defs/editors +43 -0
- polyfile/magic_defs/efi +15 -0
- polyfile/magic_defs/elf +379 -0
- polyfile/magic_defs/encore +22 -0
- polyfile/magic_defs/epoc +62 -0
- polyfile/magic_defs/erlang +21 -0
- polyfile/magic_defs/espressif +57 -0
- polyfile/magic_defs/esri +28 -0
- polyfile/magic_defs/etf +33 -0
- polyfile/magic_defs/fcs +9 -0
- polyfile/magic_defs/filesystems +2694 -0
- polyfile/magic_defs/finger +16 -0
- polyfile/magic_defs/firmware +133 -0
- polyfile/magic_defs/flash +62 -0
- polyfile/magic_defs/flif +36 -0
- polyfile/magic_defs/fonts +449 -0
- polyfile/magic_defs/forth +82 -0
- polyfile/magic_defs/fortran +9 -0
- polyfile/magic_defs/frame +62 -0
- polyfile/magic_defs/freebsd +164 -0
- polyfile/magic_defs/fsav +128 -0
- polyfile/magic_defs/fusecompress +12 -0
- polyfile/magic_defs/games +696 -0
- polyfile/magic_defs/gcc +17 -0
- polyfile/magic_defs/gconv +10 -0
- polyfile/magic_defs/gentoo +85 -0
- polyfile/magic_defs/geo +166 -0
- polyfile/magic_defs/geos +20 -0
- polyfile/magic_defs/gimp +77 -0
- polyfile/magic_defs/git +13 -0
- polyfile/magic_defs/glibc +21 -0
- polyfile/magic_defs/gnome +59 -0
- polyfile/magic_defs/gnu +173 -0
- polyfile/magic_defs/gnumeric +8 -0
- polyfile/magic_defs/gpt +240 -0
- polyfile/magic_defs/gpu +28 -0
- polyfile/magic_defs/grace +21 -0
- polyfile/magic_defs/graphviz +12 -0
- polyfile/magic_defs/gringotts +48 -0
- polyfile/magic_defs/guile +13 -0
- polyfile/magic_defs/hardware +12 -0
- polyfile/magic_defs/hitachi-sh +30 -0
- polyfile/magic_defs/hp +433 -0
- polyfile/magic_defs/human68k +26 -0
- polyfile/magic_defs/ibm370 +52 -0
- polyfile/magic_defs/ibm6000 +35 -0
- polyfile/magic_defs/icc +214 -0
- polyfile/magic_defs/iff +80 -0
- polyfile/magic_defs/images +4210 -0
- polyfile/magic_defs/inform +9 -0
- polyfile/magic_defs/intel +310 -0
- polyfile/magic_defs/interleaf +9 -0
- polyfile/magic_defs/island +10 -0
- polyfile/magic_defs/ispell +63 -0
- polyfile/magic_defs/isz +15 -0
- polyfile/magic_defs/java +52 -0
- polyfile/magic_defs/javascript +171 -0
- polyfile/magic_defs/jpeg +252 -0
- polyfile/magic_defs/json +8 -0
- polyfile/magic_defs/karma +9 -0
- polyfile/magic_defs/kde +11 -0
- polyfile/magic_defs/keepass +20 -0
- polyfile/magic_defs/kerberos +45 -0
- polyfile/magic_defs/kicad +85 -0
- polyfile/magic_defs/kml +34 -0
- polyfile/magic_defs/lammps +64 -0
- polyfile/magic_defs/lecter +6 -0
- polyfile/magic_defs/lex +12 -0
- polyfile/magic_defs/lif +50 -0
- polyfile/magic_defs/linux +557 -0
- polyfile/magic_defs/lisp +78 -0
- polyfile/magic_defs/llvm +22 -0
- polyfile/magic_defs/locoscript +12 -0
- polyfile/magic_defs/lua +31 -0
- polyfile/magic_defs/luks +126 -0
- polyfile/magic_defs/m4 +11 -0
- polyfile/magic_defs/mach +303 -0
- polyfile/magic_defs/macintosh +505 -0
- polyfile/magic_defs/macos +7 -0
- polyfile/magic_defs/magic +10 -0
- polyfile/magic_defs/magic.mgc +0 -0
- polyfile/magic_defs/mail.news +132 -0
- polyfile/magic_defs/make +21 -0
- polyfile/magic_defs/map +413 -0
- polyfile/magic_defs/maple +109 -0
- polyfile/magic_defs/marc21 +30 -0
- polyfile/magic_defs/mathcad +8 -0
- polyfile/magic_defs/mathematica +188 -0
- polyfile/magic_defs/matroska +17 -0
- polyfile/magic_defs/mcrypt +52 -0
- polyfile/magic_defs/measure +44 -0
- polyfile/magic_defs/mercurial +13 -0
- polyfile/magic_defs/metastore +8 -0
- polyfile/magic_defs/meteorological +53 -0
- polyfile/magic_defs/microfocus +21 -0
- polyfile/magic_defs/mime +9 -0
- polyfile/magic_defs/mips +120 -0
- polyfile/magic_defs/mirage +8 -0
- polyfile/magic_defs/misctools +140 -0
- polyfile/magic_defs/mkid +11 -0
- polyfile/magic_defs/mlssa +8 -0
- polyfile/magic_defs/mmdf +6 -0
- polyfile/magic_defs/modem +92 -0
- polyfile/magic_defs/modulefile +9 -0
- polyfile/magic_defs/motorola +71 -0
- polyfile/magic_defs/mozilla +37 -0
- polyfile/magic_defs/msdos +2304 -0
- polyfile/magic_defs/msooxml +68 -0
- polyfile/magic_defs/msvc +222 -0
- polyfile/magic_defs/msx +309 -0
- polyfile/magic_defs/mup +24 -0
- polyfile/magic_defs/music +17 -0
- polyfile/magic_defs/nasa +7 -0
- polyfile/magic_defs/natinst +24 -0
- polyfile/magic_defs/ncr +49 -0
- polyfile/magic_defs/neko +12 -0
- polyfile/magic_defs/netbsd +251 -0
- polyfile/magic_defs/netscape +26 -0
- polyfile/magic_defs/netware +11 -0
- polyfile/magic_defs/news +13 -0
- polyfile/magic_defs/nifty +202 -0
- polyfile/magic_defs/nim-lang +29 -0
- polyfile/magic_defs/nitpicker +14 -0
- polyfile/magic_defs/numpy +9 -0
- polyfile/magic_defs/oasis +12 -0
- polyfile/magic_defs/ocaml +14 -0
- polyfile/magic_defs/octave +6 -0
- polyfile/magic_defs/ole2compounddocs +760 -0
- polyfile/magic_defs/olf +98 -0
- polyfile/magic_defs/openfst +17 -0
- polyfile/magic_defs/opentimestamps +16 -0
- polyfile/magic_defs/oric +16 -0
- polyfile/magic_defs/os2 +186 -0
- polyfile/magic_defs/os400 +39 -0
- polyfile/magic_defs/os9 +80 -0
- polyfile/magic_defs/osf1 +10 -0
- polyfile/magic_defs/palm +156 -0
- polyfile/magic_defs/parix +13 -0
- polyfile/magic_defs/parrot +22 -0
- polyfile/magic_defs/pascal +39 -0
- polyfile/magic_defs/pbf +11 -0
- polyfile/magic_defs/pbm +8 -0
- polyfile/magic_defs/pc88 +24 -0
- polyfile/magic_defs/pc98 +77 -0
- polyfile/magic_defs/pci_ids +116 -0
- polyfile/magic_defs/pcjr +8 -0
- polyfile/magic_defs/pdf +51 -0
- polyfile/magic_defs/pdp +42 -0
- polyfile/magic_defs/perl +100 -0
- polyfile/magic_defs/pgf +52 -0
- polyfile/magic_defs/pgp +581 -0
- polyfile/magic_defs/pgp-binary-keys +388 -0
- polyfile/magic_defs/pkgadd +7 -0
- polyfile/magic_defs/plan9 +25 -0
- polyfile/magic_defs/playdate +57 -0
- polyfile/magic_defs/plus5 +18 -0
- polyfile/magic_defs/pmem +46 -0
- polyfile/magic_defs/polyfile_zip +5 -0
- polyfile/magic_defs/polyml +23 -0
- polyfile/magic_defs/printer +269 -0
- polyfile/magic_defs/project +10 -0
- polyfile/magic_defs/psdbms +14 -0
- polyfile/magic_defs/psl +14 -0
- polyfile/magic_defs/pulsar +13 -0
- polyfile/magic_defs/puzzle +17 -0
- polyfile/magic_defs/pwsafe +14 -0
- polyfile/magic_defs/pyramid +12 -0
- polyfile/magic_defs/python +305 -0
- polyfile/magic_defs/qt +30 -0
- polyfile/magic_defs/revision +66 -0
- polyfile/magic_defs/riff +840 -0
- polyfile/magic_defs/rinex +44 -0
- polyfile/magic_defs/ringdove +45 -0
- polyfile/magic_defs/rpi +52 -0
- polyfile/magic_defs/rpm +45 -0
- polyfile/magic_defs/rpmsg +7 -0
- polyfile/magic_defs/rst +11 -0
- polyfile/magic_defs/rtf +94 -0
- polyfile/magic_defs/ruby +55 -0
- polyfile/magic_defs/rust +21 -0
- polyfile/magic_defs/sc +7 -0
- polyfile/magic_defs/sccs +24 -0
- polyfile/magic_defs/scientific +144 -0
- polyfile/magic_defs/securitycerts +6 -0
- polyfile/magic_defs/selinux +24 -0
- polyfile/magic_defs/sendmail +37 -0
- polyfile/magic_defs/sequent +42 -0
- polyfile/magic_defs/sereal +35 -0
- polyfile/magic_defs/sgi +144 -0
- polyfile/magic_defs/sgml +161 -0
- polyfile/magic_defs/sharc +23 -0
- polyfile/magic_defs/sinclair +40 -0
- polyfile/magic_defs/sisu +18 -0
- polyfile/magic_defs/sketch +6 -0
- polyfile/magic_defs/smalltalk +25 -0
- polyfile/magic_defs/smile +34 -0
- polyfile/magic_defs/sniffer +482 -0
- polyfile/magic_defs/softquad +40 -0
- polyfile/magic_defs/sosi +40 -0
- polyfile/magic_defs/spec +21 -0
- polyfile/magic_defs/spectrum +184 -0
- polyfile/magic_defs/sql +288 -0
- polyfile/magic_defs/ssh +39 -0
- polyfile/magic_defs/ssl +20 -0
- polyfile/magic_defs/statistics +45 -0
- polyfile/magic_defs/subtitle +38 -0
- polyfile/magic_defs/sun +141 -0
- polyfile/magic_defs/svf +5 -0
- polyfile/magic_defs/sylk +36 -0
- polyfile/magic_defs/symbos +42 -0
- polyfile/magic_defs/sysex +429 -0
- polyfile/magic_defs/tcl +29 -0
- polyfile/magic_defs/teapot +6 -0
- polyfile/magic_defs/terminfo +63 -0
- polyfile/magic_defs/tex +141 -0
- polyfile/magic_defs/tgif +7 -0
- polyfile/magic_defs/ti-8x +239 -0
- polyfile/magic_defs/timezone +42 -0
- polyfile/magic_defs/tplink +95 -0
- polyfile/magic_defs/troff +38 -0
- polyfile/magic_defs/tuxedo +8 -0
- polyfile/magic_defs/typeset +8 -0
- polyfile/magic_defs/uf2 +72 -0
- polyfile/magic_defs/unicode +15 -0
- polyfile/magic_defs/unisig +12 -0
- polyfile/magic_defs/unknown +34 -0
- polyfile/magic_defs/usd +21 -0
- polyfile/magic_defs/uterus +16 -0
- polyfile/magic_defs/uuencode +28 -0
- polyfile/magic_defs/vacuum-cleaner +54 -0
- polyfile/magic_defs/varied.out +46 -0
- polyfile/magic_defs/varied.script +21 -0
- polyfile/magic_defs/vax +32 -0
- polyfile/magic_defs/vicar +17 -0
- polyfile/magic_defs/virtual +307 -0
- polyfile/magic_defs/virtutech +12 -0
- polyfile/magic_defs/visx +32 -0
- polyfile/magic_defs/vms +30 -0
- polyfile/magic_defs/vmware +6 -0
- polyfile/magic_defs/vorbis +155 -0
- polyfile/magic_defs/vxl +14 -0
- polyfile/magic_defs/warc +16 -0
- polyfile/magic_defs/weak +16 -0
- polyfile/magic_defs/web +18 -0
- polyfile/magic_defs/webassembly +17 -0
- polyfile/magic_defs/windows +1811 -0
- polyfile/magic_defs/wireless +7 -0
- polyfile/magic_defs/wordprocessors +630 -0
- polyfile/magic_defs/wsdl +23 -0
- polyfile/magic_defs/x68000 +25 -0
- polyfile/magic_defs/xdelta +13 -0
- polyfile/magic_defs/xenix +106 -0
- polyfile/magic_defs/xilinx +58 -0
- polyfile/magic_defs/xo65 +37 -0
- polyfile/magic_defs/xwindows +43 -0
- polyfile/magic_defs/yara +17 -0
- polyfile/magic_defs/zfs +96 -0
- polyfile/magic_defs/zilog +12 -0
- polyfile/magic_defs/zip +126 -0
- polyfile/magic_defs/zyxel +17 -0
- polyfile/nes.py +144 -0
- polyfile/nitf.py +15 -0
- polyfile/pdf.py +1264 -0
- polyfile/pickles.py +45 -0
- polyfile/polyfile.py +409 -0
- polyfile/profiling.py +115 -0
- polyfile/repl.py +624 -0
- polyfile/search.py +310 -0
- polyfile/serialization.py +323 -0
- polyfile/structmatcher.py +46 -0
- polyfile/structs.py +281 -0
- polyfile/templates/download.js +162 -0
- polyfile/templates/hexdump.css +268 -0
- polyfile/templates/hexdump.js +756 -0
- polyfile/templates/jquery-3.4.1.min.js +2 -0
- polyfile/templates/template.html +119 -0
- polyfile/wildcards.py +62 -0
- polyfile/zipmatcher.py +183 -0
- polyfile_weave-0.5.5.dist-info/METADATA +173 -0
- polyfile_weave-0.5.5.dist-info/RECORD +585 -0
- polyfile_weave-0.5.5.dist-info/WHEEL +5 -0
- polyfile_weave-0.5.5.dist-info/entry_points.txt +2 -0
- polyfile_weave-0.5.5.dist-info/licenses/LICENSE +202 -0
- polyfile_weave-0.5.5.dist-info/top_level.txt +2 -0
- polymerge/__init__.py +1 -0
- polymerge/__main__.py +296 -0
- polymerge/cfg.py +127 -0
- polymerge/polymerge.py +227 -0
- polymerge/polytracker.py +190 -0
polyfile/debugger.py
ADDED
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import atexit
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from pdb import Pdb
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, Callable, Dict, Generic, Iterable, Iterator, List, Optional, Type, TypeVar, Union
|
|
8
|
+
|
|
9
|
+
from .polyfile import __copyright__, __license__, __version__, PARSERS, Match, Parser, ParserFunction, Submatch
|
|
10
|
+
from .magic import (
|
|
11
|
+
AbsoluteOffset, FailedTest, InvalidOffsetError, MagicMatcher, MagicTest, Offset, TestResult, TEST_TYPES
|
|
12
|
+
)
|
|
13
|
+
from .profiling import Profiler, Unprofiled, unprofiled
|
|
14
|
+
from .repl import ANSIColor, ANSIWriter, arg_completer, command, ExitREPL, log, REPL, SetCompleter, string_escape
|
|
15
|
+
from .wildcards import Wildcard
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
B = TypeVar("B", bound="Breakpoint")
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
BREAKPOINT_TYPES: List[Type["Breakpoint"]] = []
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Breakpoint(ABC):
|
|
26
|
+
def __init_subclass__(cls, **kwargs):
|
|
27
|
+
super().__init_subclass__(**kwargs)
|
|
28
|
+
if cls.__name__ not in ("FailedBreakpoint", "MatchedBreakpoint"):
|
|
29
|
+
BREAKPOINT_TYPES.append(cls)
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def should_break(
|
|
33
|
+
self,
|
|
34
|
+
test: MagicTest,
|
|
35
|
+
data: bytes,
|
|
36
|
+
absolute_offset: int,
|
|
37
|
+
parent_match: Optional[TestResult],
|
|
38
|
+
result: Optional[TestResult]
|
|
39
|
+
) -> bool:
|
|
40
|
+
raise NotImplementedError()
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def parse(cls: Type[B], command: str) -> Optional[B]:
|
|
45
|
+
raise NotImplementedError()
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def from_str(command: str) -> Optional["Breakpoint"]:
|
|
49
|
+
if command.startswith("!"):
|
|
50
|
+
return FailedBreakpoint.parse(command)
|
|
51
|
+
elif command.startswith("="):
|
|
52
|
+
return MatchedBreakpoint.parse(command)
|
|
53
|
+
for b_type in BREAKPOINT_TYPES:
|
|
54
|
+
parsed = b_type.parse(command)
|
|
55
|
+
if parsed is not None:
|
|
56
|
+
return parsed
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def print_usage(cls, debugger: "Debugger"):
|
|
62
|
+
raise NotImplementedError()
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def __str__(self):
|
|
66
|
+
raise NotImplementedError()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FailedBreakpoint(Breakpoint):
|
|
70
|
+
def __init__(self, parent: Breakpoint):
|
|
71
|
+
self.parent: Breakpoint = parent
|
|
72
|
+
|
|
73
|
+
def should_break(
|
|
74
|
+
self,
|
|
75
|
+
test: MagicTest,
|
|
76
|
+
data: bytes,
|
|
77
|
+
absolute_offset: int,
|
|
78
|
+
parent_match: Optional[TestResult],
|
|
79
|
+
result: Optional[TestResult]
|
|
80
|
+
) -> bool:
|
|
81
|
+
return (result is None or isinstance(result, FailedTest)) and self.parent.should_break(
|
|
82
|
+
test, data, absolute_offset, parent_match, result
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def parse(cls: B, command: str) -> Optional[B]:
|
|
87
|
+
if not command.startswith("!"):
|
|
88
|
+
return None
|
|
89
|
+
parent = Breakpoint.from_str(command[1:])
|
|
90
|
+
if parent is not None:
|
|
91
|
+
return FailedBreakpoint(parent)
|
|
92
|
+
else:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def print_usage(cls, debugger: "Debugger"):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
def __str__(self):
|
|
100
|
+
return f"[FAILED] {self.parent!s}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class MatchedBreakpoint(Breakpoint):
|
|
104
|
+
def __init__(self, parent: Breakpoint):
|
|
105
|
+
self.parent: Breakpoint = parent
|
|
106
|
+
|
|
107
|
+
def should_break(
|
|
108
|
+
self,
|
|
109
|
+
test: MagicTest,
|
|
110
|
+
data: bytes,
|
|
111
|
+
absolute_offset: int,
|
|
112
|
+
parent_match: Optional[TestResult],
|
|
113
|
+
result: Optional[TestResult]
|
|
114
|
+
) -> bool:
|
|
115
|
+
return result is not None and not isinstance(result, FailedTest) and self.parent.should_break(
|
|
116
|
+
test, data, absolute_offset, parent_match, result
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def parse(cls: B, command: str) -> Optional[B]:
|
|
121
|
+
if not command.startswith("="):
|
|
122
|
+
return None
|
|
123
|
+
parent = Breakpoint.from_str(command[1:])
|
|
124
|
+
if parent is not None:
|
|
125
|
+
return MatchedBreakpoint(parent)
|
|
126
|
+
else:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def print_usage(cls, debugger: "Debugger"):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
def __str__(self):
|
|
134
|
+
return f"[MATCHED] {self.parent!s}"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class MimeBreakpoint(Breakpoint):
|
|
138
|
+
def __init__(self, mimetype: str):
|
|
139
|
+
self.mimetype: str = mimetype
|
|
140
|
+
self.pattern: Wildcard = Wildcard.parse(mimetype)
|
|
141
|
+
|
|
142
|
+
def should_break(
|
|
143
|
+
self,
|
|
144
|
+
test: MagicTest,
|
|
145
|
+
data: bytes,
|
|
146
|
+
absolute_offset: int,
|
|
147
|
+
parent_match: Optional[TestResult],
|
|
148
|
+
result: Optional[TestResult]
|
|
149
|
+
) -> bool:
|
|
150
|
+
return self.pattern.is_contained_in(test.mimetypes())
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def parse(cls: Type[B], command: str) -> Optional[B]:
|
|
154
|
+
if command.lower().startswith("mime:"):
|
|
155
|
+
return MimeBreakpoint(command[len("mime:"):])
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def print_usage(cls, debugger: "Debugger"):
|
|
160
|
+
debugger.write("b MIME:MIMETYPE", color=ANSIColor.MAGENTA)
|
|
161
|
+
debugger.write(" to break when a test is capable of matching that mimetype.\nThe ")
|
|
162
|
+
debugger.write("MIMETYPE", color=ANSIColor.MAGENTA)
|
|
163
|
+
debugger.write(" can include the ")
|
|
164
|
+
debugger.write("*", color=ANSIColor.MAGENTA)
|
|
165
|
+
debugger.write(" and ")
|
|
166
|
+
debugger.write("?", color=ANSIColor.MAGENTA)
|
|
167
|
+
debugger.write(" wildcards.\nFor example:\n")
|
|
168
|
+
debugger.write(" b MIME:application/pdf\n b MIME:*pdf\n", color=ANSIColor.MAGENTA)
|
|
169
|
+
|
|
170
|
+
def __str__(self):
|
|
171
|
+
return f"Breakpoint: Matching for MIME {self.mimetype}"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ExtensionBreakpoint(Breakpoint):
|
|
175
|
+
def __init__(self, ext: str):
|
|
176
|
+
self.ext: str = ext
|
|
177
|
+
|
|
178
|
+
def should_break(
|
|
179
|
+
self,
|
|
180
|
+
test: MagicTest,
|
|
181
|
+
data: bytes,
|
|
182
|
+
absolute_offset: int,
|
|
183
|
+
parent_match: Optional[TestResult],
|
|
184
|
+
result: Optional[TestResult]
|
|
185
|
+
) -> bool:
|
|
186
|
+
return self.ext in test.all_extensions()
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def parse(cls: Type[B], command: str) -> Optional[B]:
|
|
190
|
+
if command.lower().startswith("ext:"):
|
|
191
|
+
return ExtensionBreakpoint(command[len("ext:"):])
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def print_usage(cls, debugger: "Debugger") -> str:
|
|
196
|
+
debugger.write("b EXT:EXTENSION", color=ANSIColor.MAGENTA)
|
|
197
|
+
debugger.write(" to break when a test is capable of matching that extension.\nFor example:\n")
|
|
198
|
+
debugger.write(" b EXT:pdf\n", color=ANSIColor.MAGENTA)
|
|
199
|
+
|
|
200
|
+
def __str__(self):
|
|
201
|
+
return f"Breakpoint: Matching for extension {self.ext}"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class FileBreakpoint(Breakpoint):
|
|
205
|
+
def __init__(self, filename: str, line: int):
|
|
206
|
+
self.filename: str = filename
|
|
207
|
+
self.line: int = line
|
|
208
|
+
|
|
209
|
+
def should_break(
|
|
210
|
+
self,
|
|
211
|
+
test: MagicTest,
|
|
212
|
+
data: bytes,
|
|
213
|
+
absolute_offset: int,
|
|
214
|
+
parent_match: Optional[TestResult],
|
|
215
|
+
result: TestResult
|
|
216
|
+
) -> bool:
|
|
217
|
+
if test.source_info is None or test.source_info.line != self.line:
|
|
218
|
+
return False
|
|
219
|
+
if "/" in self.filename:
|
|
220
|
+
# it is a file path
|
|
221
|
+
return str(test.source_info.path) == self.filename
|
|
222
|
+
else:
|
|
223
|
+
# treat it like a filename
|
|
224
|
+
return test.source_info.path.name == self.filename
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def parse(cls: Type[B], command: str) -> Optional[B]:
|
|
228
|
+
filename, *remainder = command.split(":")
|
|
229
|
+
if not remainder:
|
|
230
|
+
return None
|
|
231
|
+
try:
|
|
232
|
+
line = int("".join(remainder))
|
|
233
|
+
except ValueError:
|
|
234
|
+
return None
|
|
235
|
+
if line <= 0:
|
|
236
|
+
return None
|
|
237
|
+
return FileBreakpoint(filename, line)
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def print_usage(cls, debugger: "Debugger"):
|
|
241
|
+
debugger.write("b FILENAME:LINE_NO", color=ANSIColor.MAGENTA)
|
|
242
|
+
debugger.write(" to break when the line of the given magic file is reached.\nFor example:\n")
|
|
243
|
+
debugger.write(" b archive:525\n", color=ANSIColor.MAGENTA)
|
|
244
|
+
debugger.write("will break on the test at line 525 of the archive DSL file.\n")
|
|
245
|
+
|
|
246
|
+
def __str__(self):
|
|
247
|
+
return f"Breakpoint: {self.filename} line {self.line}"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class InstrumentedTest:
|
|
251
|
+
def __init__(self, test: Type[MagicTest], debugger: "Debugger"):
|
|
252
|
+
self.test: Type[MagicTest] = test
|
|
253
|
+
self.debugger: Debugger = debugger
|
|
254
|
+
if "test" in test.__dict__:
|
|
255
|
+
self.original_test: Optional[Callable[[...], Optional[TestResult]]] = test.test
|
|
256
|
+
|
|
257
|
+
def wrapper(test_instance, *args, **kwargs) -> Optional[TestResult]:
|
|
258
|
+
# if self.original_test is None:
|
|
259
|
+
# # this is a NOOP
|
|
260
|
+
# return self.test.test(test_instance, *args, **kwargs)
|
|
261
|
+
return self.debugger.debug(self, test_instance, *args, **kwargs)
|
|
262
|
+
|
|
263
|
+
test.test = wrapper
|
|
264
|
+
else:
|
|
265
|
+
self.original_test = None
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def enabled(self) -> bool:
|
|
269
|
+
return self.original_test is not None
|
|
270
|
+
|
|
271
|
+
def uninstrument(self):
|
|
272
|
+
if self.original_test is not None:
|
|
273
|
+
# we are still assigned to the test function, so reset it
|
|
274
|
+
self.test.test = self.original_test
|
|
275
|
+
self.original_test = None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class InstrumentedParser:
|
|
279
|
+
def __init__(self, parser: Parser, debugger: "Debugger"):
|
|
280
|
+
self.parser: Type[Parser] = parser
|
|
281
|
+
self.debugger: Debugger = debugger
|
|
282
|
+
self.original_parser: Optional[ParserFunction] = parser.parse
|
|
283
|
+
|
|
284
|
+
def wrapper(parser_instance, *args, **kwargs) -> Iterator[Submatch]:
|
|
285
|
+
yield from self.debugger.debug_parse(self, parser_instance, *args, **kwargs)
|
|
286
|
+
|
|
287
|
+
parser.parse = wrapper
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def enalbed(self) -> bool:
|
|
291
|
+
return self.original_parser is not None
|
|
292
|
+
|
|
293
|
+
def uninstrument(self):
|
|
294
|
+
if self.original_parser is not None:
|
|
295
|
+
self.parser.parse = self.original_parser
|
|
296
|
+
self.original_parser = None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class StepMode(Enum):
|
|
300
|
+
RUNNING = 0
|
|
301
|
+
SINGLE_STEPPING = 1
|
|
302
|
+
NEXT = 2
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
T = TypeVar("T")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class Variable(Generic[T]):
|
|
309
|
+
def __init__(self, possibilities: Iterable[T], value: T):
|
|
310
|
+
self.possibilities: List[T] = list(possibilities)
|
|
311
|
+
self._value: T = value
|
|
312
|
+
self.value = value
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def value(self) -> T:
|
|
316
|
+
return self._value
|
|
317
|
+
|
|
318
|
+
@value.setter
|
|
319
|
+
def value(self, new_value: T):
|
|
320
|
+
if new_value not in self.possibilities:
|
|
321
|
+
raise ValueError(f"invalid value {new_value!r}; must be one of {self.possibilities!r}")
|
|
322
|
+
self._value = new_value
|
|
323
|
+
|
|
324
|
+
def parse(self, value: str) -> T:
|
|
325
|
+
value = value.strip().lower()
|
|
326
|
+
for p in self.possibilities:
|
|
327
|
+
if str(p).lower() == value:
|
|
328
|
+
return p
|
|
329
|
+
raise ValueError(f"Invalid value {value!r}; must be one of {', '.join(map(str, self.possibilities))}")
|
|
330
|
+
|
|
331
|
+
def __str__(self):
|
|
332
|
+
return str(self.value)
|
|
333
|
+
|
|
334
|
+
def __repr__(self):
|
|
335
|
+
return f"{self.__class__.__name__}()"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class BooleanVariable(Variable[bool]):
|
|
339
|
+
def __init__(self, value: bool):
|
|
340
|
+
super().__init__((True, False), value)
|
|
341
|
+
|
|
342
|
+
def parse(self, value: str) -> T:
|
|
343
|
+
try:
|
|
344
|
+
return super().parse(value)
|
|
345
|
+
except ValueError:
|
|
346
|
+
pass
|
|
347
|
+
value = value.strip().lower()
|
|
348
|
+
if value == "0" or value == "f":
|
|
349
|
+
return False
|
|
350
|
+
return bool(value)
|
|
351
|
+
|
|
352
|
+
def __bool__(self):
|
|
353
|
+
return self.value
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class BreakOnSubmatching(BooleanVariable):
|
|
357
|
+
def __init__(self, value: bool, debugger: "Debugger"):
|
|
358
|
+
self.debugger: Debugger = debugger
|
|
359
|
+
super().__init__(value)
|
|
360
|
+
|
|
361
|
+
@Variable.value.setter
|
|
362
|
+
def value(self, new_value):
|
|
363
|
+
Variable.value.fset(self, new_value)
|
|
364
|
+
if self.debugger.enabled:
|
|
365
|
+
# disable and re-enable the debugger to update the instrumentation
|
|
366
|
+
self.debugger._uninstrument()
|
|
367
|
+
self.debugger._instrument()
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class Profile(BooleanVariable):
|
|
371
|
+
def __init__(self, value: bool, debugger: "Debugger"):
|
|
372
|
+
self.debugger: Debugger = debugger
|
|
373
|
+
self._registered_callback = False
|
|
374
|
+
super().__init__(value)
|
|
375
|
+
|
|
376
|
+
@Variable.value.setter
|
|
377
|
+
def value(self, new_value):
|
|
378
|
+
Variable.value.fset(self, new_value)
|
|
379
|
+
if new_value and not self._registered_callback:
|
|
380
|
+
self._registered_callback = True
|
|
381
|
+
atexit.register(Debugger.profile_command, self.debugger, "")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class Debugger(REPL):
|
|
385
|
+
def __init__(self, break_on_parsing: bool = True):
|
|
386
|
+
super().__init__(name="the debugger")
|
|
387
|
+
self.instrumented_tests: List[InstrumentedTest] = []
|
|
388
|
+
self.breakpoints: List[Breakpoint] = []
|
|
389
|
+
self._entries: int = 0
|
|
390
|
+
self.step_mode: StepMode = StepMode.RUNNING
|
|
391
|
+
self.last_command: Optional[str] = None
|
|
392
|
+
self.last_test: Optional[MagicTest] = None
|
|
393
|
+
self.last_parent_match: Optional[MagicTest] = None
|
|
394
|
+
self.data: bytes = b""
|
|
395
|
+
self.last_offset: int = 0
|
|
396
|
+
self.last_result: Optional[TestResult] = None
|
|
397
|
+
self.repl_test: Optional[MagicTest] = None
|
|
398
|
+
self.instrumented_parsers: List[InstrumentedParser] = []
|
|
399
|
+
self.break_on_submatching: BreakOnSubmatching = BreakOnSubmatching(break_on_parsing, self)
|
|
400
|
+
self.profile: Profile = Profile(False, self)
|
|
401
|
+
self.variables_by_name: Dict[str, Variable] = {
|
|
402
|
+
"break_on_parsing": self.break_on_submatching,
|
|
403
|
+
"profile": self.profile
|
|
404
|
+
}
|
|
405
|
+
self.variable_descriptions: Dict[str, str] = {
|
|
406
|
+
"break_on_parsing": "Break when a PolyFile parser is about to be invoked and debug using PDB (default=True;"
|
|
407
|
+
" disable from the command line with `--no-debug-python`)",
|
|
408
|
+
"profile": "Profile the performance of each magic test that is run (default=False)"
|
|
409
|
+
}
|
|
410
|
+
self.profile_results: Dict[Union[MagicTest, Type[Parser]], float] = {}
|
|
411
|
+
self._pdb: Optional[Pdb] = None
|
|
412
|
+
self._debug_next: bool = False
|
|
413
|
+
|
|
414
|
+
def save_context(self):
|
|
415
|
+
class DebugContext:
|
|
416
|
+
def __init__(self, debugger: Debugger):
|
|
417
|
+
self.debugger: Debugger = debugger
|
|
418
|
+
self.__saved_state = {}
|
|
419
|
+
|
|
420
|
+
def __enter__(self) -> Debugger:
|
|
421
|
+
self.__saved_state = dict(self.debugger.__dict__)
|
|
422
|
+
return self.debugger
|
|
423
|
+
|
|
424
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
425
|
+
self.debugger.__dict__ = self.__saved_state
|
|
426
|
+
|
|
427
|
+
return DebugContext(self)
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def enabled(self) -> bool:
|
|
431
|
+
return any(t.enabled for t in self.instrumented_tests)
|
|
432
|
+
|
|
433
|
+
def _uninstrument(self):
|
|
434
|
+
# Uninstrument any existing instrumentation:
|
|
435
|
+
for t in self.instrumented_tests:
|
|
436
|
+
t.uninstrument()
|
|
437
|
+
self.instrumented_tests = []
|
|
438
|
+
for m in self.instrumented_parsers:
|
|
439
|
+
m.uninstrument()
|
|
440
|
+
self.instrumented_parsers = []
|
|
441
|
+
|
|
442
|
+
def _instrument(self):
|
|
443
|
+
# Instrument all of the MagicTest.test functions:
|
|
444
|
+
for test in TEST_TYPES:
|
|
445
|
+
if "test" in test.__dict__:
|
|
446
|
+
# this class actually implements the test() function
|
|
447
|
+
self.instrumented_tests.append(InstrumentedTest(test, self))
|
|
448
|
+
if self.break_on_submatching.value:
|
|
449
|
+
for parsers in PARSERS.values():
|
|
450
|
+
for parser in parsers:
|
|
451
|
+
self.instrumented_parsers.append(InstrumentedParser(parser, self))
|
|
452
|
+
|
|
453
|
+
@enabled.setter
|
|
454
|
+
def enabled(self, is_enabled: bool):
|
|
455
|
+
was_enabled = self.enabled
|
|
456
|
+
self._uninstrument()
|
|
457
|
+
if is_enabled:
|
|
458
|
+
self._instrument()
|
|
459
|
+
self.load_history()
|
|
460
|
+
self.write(f"PolyFile {__version__}\n", color=ANSIColor.MAGENTA, bold=True)
|
|
461
|
+
self.write(f"{__copyright__}\n{__license__}\n\nFor help, type \"help\".\n")
|
|
462
|
+
self.repl()
|
|
463
|
+
elif was_enabled:
|
|
464
|
+
# we are now disabled, so store our history
|
|
465
|
+
self.store_history()
|
|
466
|
+
|
|
467
|
+
def __enter__(self) -> "Debugger":
|
|
468
|
+
self._entries += 1
|
|
469
|
+
if self._entries == 1:
|
|
470
|
+
self.enabled = True
|
|
471
|
+
return self
|
|
472
|
+
|
|
473
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
474
|
+
self._entries -= 1
|
|
475
|
+
if self._entries == 0:
|
|
476
|
+
self.enabled = False
|
|
477
|
+
|
|
478
|
+
def should_break(self) -> bool:
|
|
479
|
+
return self.step_mode == StepMode.SINGLE_STEPPING or (
|
|
480
|
+
self.step_mode == StepMode.NEXT and self.last_result
|
|
481
|
+
) or any(
|
|
482
|
+
b.should_break(self.last_test, self.data, self.last_offset, self.last_parent_match, self.last_result)
|
|
483
|
+
for b in self.breakpoints
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
def write_test(self, test: MagicTest, is_current_test: bool = False):
|
|
487
|
+
writer = ANSIWriter(use_ansi=sys.stdout.isatty())
|
|
488
|
+
if self.profile.value and test in self.profile_results:
|
|
489
|
+
pre_mime_text = f"\t⏱ {int(self.profile_results[test] + 0.5)}ms"
|
|
490
|
+
else:
|
|
491
|
+
pre_mime_text = ""
|
|
492
|
+
test.write(writer, is_current_test=is_current_test, pre_mime_text=pre_mime_text)
|
|
493
|
+
super().write(str(writer))
|
|
494
|
+
|
|
495
|
+
def write(self, message: Any, bold: bool = False, dim: bool = False, color: Optional[ANSIColor] = None):
|
|
496
|
+
if sys.stdout.isatty() and isinstance(message, MagicTest):
|
|
497
|
+
self.write_test(message)
|
|
498
|
+
else:
|
|
499
|
+
super().write(message=message, bold=bold, dim=dim, color=color)
|
|
500
|
+
|
|
501
|
+
def print_context(self, data: bytes, offset: int, context_bytes: int = 32, num_bytes: int = 1):
|
|
502
|
+
writer = ANSIWriter(use_ansi=sys.stdout.isatty())
|
|
503
|
+
writer.write_context(data, offset, context_bytes, num_bytes)
|
|
504
|
+
super().write(message=str(writer))
|
|
505
|
+
|
|
506
|
+
def _debug(self, func: Callable[[Any], T], *args, **kwargs) -> T:
|
|
507
|
+
if self.profile.value:
|
|
508
|
+
self.write("Warning:", bold=True, color=ANSIColor.RED)
|
|
509
|
+
self.write(" Profiling will be disabled for this test while debugging!\n")
|
|
510
|
+
with Unprofiled():
|
|
511
|
+
# check if there is already a debugger attached, most likely an IDE like PyCharm;
|
|
512
|
+
# if so, use that debugger instead of creating our own instance of pdb
|
|
513
|
+
external_debugger = sys.gettrace() is not None or self._pdb is not None
|
|
514
|
+
if not external_debugger:
|
|
515
|
+
self._pdb = Pdb(skip=["polyfile.magic_debugger", "polyfile.magic"])
|
|
516
|
+
if sys.stderr.isatty():
|
|
517
|
+
self._pdb.prompt = "\001\u001b[1m\002(polyfile-Pdb)\001\u001b[0m\002 "
|
|
518
|
+
else:
|
|
519
|
+
self._pdb.prompt = "(polyfile-Pdb) "
|
|
520
|
+
try:
|
|
521
|
+
result = self._pdb.runcall(func, *args, **kwargs)
|
|
522
|
+
finally:
|
|
523
|
+
self._pdb = None
|
|
524
|
+
else:
|
|
525
|
+
breakpoint()
|
|
526
|
+
# This next line will invoke the test:
|
|
527
|
+
result = func(*args, **kwargs)
|
|
528
|
+
return result
|
|
529
|
+
|
|
530
|
+
def debug(
|
|
531
|
+
self,
|
|
532
|
+
instrumented_test: InstrumentedTest,
|
|
533
|
+
test: MagicTest,
|
|
534
|
+
data: bytes,
|
|
535
|
+
absolute_offset: int,
|
|
536
|
+
parent_match: Optional[TestResult]
|
|
537
|
+
) -> Optional[TestResult]:
|
|
538
|
+
profiler = Profiler()
|
|
539
|
+
if instrumented_test.original_test is None:
|
|
540
|
+
test_func = instrumented_test.test.test
|
|
541
|
+
else:
|
|
542
|
+
test_func = instrumented_test.original_test
|
|
543
|
+
if self._debug_next:
|
|
544
|
+
self._debug_next = False
|
|
545
|
+
result: TestResult = self._debug(test_func, test, data, absolute_offset, parent_match)
|
|
546
|
+
else:
|
|
547
|
+
with profiler:
|
|
548
|
+
result = test_func(test, data, absolute_offset, parent_match)
|
|
549
|
+
if self.profile.value:
|
|
550
|
+
self.profile_results[test] = profiler.elapsed_ms
|
|
551
|
+
if self.repl_test is test:
|
|
552
|
+
# this is a one-off test run from the REPL, so do not save its results
|
|
553
|
+
return result
|
|
554
|
+
self.last_result = result
|
|
555
|
+
self.last_test = test
|
|
556
|
+
self.data = data
|
|
557
|
+
self.last_offset = absolute_offset
|
|
558
|
+
self.last_parent_match = parent_match
|
|
559
|
+
if self.should_break():
|
|
560
|
+
self.repl()
|
|
561
|
+
return self.last_result
|
|
562
|
+
|
|
563
|
+
@unprofiled
|
|
564
|
+
def print_where(
|
|
565
|
+
self,
|
|
566
|
+
test: Optional[MagicTest] = None,
|
|
567
|
+
offset: Optional[int] = None,
|
|
568
|
+
parent_match: Optional[TestResult] = None,
|
|
569
|
+
result: Optional[TestResult] = None
|
|
570
|
+
):
|
|
571
|
+
if test is None:
|
|
572
|
+
test = self.last_test
|
|
573
|
+
if test is None:
|
|
574
|
+
self.write("The first test has not yet been run.\n", color=ANSIColor.RED)
|
|
575
|
+
self.write("Use `step`, `next`, or `run` to start testing.\n")
|
|
576
|
+
return
|
|
577
|
+
if offset is None:
|
|
578
|
+
offset = self.last_offset
|
|
579
|
+
if parent_match is None:
|
|
580
|
+
parent_match = self.last_parent_match
|
|
581
|
+
if result is None:
|
|
582
|
+
result = self.last_result
|
|
583
|
+
wrote_breakpoints = False
|
|
584
|
+
for b in self.breakpoints:
|
|
585
|
+
if b.should_break(test, self.data, offset, parent_match, result):
|
|
586
|
+
self.write(b, color=ANSIColor.MAGENTA)
|
|
587
|
+
self.write("\n")
|
|
588
|
+
wrote_breakpoints = True
|
|
589
|
+
if wrote_breakpoints:
|
|
590
|
+
self.write("\n")
|
|
591
|
+
test_stack = [test]
|
|
592
|
+
while test_stack[-1].parent is not None:
|
|
593
|
+
test_stack.append(test_stack[-1].parent)
|
|
594
|
+
for i, t in enumerate(reversed(test_stack)):
|
|
595
|
+
if i == len(test_stack) - 1:
|
|
596
|
+
self.write_test(t, is_current_test=True)
|
|
597
|
+
else:
|
|
598
|
+
self.write_test(t)
|
|
599
|
+
test_stack = list(reversed(test.children))
|
|
600
|
+
descendants = []
|
|
601
|
+
while test_stack:
|
|
602
|
+
descendant = test_stack.pop()
|
|
603
|
+
if descendant.can_match_mime:
|
|
604
|
+
descendants.append(descendant)
|
|
605
|
+
test_stack.extend(reversed(descendant.children))
|
|
606
|
+
for t in descendants:
|
|
607
|
+
self.write_test(t)
|
|
608
|
+
self.write("\n")
|
|
609
|
+
data_offset = offset
|
|
610
|
+
if not isinstance(test.offset, AbsoluteOffset):
|
|
611
|
+
try:
|
|
612
|
+
data_offset = test.offset.to_absolute(self.data, parent_match)
|
|
613
|
+
self.write(str(test.offset), color=ANSIColor.BLUE)
|
|
614
|
+
self.write(" = byte offset ", dim=True)
|
|
615
|
+
self.write(f"{data_offset!s}\n", bold=True)
|
|
616
|
+
except InvalidOffsetError as e:
|
|
617
|
+
self.write(f"{e!s}\n", color=ANSIColor.RED)
|
|
618
|
+
if result is not None and hasattr(result, "length"):
|
|
619
|
+
context_bytes = result.length
|
|
620
|
+
else:
|
|
621
|
+
context_bytes = 1
|
|
622
|
+
self.print_context(self.data, data_offset, num_bytes=context_bytes)
|
|
623
|
+
if result is not None:
|
|
624
|
+
if not result:
|
|
625
|
+
self.write("Test failed.\n", color=ANSIColor.RED)
|
|
626
|
+
if isinstance(result, FailedTest):
|
|
627
|
+
self.write(result.message)
|
|
628
|
+
self.write("\n")
|
|
629
|
+
else:
|
|
630
|
+
self.write("Test succeeded.\n", color=ANSIColor.GREEN)
|
|
631
|
+
|
|
632
|
+
def print_match(self, match: Match):
|
|
633
|
+
obj = match.to_obj()
|
|
634
|
+
self.write("{\n", bold=True)
|
|
635
|
+
for key, value in obj.items():
|
|
636
|
+
if isinstance(value, list):
|
|
637
|
+
# TODO: Maybe implement list printing later.
|
|
638
|
+
# I don't think there will be lists here currently, thouh.
|
|
639
|
+
continue
|
|
640
|
+
self.write(f" {key!r}", color=ANSIColor.BLUE)
|
|
641
|
+
self.write(": ", bold=True)
|
|
642
|
+
if isinstance(value, int) or isinstance(value, float):
|
|
643
|
+
self.write(str(value))
|
|
644
|
+
else:
|
|
645
|
+
self.write(repr(value), color=ANSIColor.GREEN)
|
|
646
|
+
self.write(",\n", bold=True)
|
|
647
|
+
self.write("}\n", bold=True)
|
|
648
|
+
|
|
649
|
+
def debug_parse(self, instrumented_parser: InstrumentedParser, file_stream, match: Match) -> Iterator[Submatch]:
|
|
650
|
+
log.clear_status()
|
|
651
|
+
|
|
652
|
+
if instrumented_parser.original_parser is None:
|
|
653
|
+
parse = instrumented_parser.parser.parse
|
|
654
|
+
else:
|
|
655
|
+
parse = instrumented_parser.original_parser
|
|
656
|
+
|
|
657
|
+
def print_location():
|
|
658
|
+
self.write(f"{file_stream.name}", dim=True, color=ANSIColor.CYAN)
|
|
659
|
+
self.write(":", dim=True)
|
|
660
|
+
self.write(f"{file_stream.tell()} ", dim=True, color=ANSIColor.CYAN)
|
|
661
|
+
|
|
662
|
+
if self._pdb is not None:
|
|
663
|
+
# We are already debugging!
|
|
664
|
+
print_location()
|
|
665
|
+
self.write(f"Parsing for submatches using {instrumented_parser.parser!s}.\n")
|
|
666
|
+
profiler = Profiler()
|
|
667
|
+
with profiler:
|
|
668
|
+
yield from parse(file_stream, match)
|
|
669
|
+
if self.profile.value:
|
|
670
|
+
self.profile_results[instrumented_parser.parser] = profiler.elapsed_ms
|
|
671
|
+
return
|
|
672
|
+
with Unprofiled():
|
|
673
|
+
self.print_match(match)
|
|
674
|
+
print_location()
|
|
675
|
+
self.write(f"About to parse for submatches using {instrumented_parser.parser!s}.\n")
|
|
676
|
+
buffer = ANSIWriter(use_ansi=sys.stderr.isatty(), escape_for_readline=True)
|
|
677
|
+
buffer.write("Debug using PDB? ")
|
|
678
|
+
buffer.write("(disable this prompt with `", dim=True)
|
|
679
|
+
buffer.write("set ", color=ANSIColor.BLUE)
|
|
680
|
+
buffer.write("break_on_parsing ", color=ANSIColor.GREEN)
|
|
681
|
+
buffer.write("False", color=ANSIColor.CYAN)
|
|
682
|
+
buffer.write("`)", dim=True)
|
|
683
|
+
if not self.prompt(str(buffer), default=False):
|
|
684
|
+
with Profiler() as p:
|
|
685
|
+
yield from parse(file_stream, match)
|
|
686
|
+
if self.profile.value:
|
|
687
|
+
self.profile_results[instrumented_parser.parser] = p.elapsed_ms
|
|
688
|
+
return
|
|
689
|
+
try:
|
|
690
|
+
if self.profile.value:
|
|
691
|
+
self.write("Warning:", bold=True, color=ANSIColor.RED)
|
|
692
|
+
self.write(" Profiling will be disabled for this parser while debugging!\n")
|
|
693
|
+
with Unprofiled():
|
|
694
|
+
# check if there is already a debugger attached, most likely an IDE like PyCharm;
|
|
695
|
+
# if so, use that debugger instead of creating our own instance of pdb
|
|
696
|
+
external_debugger = sys.gettrace() is not None
|
|
697
|
+
if not external_debugger:
|
|
698
|
+
self._pdb = Pdb(skip=["polyfile.magic_debugger", "polyfile.magic"])
|
|
699
|
+
if sys.stderr.isatty():
|
|
700
|
+
self._pdb.prompt = "\001\u001b[1m\002(polyfile-Pdb)\001\u001b[0m\002 "
|
|
701
|
+
else:
|
|
702
|
+
self._pdb.prompt = "(polyfile-Pdb) "
|
|
703
|
+
generator = parse(file_stream, match)
|
|
704
|
+
while True:
|
|
705
|
+
try:
|
|
706
|
+
if external_debugger:
|
|
707
|
+
breakpoint()
|
|
708
|
+
# This next line will invoke the parser:
|
|
709
|
+
result = next(generator)
|
|
710
|
+
else:
|
|
711
|
+
result = self._pdb.runcall(next, generator)
|
|
712
|
+
self.write(f"Got a submatch:\n", dim=True)
|
|
713
|
+
self.print_match(result)
|
|
714
|
+
yield result
|
|
715
|
+
except StopIteration:
|
|
716
|
+
self.write(f"Yielded all submatches from {match.__class__.__name__} at offset {match.offset}."
|
|
717
|
+
f"\n")
|
|
718
|
+
break
|
|
719
|
+
print_location()
|
|
720
|
+
if not self.prompt("Continue debugging the next submatch?", default=True):
|
|
721
|
+
if self.prompt("Print the remaining submatches?", default=False):
|
|
722
|
+
for result in generator:
|
|
723
|
+
self.print_match(result)
|
|
724
|
+
yield result
|
|
725
|
+
else:
|
|
726
|
+
yield from generator
|
|
727
|
+
break
|
|
728
|
+
finally:
|
|
729
|
+
self._pdb = None
|
|
730
|
+
|
|
731
|
+
def before_prompt(self):
|
|
732
|
+
if self.last_test is not None:
|
|
733
|
+
self.print_where()
|
|
734
|
+
|
|
735
|
+
@command(name="continue", allows_abbreviation=True, help="continue execution until the next breakpoint is hit",
|
|
736
|
+
aliases=("run",))
|
|
737
|
+
def cont(self, arguments: str):
|
|
738
|
+
self.step_mode = StepMode.RUNNING
|
|
739
|
+
self.last_command = command
|
|
740
|
+
raise ExitREPL()
|
|
741
|
+
|
|
742
|
+
@command(allows_abbreviation=True, help="step through a single magic test")
|
|
743
|
+
def step(self, arguments: str):
|
|
744
|
+
self.step_mode = StepMode.SINGLE_STEPPING
|
|
745
|
+
self.last_command = command
|
|
746
|
+
raise ExitREPL()
|
|
747
|
+
|
|
748
|
+
@command(allows_abbreviation=False, name="debug_and_rerun", help="re-run the last test and debug in PDB")
|
|
749
|
+
def debug_and_rerun(self, arguments: str):
|
|
750
|
+
self.last_command = command
|
|
751
|
+
if self.last_test is None:
|
|
752
|
+
self.write("Error: A test has not yet been run\n", color=ANSIColor.RED)
|
|
753
|
+
return
|
|
754
|
+
test = self.last_test.__class__.test
|
|
755
|
+
# Is the test instrumented? If so, find the original version!
|
|
756
|
+
for instrumented in self.instrumented_tests:
|
|
757
|
+
if instrumented.test.test is test:
|
|
758
|
+
test = instrumented.original_test
|
|
759
|
+
break
|
|
760
|
+
self._debug(test, self.last_test, self.data, self.last_offset, self.last_parent_match)
|
|
761
|
+
|
|
762
|
+
@command(allows_abbreviation=False, name="debug_and_continue", aliases=("debug", "debug_and_cont"),
|
|
763
|
+
help="continue while debugging in PDB")
|
|
764
|
+
def debug_and_continue(self, arguments: str):
|
|
765
|
+
if sys.gettrace() is None and self._pdb is None:
|
|
766
|
+
self.write("Error: ", color=ANSIColor.RED)
|
|
767
|
+
self.write("`", dim=True)
|
|
768
|
+
self.write("debug_and_continue", color=ANSIColor.BLUE)
|
|
769
|
+
self.write("`", dim=True)
|
|
770
|
+
self.write(" can only be called when running from an external debugger like PDB or PyCharm",
|
|
771
|
+
color=ANSIColor.RED)
|
|
772
|
+
return
|
|
773
|
+
self.step_mode = StepMode.RUNNING
|
|
774
|
+
self.last_command = command
|
|
775
|
+
try:
|
|
776
|
+
raise ExitREPL()
|
|
777
|
+
finally:
|
|
778
|
+
breakpoint()
|
|
779
|
+
|
|
780
|
+
@command(allows_abbreviation=False, help="step into the next magic test and debug in PDB")
|
|
781
|
+
def debug_and_step(self, arguments: str):
|
|
782
|
+
self.step_mode = StepMode.SINGLE_STEPPING
|
|
783
|
+
self.last_command = command
|
|
784
|
+
self._debug_next = True
|
|
785
|
+
raise ExitREPL()
|
|
786
|
+
|
|
787
|
+
@command(allows_abbreviation=True, help="continue execution until the next test that matches")
|
|
788
|
+
def next(self, arguments: str):
|
|
789
|
+
self.step_mode = StepMode.NEXT
|
|
790
|
+
self.last_command = command
|
|
791
|
+
raise ExitREPL()
|
|
792
|
+
|
|
793
|
+
@command(allows_abbreviation=True,
|
|
794
|
+
help="print the context of the current magic test",
|
|
795
|
+
aliases=("info stack", "backtrace"))
|
|
796
|
+
def where(self, arguments: str):
|
|
797
|
+
self.print_where()
|
|
798
|
+
|
|
799
|
+
@command(allows_abbreviation=False, name="profile", help="print current profiling results (to enable profiling, "
|
|
800
|
+
"use `set profile True`)", )
|
|
801
|
+
def profile_command(self, arguments: str):
|
|
802
|
+
if not self.profile_results:
|
|
803
|
+
if not self.profile.value:
|
|
804
|
+
self.write("Profiling is disabled.\n", color=ANSIColor.RED)
|
|
805
|
+
self.write("Enable it by running `set profile True`.\n")
|
|
806
|
+
else:
|
|
807
|
+
self.write("No profiling data yet.\n")
|
|
808
|
+
return
|
|
809
|
+
self.write("Profile Results:\n", bold=True)
|
|
810
|
+
tests = sorted([(runtime, test) for test, runtime in self.profile_results.items()], reverse=True,
|
|
811
|
+
key=lambda x: x[0])
|
|
812
|
+
max_text_width = 0
|
|
813
|
+
for runtime, test in tests:
|
|
814
|
+
if isinstance(test, MagicTest):
|
|
815
|
+
if test.source_info is not None and test.source_info.original_line is not None:
|
|
816
|
+
max_text_width = max(max_text_width,
|
|
817
|
+
len(test.source_info.path.name) + 1 + len(str(test.source_info.line)))
|
|
818
|
+
else:
|
|
819
|
+
max_text_width = max(max_text_width, test.level + len(str(test.offset)))
|
|
820
|
+
else:
|
|
821
|
+
max_text_width = max(max_text_width, len(str(test)))
|
|
822
|
+
for runtime, test in tests:
|
|
823
|
+
if isinstance(test, MagicTest):
|
|
824
|
+
self.write("🪄 ")
|
|
825
|
+
if test.source_info is not None and test.source_info.original_line is not None:
|
|
826
|
+
self.write(test.source_info.path.name, dim=True, color=ANSIColor.CYAN)
|
|
827
|
+
self.write(":", dim=True)
|
|
828
|
+
self.write(test.source_info.line, dim=True, color=ANSIColor.CYAN)
|
|
829
|
+
padding = max_text_width - (len(test.source_info.path.name) + 1 + len(str(test.source_info.line)))
|
|
830
|
+
else:
|
|
831
|
+
self.write(f"{'>' * test.level}{test.offset!s}", color=ANSIColor.BLUE)
|
|
832
|
+
padding = max_text_width - test.level - len(str(test.offset))
|
|
833
|
+
else:
|
|
834
|
+
self.write("🖥 ")
|
|
835
|
+
self.write(str(test), color=ANSIColor.BLUE)
|
|
836
|
+
padding = max_text_width - len(str(test))
|
|
837
|
+
self.write(" " * padding)
|
|
838
|
+
if runtime >= 1.0:
|
|
839
|
+
self.write(f" ⏱ {int(runtime + 0.5)}ms\n")
|
|
840
|
+
else:
|
|
841
|
+
self.write(f" ⏱ {runtime:.2f}ms\n")
|
|
842
|
+
|
|
843
|
+
@command(allows_abbreviation=True, help="test the following libmagic DSL test at the current position")
|
|
844
|
+
def test(self, args: str):
|
|
845
|
+
if args:
|
|
846
|
+
if self.last_test is None:
|
|
847
|
+
self.write("The first test has not yet been run.\n", color=ANSIColor.RED)
|
|
848
|
+
self.write("Use `step`, `next`, or `run` to start testing.\n")
|
|
849
|
+
return
|
|
850
|
+
try:
|
|
851
|
+
test = MagicMatcher.parse_test(args, Path("STDIN"), 1, parent=self.last_test)
|
|
852
|
+
if test is None:
|
|
853
|
+
self.write("Error parsing test\n", color=ANSIColor.RED)
|
|
854
|
+
return
|
|
855
|
+
try:
|
|
856
|
+
with self.save_context():
|
|
857
|
+
self.repl_test = test
|
|
858
|
+
if test.parent is None:
|
|
859
|
+
self.last_result = None
|
|
860
|
+
self.last_offset = 0
|
|
861
|
+
result = test.test(self.data, self.last_offset, parent_match=self.last_result)
|
|
862
|
+
finally:
|
|
863
|
+
if test.parent is not None:
|
|
864
|
+
test.parent.children.remove(test)
|
|
865
|
+
self.print_where(
|
|
866
|
+
test=test, offset=self.last_offset, parent_match=self.last_result, result=result
|
|
867
|
+
)
|
|
868
|
+
except ValueError as e:
|
|
869
|
+
self.write(f"{e!s}\n", color=ANSIColor.RED)
|
|
870
|
+
else:
|
|
871
|
+
self.write("Usage: ", dim=True)
|
|
872
|
+
self.write("test", bold=True, color=ANSIColor.BLUE)
|
|
873
|
+
self.write(" LIBMAGIC DSL TEST\n", bold=True)
|
|
874
|
+
self.write("Attempt to run the given test.\n\nExample:\n")
|
|
875
|
+
self.write("test", bold=True, color=ANSIColor.BLUE)
|
|
876
|
+
self.write(" 0 search \\x50\\x4b\\x05\\x06 ZIP EOCD record\n", bold=True)
|
|
877
|
+
|
|
878
|
+
@command(allows_abbreviation=True, help="print the computed absolute offset of the following libmagic DSL offset")
|
|
879
|
+
def print(self, args: str):
|
|
880
|
+
if args:
|
|
881
|
+
if self.last_test is None:
|
|
882
|
+
self.write("The first test has not yet been run.\n", color=ANSIColor.RED)
|
|
883
|
+
self.write("Use `step`, `next`, or `run` to start testing.\n")
|
|
884
|
+
return
|
|
885
|
+
try:
|
|
886
|
+
dsl_offset = Offset.parse(args)
|
|
887
|
+
except ValueError as e:
|
|
888
|
+
self.write(f"{e!s}\n", color=ANSIColor.RED)
|
|
889
|
+
return
|
|
890
|
+
try:
|
|
891
|
+
absolute = dsl_offset.to_absolute(self.data, self.last_result)
|
|
892
|
+
self.write(f"{absolute}\n", bold=True)
|
|
893
|
+
self.print_context(self.data, absolute)
|
|
894
|
+
except InvalidOffsetError as e:
|
|
895
|
+
self.write(f"{e!s}\n", color=ANSIColor.RED)
|
|
896
|
+
return
|
|
897
|
+
else:
|
|
898
|
+
self.write("Usage: ", dim=True)
|
|
899
|
+
self.write("print", bold=True, color=ANSIColor.BLUE)
|
|
900
|
+
self.write(" LIBMAGIC DSL OFFSET\n", bold=True)
|
|
901
|
+
self.write("Calculate the absolute offset for the given DSL offset.\n\nExample:\n")
|
|
902
|
+
self.write("print", bold=True, color=ANSIColor.BLUE)
|
|
903
|
+
self.write(" (&0x7c.l+0x26)\n", bold=True)
|
|
904
|
+
|
|
905
|
+
@command(allows_abbreviation=True, help="list the current breakpoints or add a new one")
|
|
906
|
+
def breakpoint(self, args: str):
|
|
907
|
+
if args:
|
|
908
|
+
parsed = Breakpoint.from_str(args)
|
|
909
|
+
if parsed is None:
|
|
910
|
+
self.write("Error: Invalid breakpoint pattern\n", color=ANSIColor.RED)
|
|
911
|
+
else:
|
|
912
|
+
self.write(parsed, color=ANSIColor.MAGENTA)
|
|
913
|
+
self.write("\n")
|
|
914
|
+
self.breakpoints.append(parsed)
|
|
915
|
+
else:
|
|
916
|
+
if self.breakpoints:
|
|
917
|
+
for i, b in enumerate(self.breakpoints):
|
|
918
|
+
self.write(f"{i}:\t", dim=True)
|
|
919
|
+
self.write(b, color=ANSIColor.MAGENTA)
|
|
920
|
+
self.write("\n")
|
|
921
|
+
else:
|
|
922
|
+
self.write("No breakpoints set.\n", color=ANSIColor.RED)
|
|
923
|
+
for b_type in BREAKPOINT_TYPES:
|
|
924
|
+
b_type.print_usage(self)
|
|
925
|
+
self.write("\n")
|
|
926
|
+
self.write("\nBy default, breakpoints will trigger whenever a matching test is run.\n\n"
|
|
927
|
+
"Prepend a breakpoint with ")
|
|
928
|
+
self.write("!", bold=True)
|
|
929
|
+
self.write(" to only trigger the breakpoint when the test fails.\nFor Example:\n")
|
|
930
|
+
self.write(" b !MIME:application/zip\n", color=ANSIColor.MAGENTA)
|
|
931
|
+
self.write("will only trigger if a test that could match a ZIP file failed.\n\n"
|
|
932
|
+
"Prepend a breakpoint with ")
|
|
933
|
+
self.write("=", bold=True)
|
|
934
|
+
self.write(" to only trigger the breakpoint when the test passes.\n For example:\n")
|
|
935
|
+
self.write(" b =archive:1337\n", color=ANSIColor.MAGENTA)
|
|
936
|
+
self.write("will only trigger if the test on line 1337 of the archive DSL matched.\n\n")
|
|
937
|
+
|
|
938
|
+
@arg_completer(for_command="delete")
|
|
939
|
+
def delete_completer(self, index: int, arg: str, prev_arguments: Iterable[str]):
|
|
940
|
+
if index == 0:
|
|
941
|
+
return SetCompleter(lambda: map(str, range(len(self.breakpoints))))(arg)
|
|
942
|
+
else:
|
|
943
|
+
return []
|
|
944
|
+
|
|
945
|
+
@command(allows_abbreviation=True, help="delete a breakpoint")
|
|
946
|
+
def delete(self, args: str):
|
|
947
|
+
if args:
|
|
948
|
+
try:
|
|
949
|
+
breakpoint_num = int(args)
|
|
950
|
+
except ValueError:
|
|
951
|
+
breakpoint_num = -1
|
|
952
|
+
if not (0 <= breakpoint_num < len(self.breakpoints)):
|
|
953
|
+
print(f"Error: Invalid breakpoint \"{args}\"")
|
|
954
|
+
else:
|
|
955
|
+
b = self.breakpoints[breakpoint_num]
|
|
956
|
+
self.breakpoints = self.breakpoints[:breakpoint_num] + self.breakpoints[breakpoint_num + 1:]
|
|
957
|
+
self.write(f"Deleted {b!s}\n")
|
|
958
|
+
|
|
959
|
+
@arg_completer(for_command="set")
|
|
960
|
+
def set_completer(self, index: int, arg: str, prev_arguments: Iterable[str]):
|
|
961
|
+
if index == 0:
|
|
962
|
+
return SetCompleter(lambda: self.variables_by_name.keys())(arg)
|
|
963
|
+
elif index == 1:
|
|
964
|
+
if prev_arguments[-1] in self.variables_by_name:
|
|
965
|
+
var = self.variables_by_name[prev_arguments[-1]]
|
|
966
|
+
return SetCompleter(lambda: map(str, var.possibilities))(arg)
|
|
967
|
+
return []
|
|
968
|
+
|
|
969
|
+
@command(help="modifies part of the debugger environment")
|
|
970
|
+
def set(self, arguments: str):
|
|
971
|
+
parsed = arguments.strip().split()
|
|
972
|
+
if len(parsed) == 3 and parsed[1].strip() == "=":
|
|
973
|
+
parsed = [parsed[0], parsed[1]]
|
|
974
|
+
if len(parsed) != 2:
|
|
975
|
+
self.write("Usage: ", dim=True)
|
|
976
|
+
self.write("set", bold=True, color=ANSIColor.BLUE)
|
|
977
|
+
self.write(" VARIABLE ", bold=True, color=ANSIColor.GREEN)
|
|
978
|
+
self.write("VALUE\n\n", bold=True, color=ANSIColor.CYAN)
|
|
979
|
+
self.write("Options:\n\n", bold=True)
|
|
980
|
+
for name, var in self.variables_by_name.items():
|
|
981
|
+
self.write(f" {name} ", bold=True, color=ANSIColor.GREEN)
|
|
982
|
+
self.write("[", dim=True)
|
|
983
|
+
for i, value in enumerate(var.possibilities):
|
|
984
|
+
if i > 0:
|
|
985
|
+
self.write("|", dim=True)
|
|
986
|
+
self.write(str(value), bold=True, color=ANSIColor.CYAN)
|
|
987
|
+
self.write("]\n ", dim=True)
|
|
988
|
+
self.write(self.variable_descriptions[name])
|
|
989
|
+
self.write("\n\n")
|
|
990
|
+
elif parsed[0] not in self.variables_by_name:
|
|
991
|
+
self.write("Error: Unknown variable ", bold=True, color=ANSIColor.RED)
|
|
992
|
+
self.write(parsed[0], bold=True)
|
|
993
|
+
self.write("\n")
|
|
994
|
+
else:
|
|
995
|
+
try:
|
|
996
|
+
var = self.variables_by_name[parsed[0]]
|
|
997
|
+
var.value = var.parse(parsed[1])
|
|
998
|
+
except ValueError as e:
|
|
999
|
+
self.write(f"{e!s}\n", bold=True, color=ANSIColor.RED)
|
|
1000
|
+
|
|
1001
|
+
@arg_completer(for_command="show")
|
|
1002
|
+
def show_completer(self, index: int, arg: str, prev_arguments: Iterable[str]):
|
|
1003
|
+
if index == 0:
|
|
1004
|
+
return SetCompleter(lambda: self.variables_by_name.keys())(arg)
|
|
1005
|
+
else:
|
|
1006
|
+
return []
|
|
1007
|
+
|
|
1008
|
+
@command(help="prints part of the debugger environment")
|
|
1009
|
+
def show(self, arguments: str):
|
|
1010
|
+
parsed = arguments.strip().split()
|
|
1011
|
+
if len(parsed) > 2:
|
|
1012
|
+
self.write("Usage: ", dim=True)
|
|
1013
|
+
self.write("show", bold=True, color=ANSIColor.BLUE)
|
|
1014
|
+
self.write(" VARIABLE\n\n", bold=True, color=ANSIColor.GREEN)
|
|
1015
|
+
self.write("Options:\n", bold=True)
|
|
1016
|
+
for name, var in self.variables_by_name.items():
|
|
1017
|
+
self.write(f"\n {name}\n ", bold=True, color=ANSIColor.GREEN)
|
|
1018
|
+
self.write(self.variable_descriptions[name])
|
|
1019
|
+
self.write("\n")
|
|
1020
|
+
elif not parsed:
|
|
1021
|
+
for i, (name, var) in enumerate(self.variables_by_name.items()):
|
|
1022
|
+
if i > 0:
|
|
1023
|
+
self.write("\n")
|
|
1024
|
+
self.write(name, bold=True, color=ANSIColor.GREEN)
|
|
1025
|
+
self.write(" = ", dim=True)
|
|
1026
|
+
self.write(str(var.value), bold=True, color=ANSIColor.CYAN)
|
|
1027
|
+
self.write("\n")
|
|
1028
|
+
self.write(self.variable_descriptions[name])
|
|
1029
|
+
self.write("\n")
|
|
1030
|
+
elif parsed[0] not in self.variables_by_name:
|
|
1031
|
+
self.write("Error: Unknown variable ", bold=True, color=ANSIColor.RED)
|
|
1032
|
+
self.write(parsed[0], bold=True)
|
|
1033
|
+
self.write("\n")
|
|
1034
|
+
else:
|
|
1035
|
+
self.write(parsed[0], bold=True, color=ANSIColor.GREEN)
|
|
1036
|
+
self.write(" = ", dim=True)
|
|
1037
|
+
self.write(str(self.variables_by_name[parsed[0]].value), bold=True, color=ANSIColor.CYAN)
|
|
1038
|
+
self.write("\n")
|
|
1039
|
+
self.write(self.variable_descriptions[parsed[0]])
|