├── .coveragerc ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .pylintrc ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── doc ├── Makefile ├── api.rst ├── conf.py ├── faq.rst ├── index.rst ├── make.bat ├── news.rst └── note.awk ├── dumprar.py ├── etc └── requirements.build.txt ├── rarfile.py ├── setup.cfg ├── setup.py ├── test ├── __init__.py ├── files │ ├── ctime0.rar │ ├── ctime0.rar.exp │ ├── ctime1.rar │ ├── ctime1.rar.exp │ ├── ctime2.rar │ ├── ctime2.rar.exp │ ├── ctime3.rar │ ├── ctime3.rar.exp │ ├── ctime4.rar │ ├── ctime4.rar.exp │ ├── ctime5.rar │ ├── ctime5.rar.exp │ ├── rar15-comment-lock.rar │ ├── rar15-comment-lock.rar.exp │ ├── rar15-comment.rar │ ├── rar15-comment.rar.exp │ ├── rar202-comment-nopsw.rar │ ├── rar202-comment-nopsw.rar.exp │ ├── rar202-comment-psw.rar │ ├── rar202-comment-psw.rar.exp │ ├── rar3-comment-hpsw.rar │ ├── rar3-comment-hpsw.rar.exp │ ├── rar3-comment-plain.rar │ ├── rar3-comment-plain.rar.exp │ ├── rar3-comment-psw.rar │ ├── rar3-comment-psw.rar.exp │ ├── rar3-old.r00 │ ├── rar3-old.r01 │ ├── rar3-old.rar │ ├── rar3-old.rar.exp │ ├── rar3-owner.rar │ ├── rar3-owner.rar.exp │ ├── rar3-readonly-unix.rar │ ├── rar3-readonly-unix.rar.exp │ ├── rar3-readonly-win.rar │ ├── rar3-readonly-win.rar.exp │ ├── rar3-seektest.sfx │ ├── rar3-solid.rar │ ├── rar3-solid.rar.exp │ ├── rar3-subdirs.rar │ ├── rar3-subdirs.rar.exp │ ├── rar3-symlink-unix.rar │ ├── rar3-symlink-unix.rar.exp │ ├── rar3-versions.rar │ ├── rar3-versions.rar.exp │ ├── rar3-vols.part1.rar │ ├── rar3-vols.part1.rar.exp │ ├── rar3-vols.part2.rar │ ├── rar3-vols.part2.rar.exp │ ├── rar3-vols.part3.rar │ ├── rar3-vols.part3.rar.exp │ ├── rar5-blake.rar │ ├── rar5-blake.rar.exp │ ├── rar5-crc.rar │ ├── rar5-crc.rar.exp │ ├── rar5-crc.sfx │ ├── rar5-dups.rar │ ├── rar5-dups.rar.exp │ ├── rar5-hlink.rar │ ├── rar5-hlink.rar.exp │ ├── rar5-hpsw.rar │ ├── rar5-hpsw.rar.exp │ ├── rar5-owner.rar │ ├── rar5-owner.rar.exp │ ├── rar5-psw-blake.rar │ ├── rar5-psw-blake.rar.exp │ ├── rar5-psw.rar │ ├── rar5-psw.rar.exp │ ├── rar5-quick-open.rar │ ├── rar5-quick-open.rar.exp │ ├── rar5-readonly-unix.rar │ ├── rar5-readonly-unix.rar.exp │ ├── rar5-readonly-win.rar │ ├── rar5-readonly-win.rar.exp │ ├── rar5-solid-qo.rar │ ├── rar5-solid-qo.rar.exp │ ├── rar5-solid.rar │ ├── rar5-solid.rar.exp │ ├── rar5-subdirs.rar │ ├── rar5-subdirs.rar.exp │ ├── rar5-symlink-unix.rar │ ├── rar5-symlink-unix.rar.exp │ ├── rar5-symlink-win.rar │ ├── rar5-symlink-win.rar.exp │ ├── rar5-times.rar │ ├── rar5-times.rar.exp │ ├── rar5-times2.rar │ ├── rar5-times2.rar.exp │ ├── rar5-versions.rar │ ├── rar5-versions.rar.exp │ ├── rar5-vols.part1.rar │ ├── rar5-vols.part1.rar.exp │ ├── rar5-vols.part2.rar │ ├── rar5-vols.part2.rar.exp │ ├── rar5-vols.part3.rar │ ├── rar5-vols.part3.rar.exp │ ├── seektest.rar │ ├── seektest.rar.exp │ ├── unicode.rar │ ├── unicode.rar.exp │ ├── unicode2.rar │ └── unicode2.rar.exp ├── run_dump.sh ├── run_dump_all.sh ├── test_api.py ├── test_compat.py ├── test_crypto.py ├── test_extract.py ├── test_format.py ├── test_hashing.py ├── test_korrupt.py ├── test_reading.py ├── test_seek.py ├── test_tool.py └── test_util.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | ^try: 4 | ^except 5 | pragma: no cover 6 | if __name__ 7 | raise NotImplementedError 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # 2 | # https://docs.github.com/en/actions 3 | # https://github.com/actions 4 | # 5 | # https://formulae.brew.sh/ 6 | # https://packages.msys2.org/ 7 | # 8 | 9 | name: CI 10 | 11 | on: 12 | pull_request: {} 13 | push: {} 14 | 15 | jobs: 16 | 17 | lint: 18 | name: "Lint - ${{matrix.pick.OS}} / Python ${{matrix.pick.PY}}" 19 | runs-on: ${{matrix.pick.OS}} 20 | strategy: 21 | matrix: 22 | pick: 23 | - {OS: "ubuntu-latest", PY: "3.10", TOXENV: "lint,docs"} 24 | steps: 25 | - name: "Checkout" 26 | uses: actions/checkout@v4 27 | 28 | - name: "Setup Python ${{matrix.pick.PY}}" 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{matrix.pick.PY}} 32 | 33 | - name: "Install build tools" 34 | run: python -m pip install -r etc/requirements.build.txt --disable-pip-version-check 35 | 36 | - name: "Run tox - ${{matrix.pick.TOXENV}}" 37 | env: 38 | TOXENV: ${{matrix.pick.TOXENV}} 39 | run: | 40 | python -m tox -r -- --color=yes 41 | 42 | test: 43 | name: "Test - ${{matrix.pick.OS}} / ${{matrix.pick.PYNAME}}" 44 | runs-on: ${{matrix.pick.OS}} 45 | strategy: 46 | matrix: 47 | pick: 48 | - {OS: "ubuntu-latest", PY: "3.8", PYNAME: "Python 3.8", TOXENV: "py38"} 49 | - {OS: "ubuntu-latest", PY: "3.9", PYNAME: "Python 3.9", TOXENV: "py39-pycryptodome"} 50 | - {OS: "ubuntu-latest", PY: "3.10", PYNAME: "Python 3.10", TOXENV: "py310-cryptography"} 51 | - {OS: "ubuntu-latest", PY: "3.11", PYNAME: "Python 3.11", TOXENV: "py311-cryptography"} 52 | - {OS: "ubuntu-latest", PY: "3.12", PYNAME: "Python 3.12", TOXENV: "py312-cryptography"} 53 | - {OS: "ubuntu-latest", PY: "pypy3.9", PYNAME: "PyPy3.9", TOXENV: "pypy39-cryptography"} 54 | - {OS: "ubuntu-latest", PY: "pypy3.10", PYNAME: "PyPy3.10", TOXENV: "pypy310-cryptography"} 55 | - {OS: "macos-latest", PY: "3.9", PYNAME: "Python 3.9", TOXENV: "py39-pycryptodome"} 56 | - {OS: "macos-latest", PY: "3.10", PYNAME: "Python 3.10", TOXENV: "py310-cryptography"} 57 | - {OS: "windows-latest", PY: "3.9", PYNAME: "Python 3.9", TOXENV: "py39-cryptography" } 58 | - {OS: "windows-latest", PY: "3.10", PYNAME: "Python 3.10", TOXENV: "py310-cryptography" } 59 | steps: 60 | - name: "Checkout" 61 | uses: actions/checkout@v4 62 | 63 | - name: "Setup ${{matrix.pick.PYNAME}}" 64 | uses: actions/setup-python@v5 65 | with: 66 | python-version: ${{matrix.pick.PY}} 67 | 68 | - name: "Install archivers (linux)" 69 | if: ${{runner.os == 'Linux'}} 70 | run: | 71 | sudo -nH apt-get -qqy install unrar unar libarchive-tools p7zip-rar 72 | unrar 73 | unar -h 74 | bsdtar -h 75 | 7z i 76 | 77 | - name: "Install archivers (macos)" 78 | if: ${{runner.os == 'macOS'}} 79 | run: | 80 | brew install rar unar libarchive sevenzip 81 | unrar 82 | unar -h 83 | bsdtar -h 84 | 7z i 85 | 7zz i 86 | 87 | - name: "Install archivers (windows)" 88 | if: ${{runner.os == 'Windows'}} 89 | shell: cmd 90 | run: | 91 | set "PATH=c:\msys64\usr\bin;%PATH%" 92 | pacman -S --noconfirm --needed bsdtar p7zip 93 | curl -sS -o unrarw64.exe https://www.rarlab.com/rar/unrarw64.exe 94 | 7z x unrarw64.exe 95 | unrar 96 | bsdtar -h 97 | 7z i 98 | 99 | - name: "Install tools" 100 | run: python -m pip install -r etc/requirements.build.txt --disable-pip-version-check 101 | 102 | - name: "Run tox - ${{matrix.pick.TOXENV}} - (linux/macos)" 103 | if: ${{runner.os == 'Linux' || runner.os == 'macOS'}} 104 | env: 105 | TOXENV: ${{matrix.pick.TOXENV}} 106 | run: | 107 | python -m tox -r -- --color=yes -n auto -v 108 | 109 | - name: "Run tox - ${{matrix.pick.TOXENV}} - (windows)" 110 | if: ${{runner.os == 'Windows'}} 111 | env: 112 | TOXENV: ${{matrix.pick.TOXENV}} 113 | shell: cmd 114 | run: | 115 | set "PATH=%PATH%;c:\msys64\usr\bin" 116 | python -m tox -r -- --color=yes -n auto -v 117 | 118 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # 2 | # This runs when version tag is pushed 3 | # 4 | 5 | name: REL 6 | 7 | on: 8 | push: 9 | tags: ["v[0-9]*"] 10 | 11 | jobs: 12 | sdist: 13 | name: "Build source package" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-python@v5 18 | with: {python-version: "3.11"} 19 | - run: python3 -m pip install -r etc/requirements.build.txt --disable-pip-version-check 20 | - run: python3 setup.py sdist 21 | - run: python3 setup.py bdist_wheel 22 | - uses: actions/upload-artifact@v4 23 | with: {name: "dist", path: "dist"} 24 | 25 | publish: 26 | name: "Publish" 27 | runs-on: ubuntu-latest 28 | needs: [sdist] 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-python@v5 32 | with: {python-version: "3.11"} 33 | 34 | - run: python3 -m pip install -r etc/requirements.build.txt --disable-pip-version-check 35 | 36 | - name: "Get files" 37 | uses: actions/download-artifact@v4 38 | with: {name: "dist", path: "dist"} 39 | 40 | - name: "Install pandoc" 41 | run: | 42 | sudo -nH apt-get -u -y install pandoc 43 | pandoc --version 44 | 45 | - name: "Prepare" 46 | run: | 47 | PACKAGE=$(python3 setup.py --name) 48 | VERSION=$(python3 setup.py --version) 49 | TGZ="${PACKAGE}-${VERSION}.tar.gz" 50 | 51 | # default - gh:release, pypi 52 | # PRERELEASE - gh:prerelease, pypi 53 | # DRAFT - gh:draft,prerelease, testpypi 54 | PRERELEASE="false"; DRAFT="false" 55 | case "${VERSION}" in 56 | *[ab]*|*rc*) PRERELEASE="true";; 57 | *dev*) PRERELEASE="true"; DRAFT="true";; 58 | esac 59 | 60 | test "${{github.ref}}" = "refs/tags/v${VERSION}" || { echo "ERR: tag mismatch"; exit 1; } 61 | test -f "dist/${TGZ}" || { echo "ERR: sdist failed"; exit 1; } 62 | echo "PACKAGE=${PACKAGE}" >> $GITHUB_ENV 63 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 64 | echo "TGZ=${TGZ}" >> $GITHUB_ENV 65 | echo "PRERELEASE=${PRERELEASE}" >> $GITHUB_ENV 66 | echo "DRAFT=${DRAFT}" >> $GITHUB_ENV 67 | mkdir -p tmp 68 | make -s shownote > tmp/note.md 69 | cat tmp/note.md 70 | ls -l dist 71 | 72 | - name: "Create Github release" 73 | env: 74 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 75 | run: | 76 | title="${PACKAGE} v${VERSION}" 77 | ghf="--notes-file=./tmp/note.md" 78 | if test "${DRAFT}" = "true"; then ghf="${ghf} --draft"; fi 79 | if test "${PRERELEASE}" = "true"; then ghf="${ghf} --prerelease"; fi 80 | gh release create "v${VERSION}" "dist/${TGZ}" --title="${title}" ${ghf} 81 | 82 | - name: "Upload to PYPI" 83 | id: pypi_upload 84 | env: 85 | PYPI_TOKEN: ${{secrets.PYPI_TOKEN}} 86 | PYPI_TEST_TOKEN: ${{secrets.PYPI_TEST_TOKEN}} 87 | run: | 88 | ls -l dist 89 | if test "${DRAFT}" = "false"; then 90 | python -m twine upload -u __token__ -p ${PYPI_TOKEN} \ 91 | --repository pypi --disable-progress-bar dist/* 92 | else 93 | python -m twine upload -u __token__ -p ${PYPI_TEST_TOKEN} \ 94 | --repository testpypi --disable-progress-bar dist/* 95 | fi 96 | 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | *.rar.py* 4 | *.rar.jy* 5 | *.class 6 | *.orig 7 | *.log 8 | *.diffs 9 | *.egg-info 10 | __pycache__ 11 | .coverage 12 | .tox 13 | .mypy_cache 14 | .pytype 15 | dist 16 | MANIFEST 17 | build 18 | tmp 19 | doc/_build 20 | doc/html 21 | cover 22 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS,tmp,dist 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 21 | # number of processors available to use. 22 | jobs=1 23 | 24 | # Control the amount of potential inferred values when inferring a single 25 | # object. This can help the performance when dealing with large functions or 26 | # complex, nested conditions. 27 | limit-inference-results=100 28 | 29 | # List of plugins (as comma separated values of python modules names) to load, 30 | # usually to register additional checkers. 31 | load-plugins= 32 | 33 | # Pickle collected data for later comparisons. 34 | persistent=yes 35 | 36 | # Specify a configuration file. 37 | #rcfile= 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=bare-except, 64 | broad-except, 65 | consider-using-f-string, 66 | consider-using-in, 67 | consider-using-ternary, 68 | consider-using-with, 69 | fixme, 70 | global-statement, 71 | invalid-name, 72 | missing-docstring, 73 | no-else-raise, 74 | no-else-return, 75 | trailing-newlines, 76 | unused-argument, 77 | unused-variable, 78 | protected-access, 79 | ungrouped-imports, 80 | chained-comparison, 81 | len-as-condition, 82 | redefined-builtin, 83 | import-outside-toplevel, 84 | duplicate-code, 85 | consider-using-min-builtin, 86 | unnecessary-pass 87 | 88 | # Enable the message, report, category or checker with the given id(s). You can 89 | # either give multiple identifier separated by comma (,) or put this option 90 | # multiple time (only on the command line, not in the configuration file where 91 | # it should appear only once). See also the "--disable" option for examples. 92 | enable=c-extension-no-member 93 | 94 | 95 | [REPORTS] 96 | 97 | # Python expression which should return a note less than 10 (10 is the highest 98 | # note). You have access to the variables errors warning, statement which 99 | # respectively contain the number of errors / warnings messages and the total 100 | # number of statements analyzed. This is used by the global evaluation report 101 | # (RP0004). 102 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 103 | 104 | # Template used to display messages. This is a python new-style format string 105 | # used to format the message information. See doc for all details. 106 | #msg-template= 107 | 108 | # Set the output format. Available formats are text, parseable, colorized, json 109 | # and msvs (visual studio). You can also give a reporter class, e.g. 110 | # mypackage.mymodule.MyReporterClass. 111 | output-format=colorized 112 | 113 | # Tells whether to display a full report or only the messages. 114 | reports=no 115 | 116 | # Activate the evaluation score. 117 | score=no 118 | 119 | 120 | [REFACTORING] 121 | 122 | # Maximum number of nested blocks for function / method body 123 | max-nested-blocks=10 124 | 125 | # Complete name of functions that never returns. When checking for 126 | # inconsistent-return-statements if a never returning function is called then 127 | # it will be considered as an explicit return statement and no message will be 128 | # printed. 129 | never-returning-functions=sys.exit 130 | 131 | 132 | [LOGGING] 133 | 134 | # Format style used to check logging format string. `old` means using % 135 | # formatting, while `new` is for `{}` formatting. 136 | logging-format-style=old 137 | 138 | # Logging modules to check that the string format arguments are in logging 139 | # function parameter format. 140 | logging-modules=logging 141 | 142 | 143 | [MISCELLANEOUS] 144 | 145 | # List of note tags to take in consideration, separated by a comma. 146 | notes=FIXME, 147 | XXX, 148 | TODO 149 | 150 | 151 | [SPELLING] 152 | 153 | # Limits count of emitted suggestions for spelling mistakes. 154 | max-spelling-suggestions=4 155 | 156 | # Spelling dictionary name. Available dictionaries: none. To make it working 157 | # install python-enchant package.. 158 | #spelling-dict=en_US 159 | 160 | # List of comma separated words that should not be checked. 161 | spelling-ignore-words=usr,bin,env 162 | 163 | # A path to a file that contains private dictionary; one word per line. 164 | spelling-private-dict-file=.local.dict 165 | 166 | # Tells whether to store unknown words to indicated private dictionary in 167 | # --spelling-private-dict-file option instead of raising a message. 168 | spelling-store-unknown-words=no 169 | 170 | 171 | [BASIC] 172 | 173 | # Naming style matching correct argument names. 174 | argument-naming-style=snake_case 175 | 176 | # Regular expression matching correct argument names. Overrides argument- 177 | # naming-style. 178 | #argument-rgx= 179 | 180 | # Naming style matching correct attribute names. 181 | attr-naming-style=snake_case 182 | 183 | # Regular expression matching correct attribute names. Overrides attr-naming- 184 | # style. 185 | #attr-rgx= 186 | 187 | # Bad variable names which should always be refused, separated by a comma. 188 | bad-names=foo, 189 | bar, 190 | baz, 191 | toto, 192 | tutu, 193 | tata 194 | 195 | # Naming style matching correct class attribute names. 196 | class-attribute-naming-style=any 197 | 198 | # Regular expression matching correct class attribute names. Overrides class- 199 | # attribute-naming-style. 200 | #class-attribute-rgx= 201 | 202 | # Naming style matching correct class names. 203 | class-naming-style=PascalCase 204 | 205 | # Regular expression matching correct class names. Overrides class-naming- 206 | # style. 207 | #class-rgx= 208 | 209 | # Naming style matching correct constant names. 210 | const-naming-style=UPPER_CASE 211 | 212 | # Regular expression matching correct constant names. Overrides const-naming- 213 | # style. 214 | #const-rgx= 215 | 216 | # Minimum line length for functions/classes that require docstrings, shorter 217 | # ones are exempt. 218 | docstring-min-length=-1 219 | 220 | # Naming style matching correct function names. 221 | function-naming-style=snake_case 222 | 223 | # Regular expression matching correct function names. Overrides function- 224 | # naming-style. 225 | #function-rgx= 226 | 227 | # Good variable names which should always be accepted, separated by a comma. 228 | good-names=i, 229 | j, 230 | k, 231 | ex, 232 | Run, 233 | _ 234 | 235 | # Include a hint for the correct naming format with invalid-name. 236 | include-naming-hint=no 237 | 238 | # Naming style matching correct inline iteration names. 239 | inlinevar-naming-style=any 240 | 241 | # Regular expression matching correct inline iteration names. Overrides 242 | # inlinevar-naming-style. 243 | #inlinevar-rgx= 244 | 245 | # Naming style matching correct method names. 246 | method-naming-style=snake_case 247 | 248 | # Regular expression matching correct method names. Overrides method-naming- 249 | # style. 250 | #method-rgx= 251 | 252 | # Naming style matching correct module names. 253 | module-naming-style=snake_case 254 | 255 | # Regular expression matching correct module names. Overrides module-naming- 256 | # style. 257 | #module-rgx= 258 | 259 | # Colon-delimited sets of names that determine each other's naming style when 260 | # the name regexes allow several styles. 261 | name-group= 262 | 263 | # Regular expression which should only match function or class names that do 264 | # not require a docstring. 265 | no-docstring-rgx=^_ 266 | 267 | # List of decorators that produce properties, such as abc.abstractproperty. Add 268 | # to this list to register other decorators that produce valid properties. 269 | # These decorators are taken in consideration only for invalid-name. 270 | property-classes=abc.abstractproperty 271 | 272 | # Naming style matching correct variable names. 273 | variable-naming-style=snake_case 274 | 275 | # Regular expression matching correct variable names. Overrides variable- 276 | # naming-style. 277 | #variable-rgx= 278 | 279 | 280 | [STRING] 281 | 282 | # This flag controls whether the implicit-str-concat-in-sequence should 283 | # generate a warning on implicit string concatenation in sequences defined over 284 | # several lines. 285 | check-str-concat-over-line-jumps=no 286 | 287 | 288 | [SIMILARITIES] 289 | 290 | # Ignore comments when computing similarities. 291 | ignore-comments=yes 292 | 293 | # Ignore docstrings when computing similarities. 294 | ignore-docstrings=yes 295 | 296 | # Ignore imports when computing similarities. 297 | ignore-imports=no 298 | 299 | # Minimum lines number of a similarity. 300 | min-similarity-lines=4 301 | 302 | 303 | [VARIABLES] 304 | 305 | # List of additional names supposed to be defined in builtins. Remember that 306 | # you should avoid defining new builtins when possible. 307 | additional-builtins= 308 | 309 | # Tells whether unused global variables should be treated as a violation. 310 | allow-global-unused-variables=yes 311 | 312 | # List of strings which can identify a callback function by name. A callback 313 | # name must start or end with one of those strings. 314 | callbacks=cb_, 315 | _cb 316 | 317 | # A regular expression matching the name of dummy variables (i.e. expected to 318 | # not be used). 319 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 320 | 321 | # Argument names that match this expression will be ignored. Default to name 322 | # with leading underscore. 323 | ignored-argument-names=_.*|^ignored_|^unused_ 324 | 325 | # Tells whether we should check for unused import in __init__ files. 326 | init-import=no 327 | 328 | # List of qualified module names which can have objects that can redefine 329 | # builtins. 330 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 331 | 332 | 333 | [TYPECHECK] 334 | 335 | # List of decorators that produce context managers, such as 336 | # contextlib.contextmanager. Add to this list to register other decorators that 337 | # produce valid context managers. 338 | contextmanager-decorators=contextlib.contextmanager 339 | 340 | # List of members which are set dynamically and missed by pylint inference 341 | # system, and so shouldn't trigger E1101 when accessed. Python regular 342 | # expressions are accepted. 343 | generated-members= 344 | 345 | # Tells whether missing members accessed in mixin class should be ignored. A 346 | # mixin class is detected if its name ends with "mixin" (case insensitive). 347 | ignore-mixin-members=yes 348 | 349 | # Tells whether to warn about missing members when the owner of the attribute 350 | # is inferred to be None. 351 | ignore-none=yes 352 | 353 | # This flag controls whether pylint should warn about no-member and similar 354 | # checks whenever an opaque object is returned when inferring. The inference 355 | # can return multiple potential results while evaluating a Python object, but 356 | # some branches might not be evaluated, which results in partial inference. In 357 | # that case, it might be useful to still emit no-member and other checks for 358 | # the rest of the inferred objects. 359 | ignore-on-opaque-inference=yes 360 | 361 | # List of class names for which member attributes should not be checked (useful 362 | # for classes with dynamically set attributes). This supports the use of 363 | # qualified names. 364 | ignored-classes=optparse.Values,thread._local,_thread._local 365 | 366 | # List of module names for which member attributes should not be checked 367 | # (useful for modules/projects where namespaces are manipulated during runtime 368 | # and thus existing member attributes cannot be deduced by static analysis. It 369 | # supports qualified module names, as well as Unix pattern matching. 370 | ignored-modules= 371 | 372 | # Show a hint with possible names when a member name was not found. The aspect 373 | # of finding the hint is based on edit distance. 374 | missing-member-hint=yes 375 | 376 | # The minimum edit distance a name should have in order to be considered a 377 | # similar match for a missing member name. 378 | missing-member-hint-distance=1 379 | 380 | # The total number of similar names that should be taken in consideration when 381 | # showing a hint for a missing member. 382 | missing-member-max-choices=1 383 | 384 | 385 | [FORMAT] 386 | 387 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 388 | expected-line-ending-format=LF 389 | 390 | # Regexp for a line that is allowed to be longer than the limit. 391 | ignore-long-lines=^\s*(# )??$ 392 | 393 | # Number of spaces of indent required inside a hanging or continued line. 394 | indent-after-paren=4 395 | 396 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 397 | # tab). 398 | indent-string=' ' 399 | 400 | # Maximum number of characters on a single line. 401 | max-line-length=160 402 | 403 | # Maximum number of lines in a module. 404 | max-module-lines=10000 405 | 406 | # Allow the body of a class to be on the same line as the declaration if body 407 | # contains single statement. 408 | single-line-class-stmt=no 409 | 410 | # Allow the body of an if to be on the same line as the test if there is no 411 | # else. 412 | single-line-if-stmt=no 413 | 414 | 415 | [CLASSES] 416 | 417 | # List of method names used to declare (i.e. assign) instance attributes. 418 | defining-attr-methods=__init__, 419 | __new__, 420 | setUp 421 | 422 | # List of member names, which should be excluded from the protected access 423 | # warning. 424 | exclude-protected=_asdict, 425 | _fields, 426 | _replace, 427 | _source, 428 | _make 429 | 430 | # List of valid names for the first argument in a class method. 431 | valid-classmethod-first-arg=cls 432 | 433 | # List of valid names for the first argument in a metaclass class method. 434 | valid-metaclass-classmethod-first-arg=cls 435 | 436 | 437 | [DESIGN] 438 | 439 | # Maximum number of arguments for function / method. 440 | max-args=15 441 | 442 | # Maximum number of attributes for a class (see R0902). 443 | max-attributes=17 444 | 445 | # Maximum number of boolean expressions in an if statement. 446 | max-bool-expr=5 447 | 448 | # Maximum number of branch for function / method body. 449 | max-branches=50 450 | 451 | # Maximum number of locals for function / method body. 452 | max-locals=45 453 | 454 | # Maximum number of parents for a class (see R0901). 455 | max-parents=7 456 | 457 | # Maximum number of public methods for a class (see R0904). 458 | max-public-methods=420 459 | 460 | # Maximum number of return / yield for function / method body. 461 | max-returns=16 462 | 463 | # Maximum number of statements in function / method body. 464 | max-statements=150 465 | 466 | # Minimum number of public methods for a class (see R0903). 467 | min-public-methods=0 468 | 469 | 470 | [IMPORTS] 471 | 472 | # Allow wildcard imports from modules that define __all__. 473 | allow-wildcard-with-all=no 474 | 475 | # Analyse import fallback blocks. This can be used to support both Python 2 and 476 | # 3 compatible code, which means that the block might have code that exists 477 | # only in one or another interpreter, leading to false positives when analysed. 478 | analyse-fallback-blocks=no 479 | 480 | # Deprecated modules which should not be used, separated by a comma. 481 | deprecated-modules=optparse,tkinter.tix 482 | 483 | # Create a graph of external dependencies in the given file (report RP0402 must 484 | # not be disabled). 485 | ext-import-graph= 486 | 487 | # Create a graph of every (i.e. internal and external) dependencies in the 488 | # given file (report RP0402 must not be disabled). 489 | import-graph= 490 | 491 | # Create a graph of internal dependencies in the given file (report RP0402 must 492 | # not be disabled). 493 | int-import-graph= 494 | 495 | # Force import order to recognize a module as part of the standard 496 | # compatibility libraries. 497 | known-standard-library= 498 | 499 | # Force import order to recognize a module as part of a third party library. 500 | known-third-party=enchant 501 | 502 | 503 | [EXCEPTIONS] 504 | 505 | # Exceptions that will emit a warning when being caught. Defaults to 506 | # "BaseException, Exception". 507 | overgeneral-exceptions=builtins.BaseException, 508 | builtins.Exception 509 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | build: 6 | os: ubuntu-lts-latest 7 | tools: 8 | python: "3.12" 9 | sphinx: 10 | configuration: doc/conf.py 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2005-2024 Marko Kreen 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst Makefile MANIFEST.in LICENSE dumprar.py 2 | include doc/*.rst doc/*.awk doc/Makefile doc/conf.py doc/make.bat 3 | include test/Makefile test/*.py test/*.sh 4 | include test/files/*.rar test/files/*.r[0-9][0-9] test/files/*.exp test/files/*.sfx 5 | include tox.ini .pylintrc .coveragerc .github/*/*.yml etc/*.txt 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | prefix = /usr/local 3 | 4 | REPO = https://github.com/markokr/rarfile 5 | NEWS = doc/news.rst 6 | 7 | PACKAGE = $(shell python3 setup.py --name) 8 | VERSION = $(shell python3 setup.py --version) 9 | RXVERSION = $(shell python3 setup.py --version | sed 's/\./[.]/g') 10 | TAG = v$(VERSION) 11 | TGZ = $(PACKAGE)-$(VERSION).tar.gz 12 | WHEEL = $(PACKAGE)-$(VERSION)-py3-none-any.whl 13 | URL = $(REPO)/releases/download/$(TAG) 14 | 15 | all: 16 | pyflakes rarfile.py 17 | tox -e lint 18 | tox -e py38-cryptography -- -n auto 19 | 20 | install: 21 | python setup.py install --prefix=$(prefix) 22 | 23 | clean: 24 | rm -rf __pycache__ build dist .tox 25 | rm -f *.pyc MANIFEST *.orig *.rej *.html *.class test/*.pyc 26 | rm -rf doc/_build doc/_static doc/_templates doc/html 27 | rm -rf .coverage cover* 28 | rm -rf *.egg-info 29 | rm -f test/files/*.rar.[pjt]* *.diffs 30 | 31 | toxclean: clean 32 | rm -rf .tox 33 | 34 | ack: 35 | for fn in test/files/*.py38-cryptography; do \ 36 | cp $$fn `echo $$fn | sed 's/[.]py.*/.exp/'` || exit 1; \ 37 | done 38 | 39 | prepare: 40 | @echo "Checking version - $(VERSION)" 41 | @grep -qE '^\w+ $(RXVERSION)\b' $(NEWS) \ 42 | || { echo "Version '$(VERSION)' not in $(NEWS)"; exit 1; } 43 | @echo "Checking git repo" 44 | @git diff --stat --exit-code || { echo "ERROR: Unclean repo"; exit 1; } 45 | 46 | release: prepare 47 | git tag $(TAG) 48 | git push github $(TAG):$(TAG) 49 | 50 | upload: 51 | mkdir -p dist && rm -f dist/* 52 | cd dist && wget -q $(URL)/$(TGZ) 53 | cd dist && wget -q $(URL)/$(WHEEL) 54 | tar tvf dist/$(TGZ) 55 | unzip -t dist/$(WHEEL) 56 | twine upload dist/$(TGZ) dist/$(WHEEL) 57 | 58 | shownote: 59 | awk -v VER="$(VERSION)" -f doc/note.awk $(NEWS) \ 60 | | pandoc -f rst -t gfm --wrap=none 61 | 62 | unrelease: 63 | git push github :$(TAG) 64 | git tag -d $(TAG) 65 | 66 | dist-test: 67 | python3 setup.py sdist 68 | rm -rf $(PACKAGE)-$(VERSION) 69 | tar xf dist/$(TGZ) 70 | cd $(PACKAGE)-$(VERSION) && tox 71 | 72 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | rarfile - RAR archive reader for Python 3 | ======================================= 4 | 5 | This is Python module for RAR_ archive reading. 6 | The interface follows the style of zipfile_. 7 | Licensed under ISC_ license. 8 | 9 | Features: 10 | 11 | * Supports both RAR3 and RAR5 format archives. 12 | * Supports multi volume archives. 13 | * Supports Unicode filenames. 14 | * Supports password-protected archives. 15 | * Supports archive and file comments. 16 | * Archive parsing and non-compressed files are handled in pure Python code. 17 | * Compressed files are extracted by executing external tool: 18 | unrar_ (preferred), unar_, 7zip_ or bsdtar_. 19 | * Works with Python 3.6+. 20 | 21 | .. _RAR: https://en.wikipedia.org/wiki/RAR_%28file_format%29 22 | .. _zipfile: https://docs.python.org/3/library/zipfile.html 23 | .. _ISC: https://en.wikipedia.org/wiki/ISC_license 24 | .. _bsdtar: https://github.com/libarchive/libarchive 25 | .. _unrar: https://www.rarlab.com/ 26 | .. _unar: https://theunarchiver.com/command-line 27 | .. _7zip: https://www.7-zip.org/ 28 | 29 | Backends: 30 | 31 | +-------------+----------------------+-----------------------------------------------------+ 32 | | Backend | Status | Notes | 33 | +=============+======================+=====================================================+ 34 | | unrar_ | Supported | * Recommended: full format support. | 35 | | | | * Non-free software, but free to use. | 36 | +-------------+----------------------+-----------------------------------------------------+ 37 | | unar_ | Supported | * Not usable on Windows: last build is from 2013 | 38 | | | | (v1.8.1) that does support output to stdout. | 39 | | | | * Does not support RAR2 locked files. | 40 | | | | * Does not support RAR5 Blake2 hash checking. | 41 | +-------------+----------------------+-----------------------------------------------------+ 42 | | 7zip_ | Supported | * RAR support not available on Debian/Ubuntu repos. | 43 | +-------------+----------------------+-----------------------------------------------------+ 44 | | p7zip_ | Supported | * Unmaintained? | 45 | | | | * Requires ``p7zip-rar`` package on Debian/Ubuntu. | 46 | +-------------+----------------------+-----------------------------------------------------+ 47 | | bsdtar_ | Supported | * Not recommended: limited RAR format support. | 48 | | | | * Does not support multi-volume archives. | 49 | | | | * Does not support solid archives. | 50 | | | | * Does not support password-protected archives. | 51 | | | | * Does not support RARVM-based compression filters. | 52 | +-------------+----------------------+-----------------------------------------------------+ 53 | | unrar-free_ | Supported | * Supports output to stdout (v0.2.0). | 54 | | | | * Based on libarchive so similar format support | 55 | | | | as ``bsdtar`` but supports multi-volume (v0.3.0). | 56 | +-------------+----------------------+-----------------------------------------------------+ 57 | 58 | .. _p7zip: https://sourceforge.net/projects/p7zip/ 59 | .. _unrar-free: https://gitlab.com/bgermann/unrar-free 60 | 61 | Links: 62 | 63 | - `Documentation`_ 64 | - `Downloads`_ 65 | - `Git`_ repo 66 | 67 | .. _Git: https://github.com/markokr/rarfile 68 | .. _Downloads: https://pypi.org/project/rarfile/#files 69 | .. _Documentation: https://rarfile.readthedocs.io/ 70 | 71 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/RarFile.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/RarFile.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/RarFile" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/RarFile" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/api.rst: -------------------------------------------------------------------------------- 1 | 2 | rarfile API documentation 3 | ========================= 4 | 5 | .. contents:: Table Of Contents 6 | 7 | Introduction 8 | ------------ 9 | 10 | .. automodule:: rarfile 11 | 12 | RarFile class 13 | ------------- 14 | 15 | .. autoclass:: RarFile 16 | :members: 17 | :special-members: __enter__, __exit__, __iter__ 18 | 19 | RarInfo class 20 | ------------- 21 | 22 | .. autoclass:: RarInfo 23 | :members: 24 | 25 | RarExtFile class 26 | ---------------- 27 | 28 | .. autoclass:: RarExtFile 29 | :show-inheritance: 30 | :members: 31 | :inherited-members: 32 | :exclude-members: truncate, flush 33 | 34 | nsdatetime class 35 | ---------------- 36 | 37 | .. autoclass:: nsdatetime 38 | :show-inheritance: 39 | :members: 40 | 41 | Functions 42 | --------- 43 | 44 | .. autofunction:: is_rarfile 45 | .. autofunction:: is_rarfile_sfx 46 | 47 | Constants 48 | --------- 49 | 50 | .. autodata:: RAR_M0 51 | .. autodata:: RAR_M1 52 | .. autodata:: RAR_M2 53 | .. autodata:: RAR_M3 54 | .. autodata:: RAR_M4 55 | .. autodata:: RAR_M5 56 | 57 | .. autodata:: RAR_OS_WIN32 58 | .. autodata:: RAR_OS_UNIX 59 | .. autodata:: RAR_OS_MACOS 60 | .. autodata:: RAR_OS_BEOS 61 | .. autodata:: RAR_OS_OS2 62 | .. autodata:: RAR_OS_MSDOS 63 | 64 | Warnings 65 | -------- 66 | 67 | .. autoclass:: UnsupportedWarning 68 | 69 | Exceptions 70 | ---------- 71 | 72 | .. autoclass:: Error 73 | .. autoclass:: BadRarFile 74 | .. autoclass:: NotRarFile 75 | .. autoclass:: BadRarName 76 | .. autoclass:: NoRarEntry 77 | .. autoclass:: PasswordRequired 78 | .. autoclass:: NeedFirstVolume 79 | .. autoclass:: NoCrypto 80 | .. autoclass:: RarExecError 81 | .. autoclass:: RarWarning 82 | .. autoclass:: RarFatalError 83 | .. autoclass:: RarCRCError 84 | .. autoclass:: RarLockedArchiveError 85 | .. autoclass:: RarWriteError 86 | .. autoclass:: RarOpenError 87 | .. autoclass:: RarUserError 88 | .. autoclass:: RarMemoryError 89 | .. autoclass:: RarCreateError 90 | .. autoclass:: RarNoFilesError 91 | .. autoclass:: RarUserBreak 92 | .. autoclass:: RarWrongPassword 93 | .. autoclass:: RarUnknownError 94 | .. autoclass:: RarSignalExit 95 | .. autoclass:: RarCannotExec 96 | 97 | 98 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # RarFile documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Mar 24 13:29:46 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os, os.path 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 20 | import rarfile 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | needs_sphinx = '1.3' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon'] 30 | 31 | autodoc_member_order = 'bysource' 32 | autoclass_content = 'class' 33 | autodoc_default_options = {} 34 | 35 | intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | #source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'RarFile' 51 | copyright = u'' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = rarfile.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = rarfile.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | #language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ['_build', 'html'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all documents. 77 | #default_role = None 78 | 79 | # If true, '()' will be appended to :func: etc. cross-reference text. 80 | #add_function_parentheses = True 81 | 82 | # If true, the current module name will be prepended to all description 83 | # unit titles (such as .. function::). 84 | #add_module_names = True 85 | 86 | # If true, sectionauthor and moduleauthor directives will be shown in the 87 | # output. They are ignored by default. 88 | #show_authors = False 89 | 90 | # The name of the Pygments (syntax highlighting) style to use. 91 | pygments_style = 'sphinx' 92 | 93 | # A list of ignored prefixes for module index sorting. 94 | #modindex_common_prefix = [] 95 | 96 | 97 | # -- Options for HTML output --------------------------------------------------- 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | html_theme = 'default' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | #html_theme_options = {} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | html_title = "RarFile documentation" 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | #html_short_title = None 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | #html_logo = None 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | #html_static_path = ['_static'] 131 | 132 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 133 | # using the given strftime format. 134 | #html_last_updated_fmt = '%b %d, %Y' 135 | 136 | # If true, SmartyPants will be used to convert quotes and dashes to 137 | # typographically correct entities. 138 | #html_use_smartypants = True 139 | 140 | # Custom sidebar templates, maps document names to template names. 141 | #html_sidebars = {} 142 | 143 | # Additional templates that should be rendered to pages, maps page names to 144 | # template names. 145 | #html_additional_pages = {} 146 | 147 | # If false, no module index is generated. 148 | #html_domain_indices = True 149 | 150 | # If false, no index is generated. 151 | #html_use_index = True 152 | 153 | # If true, the index is split into individual pages for each letter. 154 | #html_split_index = False 155 | 156 | # If true, links to the reST sources are added to the pages. 157 | html_show_sourcelink = False 158 | 159 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 160 | html_show_sphinx = False 161 | 162 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 163 | html_show_copyright = False 164 | 165 | # If true, an OpenSearch description file will be output, and all pages will 166 | # contain a tag referring to it. The value of this option must be the 167 | # base URL from which the finished HTML is served. 168 | #html_use_opensearch = '' 169 | 170 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 171 | #html_file_suffix = None 172 | 173 | # Output file base name for HTML help builder. 174 | #htmlhelp_basename = 'RarFiledoc' 175 | 176 | 177 | # -- Options for LaTeX output -------------------------------------------------- 178 | 179 | latex_elements = { 180 | # The paper size ('letterpaper' or 'a4paper'). 181 | #'papersize': 'letterpaper', 182 | 183 | # The font size ('10pt', '11pt' or '12pt'). 184 | #'pointsize': '10pt', 185 | 186 | # Additional stuff for the LaTeX preamble. 187 | #'preamble': '', 188 | } 189 | 190 | # Grouping the document tree into LaTeX files. List of tuples 191 | # (source start file, target name, title, author, documentclass [howto/manual]). 192 | latex_documents = [ 193 | ('index', 'RarFile.tex', u'RarFile Documentation', 194 | u'Marko Kreen', 'manual'), 195 | ] 196 | 197 | # The name of an image file (relative to this directory) to place at the top of 198 | # the title page. 199 | #latex_logo = None 200 | 201 | # For "manual" documents, if this is true, then toplevel headings are parts, 202 | # not chapters. 203 | #latex_use_parts = False 204 | 205 | # If true, show page references after internal links. 206 | #latex_show_pagerefs = False 207 | 208 | # If true, show URL addresses after external links. 209 | #latex_show_urls = False 210 | 211 | # Documents to append as an appendix to all manuals. 212 | #latex_appendices = [] 213 | 214 | # If false, no module index is generated. 215 | #latex_domain_indices = True 216 | 217 | 218 | # -- Options for manual page output -------------------------------------------- 219 | 220 | # One entry per manual page. List of tuples 221 | # (source start file, name, description, authors, manual section). 222 | #man_pages = [ 223 | # ('index', 'rarfile', u'RarFile Documentation', 224 | # [u'Marko Kreen'], 1) 225 | #] 226 | 227 | # If true, show URL addresses after external links. 228 | #man_show_urls = False 229 | 230 | 231 | # -- Options for Texinfo output ------------------------------------------------ 232 | 233 | # Grouping the document tree into Texinfo files. List of tuples 234 | # (source start file, target name, title, author, 235 | # dir menu entry, description, category) 236 | texinfo_documents = [ 237 | ('index', 'RarFile', u'RarFile Documentation', 238 | u'Marko Kreen', 'RarFile', 'One line description of project.', 239 | 'Miscellaneous'), 240 | ] 241 | 242 | # Documents to append as an appendix to all manuals. 243 | #texinfo_appendices = [] 244 | 245 | # If false, no module index is generated. 246 | #texinfo_domain_indices = True 247 | 248 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 249 | #texinfo_show_urls = 'footnote' 250 | -------------------------------------------------------------------------------- /doc/faq.rst: -------------------------------------------------------------------------------- 1 | 2 | rarfile FAQ 3 | =========== 4 | 5 | .. contents:: Table of Contents 6 | 7 | What are the dependencies? 8 | -------------------------- 9 | 10 | It depends on ``unrar`` command-line utility to do the actual decompression. 11 | Note that by default it expect it to be in ``PATH``. If unrar 12 | launching fails, you need to fix this. 13 | 14 | Alternatively, :mod:`rarfile` can also use either unar_ from TheUnarchiver_ 15 | or bsdtar_ from libarchive_ as decompression backend. From those 16 | ``unar`` is preferred as ``bsdtar`` has very limited support for RAR archives. 17 | 18 | .. _unar: https://theunarchiver.com/command-line 19 | .. _TheUnarchiver: https://theunarchiver.com/ 20 | .. _bsdtar: https://github.com/libarchive/libarchive/wiki/ManPageBsdtar1 21 | .. _libarchive: https://www.libarchive.org/ 22 | 23 | It depends on cryptography_ or PyCryptodome_ modules to process 24 | archives with password-protected headers. 25 | 26 | .. _cryptography: https://pypi.org/project/cryptography/ 27 | .. _PyCryptodome: https://pypi.org/project/pycryptodome/ 28 | 29 | Does it parse ``unrar`` output to get archive contents? 30 | ------------------------------------------------------- 31 | 32 | No, :mod:`rarfile` parses RAR structure in Python code. Also it can 33 | read uncompressed files from archive without external utility. 34 | 35 | Will rarfile support wrapping unrarlib/unrar.dll/unrar.so in the future? 36 | ------------------------------------------------------------------------ 37 | 38 | No. The current architecture - parsing in Python and decompression with 39 | command line tools work well across all interesting operating systems 40 | (Windows/Linux/MacOS), wrapping a library does not bring any advantages. 41 | 42 | Simple execution of command-line tools is also legally simpler situation 43 | than linking with external library. 44 | 45 | How can I get it work on Windows? 46 | --------------------------------- 47 | 48 | On Windows the ``unrar.exe`` is not in ``PATH`` so simple ``Popen("unrar ..")`` does not work. 49 | Solutions: 50 | 51 | 1. Add location of ``unrar.exe`` to PATH. 52 | 2. Copy ``unrar.exe`` to system directory that is in PATH, eg. ``C:\Windows``. 53 | 54 | It can be tested by simply opening command-line console and running ``unrar``. 55 | 56 | How can I get it work on Linux/MacOS? 57 | ------------------------------------- 58 | 59 | It fails because ``unrar`` is not installed or not in PATH. 60 | 61 | 1. Install ``unrar``. 62 | 2. Make sure the location is in PATH. 63 | 64 | It can be tested by simply opening command-line console and running ``unrar``. 65 | 66 | Instead ``unrar`` it might be preferable to install ``unar``. 67 | 68 | How to avoid the need for user to manually install rarfile/unrar? 69 | ----------------------------------------------------------------- 70 | 71 | Include ``rarfile.py`` and/or ``unrar`` (or ``unar``) with your application. 72 | 73 | Will it support creating RAR archives? 74 | -------------------------------------- 75 | 76 | No. RARLAB_ is not interested in RAR becoming open format 77 | and specifically discourages writing RAR creation software. 78 | 79 | In the meantime use either Zip_ (better compatibility) or 7z_ (better compression) 80 | format for your own archives. 81 | 82 | .. _RARLAB: https://www.rarlab.com/ 83 | .. _Zip: https://en.wikipedia.org/wiki/ZIP_%28file_format%29 84 | .. _7z: https://en.wikipedia.org/wiki/7z 85 | 86 | What is the USE_EXTRACT_HACK? 87 | ----------------------------- 88 | 89 | RarFile uses ``unrar`` to extract compressed files. But when extracting 90 | single file from archive containing many entries, ``unrar`` needs to parse 91 | whole archive until it finds the right entry. This makes random-access 92 | to entries slow. To avoid that, RarFile remembers location of compressed 93 | data for each entry and on read it copies it to temporary archive containing 94 | only data for that one file, thus making ``unrar`` fast. 95 | 96 | The logic is only activated for entries smaller than :data:`rarfile.HACK_SIZE_LIMIT` 97 | (20M by default). Bigger files are accessed directly from RAR. 98 | 99 | Note - it only works for non-solid archives. So if you care about 100 | random access to files in your archive, do not create solid archives. 101 | 102 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: ../README.rst 3 | 4 | Documentation: 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | API Documentation 10 | FAQs 11 | Release News 12 | 13 | 14 | Indices and tables 15 | ------------------ 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | 21 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\RarFile.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\RarFile.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /doc/news.rst: -------------------------------------------------------------------------------- 1 | 2 | rarfile history 3 | =============== 4 | 5 | .. py:currentmodule:: rarfile 6 | 7 | Version 4.2 (2024-04-03) 8 | ------------------------ 9 | 10 | Features: 11 | 12 | * Support ``unrar-free`` >= 0.2.0. 13 | [`#103 `_] 14 | * Add :meth:`RarFile.is_solid` to check if archive uses 15 | solid compression. 16 | [`#101 `_] 17 | 18 | Fixes: 19 | 20 | * Support old multi-volume archives better where ENDARC 21 | does not contain NEXTVOL. 22 | [`#97 `_] 23 | 24 | Cleanups: 25 | 26 | * ci: Drop Python 3.7, add 3.12 27 | * ci: upgrade actions 28 | 29 | Version 4.1 (2023-09-17) 30 | ------------------------ 31 | 32 | Features: 33 | 34 | * Support 7zip/p7zip as decompression backend. 35 | [`#71 `_] 36 | * RAR5: check password before trying to read file (chigusa) 37 | [`#79 `_] 38 | 39 | New APIs: 40 | 41 | * Make get_rar_version a public function (Safihre) 42 | [`#63 `_] 43 | * New option: ``part_only`` for :class:`RarFile`, 44 | to read only single file and allow it to be middle-part 45 | of multi-volume archive. 46 | * Add :meth:`RarFile.printdir`, use it in dumprar. Needed to examine 47 | FILE_COPY or HARD_LINK entries that do not contain data. 48 | 49 | Fixes: 50 | 51 | * Use OS separator to access filename. Should fix 52 | subdirectory entry access on Windows. 53 | [`#96 `_] 54 | * DirectReader: check seek position on each read. 55 | Fixes read reading from multiple entries in parallel 56 | on RarFile backed by file object. 57 | [`#81 `_] 58 | * DirectReader: properly disable CRC check when seeking. 59 | [`#73 `_] 60 | * Reset _hdrenc_main before processing a new volume. 61 | Fixes CRC checks on multi-volume reads. 62 | [`#80 `_] 63 | * Adapt to Python 3.10 argparse (MeggyCal) 64 | [`#85 `_] 65 | * SFX: Handle volume numbering special cases better. 66 | * nsdatetime: support pypy internal use 67 | * Throw error if backend does not support passwords. 68 | 69 | Cleanups: 70 | 71 | * ci: Use proper unrar on Windows. MingW one tolaretes 72 | paths with ``/`` better than upstream build. 73 | * ci: Add Python 3.10 to the testing (Christian Clauss) 74 | [`#76 `_] 75 | * Avoid isascii, not in 3.6 76 | 77 | Version 4.0 (2020-07-31) 78 | ------------------------ 79 | 80 | Main goals are: 81 | 82 | * Increased ``zipfile``-compatibility, thus also achieving smaller 83 | difference between RAR3 and RAR5 archives. 84 | * Implement :meth:`RarFile.extract` on top of :meth:`RarFile.open` instead 85 | using ``unrar x`` directly, thus making maintenance of alternative backends 86 | more manageable. Negative aspect of that is that there are features that 87 | internal extract code does not support - hard links, NTFS streams and 88 | junctions. 89 | 90 | Breaking changes: 91 | 92 | * Directory names will have "/" appended. 93 | [`#31 `_] 94 | * :meth:`RarFile.extract` operates only on single entry, 95 | so when used on directory it will create directory 96 | but not extract files under it. 97 | * :meth:`RarFile.extract`/:meth:`RarFile.extractall`/:meth:`RarFile.testrar` 98 | will not launch special unrar command line, instead they are 99 | implemented on top of :meth:`RarFile.open`. 100 | * Keyword args in top-level APIs were renamed to match zipfile: 101 | 102 | * RarFile(rarfile) -> RarFile(file) 103 | * RarFile.setpassword(password) -> .setpassword(pwd) 104 | * RarFile.getinfo(fname) -> .getinfo(name) 105 | * RarFile.open(fname, mode, psw) -> .open(name, mode, pwd) 106 | * RarFile.read(fname, psw) -> .read(name, pwd) 107 | 108 | * :data:`PATH_SEP` cannot be changed from "/". 109 | 110 | New features: 111 | 112 | * :meth:`RarFile.extract` will return final sanitized filename for 113 | target file. 114 | [`#42 `_, 115 | `#52 `_] 116 | * :meth:`RarInfo.is_dir` is now preferred spelling of ``isdir()``. 117 | Old method kept as alias. 118 | [`#44 `_] 119 | * New :meth:`RarInfo.is_file` and :meth:`RarInfo.is_symlink` 120 | methods. Only one of :meth:`~RarInfo.is_file`, :meth:`~RarInfo.is_dir` 121 | or :meth:`~RarInfo.is_symlink` can be True. 122 | * :meth:`RarFile.printdir` has ``file`` argument for output. 123 | * :meth:`RarFile.__iter__` loops over :class:`RarInfo` entries. 124 | * RAR3: throw :exc:`NeedFirstVolume` exception with current volume number, 125 | like RAR5 does. 126 | [`#58 `_] 127 | * Nanosecond timestamp support. Visible as :class:`nsdatetime` 128 | instance. 129 | * Minimal CLI when run as script: ``python3 -m rarfile`` 130 | * Skip old file versions in versioned archive. 131 | 132 | Cleanups: 133 | 134 | * Use PBKDF2 implementation from :mod:`hashlib`. 135 | * Improve test coverage. 136 | 137 | Version 3.3 (2020-07-26) 138 | ------------------------ 139 | 140 | Fixes: 141 | 142 | * Add the .sfx test files to MANIFEST.in for inclusion in pypi tarball. 143 | [`#60 `_] 144 | * Add all files in git to tarball. 145 | 146 | Version 3.2 (2020-07-19) 147 | ------------------------ 148 | 149 | Breaking change: 150 | 151 | * Top-level function ``custom_check()`` is removed as part 152 | of tool discovery refactor. 153 | 154 | New features: 155 | 156 | * Support ``unar`` as decompression backend. It has much better 157 | support for RAR features than ``bsdtar``. 158 | [`#36 `_] 159 | 160 | * Support SFX archives - archive header is searched in first 161 | 2MB of the file. 162 | [`#48 `_] 163 | 164 | * Add :data:`HACK_TMP_DIR` option, to force temp files into 165 | specific directory. 166 | [`#43 `_] 167 | 168 | Fixes: 169 | 170 | * Always use "/" for path separator in command-line, gives better 171 | results on Windows. 172 | 173 | Cleanups: 174 | 175 | * Drop module-level options from docs, they create confusion. 176 | [`#47 `_] 177 | 178 | * Drop support for Python 2 and 3.5 and earlier. Python 2 is dead 179 | and requiring Python 3.6 gives blake2s, stdlib that supports pathlib, 180 | and ordered dict without compat hacks. 181 | 182 | * Replace PyCrypto with PyCryptodome in tests. 183 | 184 | * Use Github Actions for CI. 185 | 186 | Version 3.1 (2019-09-15) 187 | ------------------------ 188 | 189 | **This will be last version with support for Python 2.x** 190 | 191 | New feature: 192 | 193 | * Accept pathlib objects as filenames. 194 | (Aleksey Popov) 195 | 196 | * Accept `bytes` filenames in Python 3 197 | (Nate Bogdanowicz) 198 | 199 | Fixes: 200 | 201 | * Use bug-compatible SHA1 for longer passwords (> 28 chars) 202 | in RAR3 encrypted headers. 203 | (Marko Kreen) 204 | 205 | * Return true/false from _check_unrar_tool 206 | (miigotu) 207 | 208 | * Include all test files in archive 209 | (Benedikt Morbach) 210 | 211 | * Include volume number in NeedFirstVolume exception if available (rar5). 212 | 213 | Cleanups: 214 | 215 | * Convert tests to pytest. 216 | 217 | Version 3.0 (2016-12-27) 218 | ------------------------ 219 | 220 | New feature: 221 | 222 | * Support RAR5 archive format. It is actually completely different 223 | archive format from RAR3 one, only is uses same file extension 224 | and tools are old one. 225 | 226 | Except incompatibilies noted below, most of code should notice no change, 227 | existing :class:`RarInfo` fields will continue using RAR3-compatible 228 | values (eg. :attr:`RarInfo.host_os`). RAR5-specific values will use 229 | new fields. 230 | 231 | Incompatibilities between rarfile v2.x and 3.x: 232 | 233 | * Default :data:`PATH_SEP` is now '/' instead '\\'. 234 | 235 | * Removed :data:`NEED_COMMENTS` option, comments are always extracted. 236 | 237 | * Removed :data:`UNICODE_COMMENTS` option, they are always decoded. 238 | 239 | * Removed :data:`USE_DATETIME` option, :attr:`RarInfo.date_time` is always tuple, 240 | :attr:`RarInfo.mtime`, :attr:`RarInfo.atime`, :attr:`RarInfo.ctime` and 241 | :attr:`RarInfo.arctime` are always :class:`datetime.datetime` objects. 242 | 243 | Fixes: 244 | 245 | * Fixed bug when calling rarfp.open() on a RarInfo structure. 246 | 247 | Cleanups: 248 | 249 | * Code refactor to allow 2 different file format parsers. 250 | 251 | * Code cleanups to pass modern linters. 252 | 253 | * New testing and linting setup based on Tox_. 254 | 255 | * Use setuptools instead distutils for install. 256 | 257 | .. _Tox: https://tox.readthedocs.io/en/latest/ 258 | 259 | Version 2.8 (2016-06-07) 260 | ------------------------ 261 | 262 | * Fix: support solid archives from in-memory file object. 263 | Full archive will be written out to temp file. 264 | [`#21 `_] 265 | 266 | * Fix: ask unrar stop switches scanning, 267 | to handle archive names starting with "-". 268 | (Alexander Shadchin) 269 | [`#12 `_] 270 | 271 | * Fix: add missing _parse_error variable to RarFile object. 272 | (Gregory Mazzola) 273 | [`#20 `_] 274 | 275 | * Fix: return proper boolean from :meth:`RarInfo.needs_password`. 276 | [`#22 `_] 277 | 278 | * Fix: do not insert non-string rarfile into exception string. 279 | (Tim Muller) 280 | [`#23 `_] 281 | 282 | * Fix: make :meth:`RarFile.extract` and :meth:`RarFile.testrar` 283 | support in-memory archives. 284 | 285 | * Use cryptography_ module as preferred crypto backend. 286 | PyCrypto_ will be used as fallback. 287 | 288 | * Cleanup: remove compat code for Python 2.4/2.5/2.6. 289 | 290 | .. _cryptography: https://pypi.org/project/cryptography/ 291 | .. _PyCrypto: https://pypi.org/project/pycrypto/ 292 | 293 | Version 2.7 (2014-11-23) 294 | ------------------------ 295 | 296 | * Allow use of bsdtar_ as decompression backend. It sits 297 | on top of libarchive_, which has support for reading RAR archives. 298 | 299 | Limitations of ``libarchive`` RAR backend: 300 | 301 | - Does not support solid archives. 302 | - Does not support password-protected archives. 303 | - Does not support "parsing filters" used for audio/image/executable data, 304 | so few non-solid, non-encrypted archives also fail. 305 | 306 | Now :mod:`rarfile` checks if ``unrar`` and if not then tries ``bsdtar``. 307 | If that works, then keeps using it. If not then configuration 308 | stays with ``unrar`` which will then appear in error messages. 309 | 310 | .. _bsdtar: https://github.com/libarchive/libarchive/wiki/ManPageBsdtar1 311 | .. _libarchive: https://www.libarchive.org/ 312 | 313 | * Both :class:`RarFile` and :func:`is_rarfile` now accept file-like 314 | object. Eg. :class:`io.BytesIO`. Only requirement is that the object 315 | must be seekable. This mirrors similar funtionality in zipfile. 316 | 317 | Based on patch by Chase Zhang. 318 | 319 | * Uniform error handling. :class:`RarFile` accepts ``errors="strict"`` 320 | argument. 321 | 322 | Allow user to tune whether parsing and missing file errors will raise 323 | exception. If error is not raised, the error string can be queried 324 | with :meth:`RarFile.strerror` method. 325 | 326 | Version 2.6 (2013-04-10) 327 | ------------------------ 328 | 329 | * Add context manager support for :class:`RarFile` class. 330 | Both :class:`RarFile` and :class:`RarExtFile` support 331 | :keyword:`with` statement now. 332 | (Wentao Han) 333 | * :meth:`RarFile.volumelist` method, returns filenames of archive volumes. 334 | * Re-throw clearer error in case ``unrar`` is not found in ``PATH``. 335 | * Sync new unrar4.x error code from ``rar.txt``. 336 | * Use Sphinx for documentation, push docs to rtfd.org_ 337 | 338 | .. _rtfd.org: https://rarfile.readthedocs.org/ 339 | 340 | Version 2.5 (2012-01-19) 341 | ------------------------ 342 | 343 | Fixes: 344 | 345 | * :meth:`RarExtFile.read` and :meth:`RarExtFile.readinto` now do looping read 346 | to work properly on short reads. Important for Python 3.2+ where read from pipe 347 | can return short result even on blocking file descriptor. 348 | * Proper error reporting in :meth:`RarFile.extract`, :meth:`RarFile.extractall` 349 | and :meth:`RarFile.testrar`. 350 | * :meth:`RarExtFile.read` from unrar pipe: prefer to return unrar error code, 351 | if thats not available, do own error checks. 352 | * Avoid string addition in :meth:`RarExtFile.read`, instead use always list+join to 353 | merge multi-part reads. 354 | * dumprar: dont re-encode byte strings (Python 2.x). This avoids 355 | unneccessary failure when printing invalid unicode. 356 | 357 | Version 2.4 (2011-11-05) 358 | ------------------------ 359 | 360 | Fixes: 361 | 362 | * :data:`USE_DATETIME`: survive bad values from RAR 363 | * Fix bug in corrupt unicode filename handling 364 | * dumprar: make unicode chars work with both pipe and console 365 | 366 | Version 2.3 (2011-07-03) 367 | ------------------------ 368 | 369 | Features: 370 | 371 | * Support .seek() method on file streams. (Kristian Larsson) 372 | * Support .readinto() method on file streams. Optimized implementation 373 | is available on Python 2.6+ where :class:`memoryview` is available. 374 | * Support file comments - :attr:`RarInfo.comment` contains decompressed data if available. 375 | * File objects returned by :meth:`RarFile.open()` are :class:`io.RawIOBase`-compatible. 376 | They can further wrapped with :class:`io.BufferedReader` and :class:`io.TextIOWrapper`. 377 | * Now .getinfo() uses dict lookup instead of sequential scan when 378 | searching archive entry. This speeds up prococessing for archives that 379 | have many entries. 380 | * Option :data:`UNICODE_COMMENTS` to decode both archive and file comments to unicode. 381 | It uses :data:`TRY_ENCODINGS` for list of encodings to try. If off, comments are 382 | left as byte strings. Default: 0 383 | * Option :data:`PATH_SEP` to change path separator. Default: ``r'\'``, 384 | set ``rarfile.PATH_SEP='/'`` to be compatibe with zipfile. 385 | * Option :data:`USE_DATETIME` to convert timestamps to datetime objects. 386 | Default: 0, timestamps are tuples. 387 | * Option :data:`TRY_ENCODINGS` to allow tuning attempted encoding list. 388 | * Reorder :class:`RarInfo` fiels to better show zipfile-compatible fields. 389 | * Standard regtests to make sure various features work 390 | 391 | Compatibility: 392 | 393 | * Drop :attr:`RarInfo.unicode_filename`, plain :attr:`RarInfo.filename` is already unicode since 2.0. 394 | * .read(-1) reads now until EOF. Previously it returned empty buffer. 395 | 396 | Fixes: 397 | 398 | * Make encrypted headers work with Python 3.x bytes() and with old 2.x 'sha' module. 399 | * Simplify :class:`subprocess.Popen` usage when launching ``unrar``. Previously 400 | it tried to optimize and work around OS/Python bugs, but this is not 401 | maintainable. 402 | * Use temp rar file hack on multi-volume archives too. 403 | * Always .wait() on unrar, to avoid zombies 404 | * Convert struct.error to BadRarFile 405 | * Plug some fd leaks. Affected: Jython, PyPy. 406 | * Broken archives are handled more robustly. 407 | 408 | Version 2.2 (2010-08-19) 409 | ------------------------ 410 | 411 | Fixes: 412 | 413 | * Relaxed volume naming. Now it just calculates new volume name by finding number 414 | in old one and increasing it, without any expectations what that number should be. 415 | * Files with 4G of compressed data in one colume were handled wrong. Fix. 416 | * DOS timestamp seconds need to be multiplied with 2. 417 | * Correct EXTTIME parsing. 418 | 419 | Cleanups: 420 | 421 | * Compressed size is per-volume, sum them together, so that user sees complete 422 | compressed size for files split over several volumes. 423 | * dumprar: Show unknown bits. 424 | * Use :class:`struct.Struct` to cache unpack formats. 425 | * Support missing :data:`os.devnull`. (Python 2.3) 426 | 427 | Version 2.1 (2010-07-31) 428 | ------------------------ 429 | 430 | Features: 431 | 432 | * Minimal implmentation for :meth:`RarFile.extract`, :meth:`RarFile.extractall`, :meth:`RarFile.testrar`. 433 | They are simple shortcuts to ``unrar`` invocation. 434 | * Accept :class:`RarInfo` object where filename is expected. 435 | * Include ``dumprar.py`` in .tgz. It can be used to visualize RAR structure 436 | and test module. 437 | * Support for encrypted file headers. 438 | 439 | Fixes: 440 | 441 | * Don't read past ENDARC, there could be non-RAR data there. 442 | * RAR 2.x: It does not write ENDARC, but our volume code expected it. Fix that. 443 | * RAR 2.x: Support more than 200 old-style volumes. 444 | 445 | Cleanups: 446 | 447 | * Load comment only when requested. 448 | * Cleanup of internal config variables. They should have now final names. 449 | * :meth:`RarFile.open`: Add mode=r argument to match zipfile. 450 | * Doc and comments cleanup, minimize duplication. 451 | * Common wrappers for both compressed and uncompressed files, 452 | now :meth:`RarFile.open` also does CRC-checking. 453 | 454 | Version 2.0 (2010-04-29) 455 | ------------------------ 456 | 457 | Features: 458 | 459 | * Python 3 support. Still works with 2.x. 460 | * Parses extended time fields. (.mtime, .ctime, .atime) 461 | * :meth:`RarFile.open` method. This makes possible to process large 462 | entries that do not fit into memory. 463 | * Supports password-protected archives. 464 | * Supports archive comments. 465 | 466 | Cleanups: 467 | 468 | * Uses :mod:`subprocess` module to launch ``unrar``. 469 | * .filename is always Unicode string, .unicode_filename is now deprecated. 470 | * .CRC is unsigned again, as python3 crc32() is unsigned. 471 | 472 | Version 1.1 (2008-08-31) 473 | ------------------------ 474 | 475 | Fixes: 476 | 477 | * Replace :func:`os.tempnam` with :func:`tempfile.mkstemp`. (Jason Moiron) 478 | * Fix infinite loop in _extract_hack on unexpected EOF 479 | * :attr:`RarInfo.CRC` is now signed value to match crc32() 480 | * :meth:`RarFile.read` now checks file crc 481 | 482 | Cleanups: 483 | 484 | * more docstrings 485 | * throw proper exceptions (subclasses of :exc:`rarfile.Error`) 486 | * RarInfo has fields pre-initialized, so they appear in help() 487 | * rename RarInfo.data to RarInfo.header_data 488 | * dont use "print" when header parsing fails 489 | * use try/finally to delete temp rar 490 | 491 | Version 1.0 (2005-08-08) 492 | ------------------------ 493 | 494 | * First release. 495 | 496 | -------------------------------------------------------------------------------- /doc/note.awk: -------------------------------------------------------------------------------- 1 | # extract version notes for version VER 2 | 3 | /^[-_0-9a-zA-Z]+ v?[0-9]/ { 4 | if ($2 == VER) { 5 | good = 1 6 | next 7 | } else { 8 | good = 0 9 | } 10 | } 11 | 12 | /^(===|---)/ { next } 13 | 14 | { 15 | if (good) { 16 | # also remove sphinx syntax 17 | print gensub(/:(\w+):`~?([^`]+)`/, "``\\2``", "g") 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /dumprar.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | """Dump archive contents, test extraction.""" 4 | 5 | import binascii 6 | import getopt 7 | import io 8 | import sys 9 | from datetime import datetime 10 | 11 | import rarfile as rf 12 | 13 | usage = """ 14 | dumprar [switches] [ARC1 ARC2 ...] [@ARCLIST] 15 | switches: 16 | @file read archive names from file 17 | -pPWD set password 18 | -Ccharset set fallback charset 19 | -v increase verbosity 20 | -t attempt to read all files 21 | -x write read files out 22 | -c show archive comment 23 | -h show usage 24 | -bTOOL set backend tool (unrar, unar, bsdtar, 7z, 7zz) 25 | -- stop switch parsing 26 | """.strip() 27 | 28 | os_list = ["DOS", "OS2", "WIN", "UNIX", "MACOS", "BEOS"] 29 | 30 | block_strs = ["MARK", "MAIN", "FILE", "OLD_COMMENT", "OLD_EXTRA", 31 | "OLD_SUB", "OLD_RECOVERY", "OLD_AUTH", "SUB", "ENDARC"] 32 | 33 | r5_block_types = { 34 | rf.RAR5_BLOCK_MAIN: "R5_MAIN", 35 | rf.RAR5_BLOCK_FILE: "R5_FILE", 36 | rf.RAR5_BLOCK_SERVICE: "R5_SVC", 37 | rf.RAR5_BLOCK_ENCRYPTION: "R5_ENC", 38 | rf.RAR5_BLOCK_ENDARC: "R5_ENDARC", 39 | } 40 | 41 | 42 | def rar3_type(btype): 43 | """RAR3 type code as string.""" 44 | if btype < rf.RAR_BLOCK_MARK or btype > rf.RAR_BLOCK_ENDARC: 45 | return "*UNKNOWN*" 46 | return block_strs[btype - rf.RAR_BLOCK_MARK] 47 | 48 | 49 | def rar5_type(btype): 50 | """RAR5 type code as string.""" 51 | return r5_block_types.get(btype, "*UNKNOWN*") 52 | 53 | 54 | main_bits = ( 55 | (rf.RAR_MAIN_VOLUME, "VOL"), 56 | (rf.RAR_MAIN_COMMENT, "COMMENT"), 57 | (rf.RAR_MAIN_LOCK, "LOCK"), 58 | (rf.RAR_MAIN_SOLID, "SOLID"), 59 | (rf.RAR_MAIN_NEWNUMBERING, "NEWNR"), 60 | (rf.RAR_MAIN_AUTH, "AUTH"), 61 | (rf.RAR_MAIN_RECOVERY, "RECOVERY"), 62 | (rf.RAR_MAIN_PASSWORD, "PASSWORD"), 63 | (rf.RAR_MAIN_FIRSTVOLUME, "FIRSTVOL"), 64 | (rf.RAR_SKIP_IF_UNKNOWN, "SKIP"), 65 | (rf.RAR_LONG_BLOCK, "LONG"), 66 | ) 67 | 68 | endarc_bits = ( 69 | (rf.RAR_ENDARC_NEXT_VOLUME, "NEXTVOL"), 70 | (rf.RAR_ENDARC_DATACRC, "DATACRC"), 71 | (rf.RAR_ENDARC_REVSPACE, "REVSPACE"), 72 | (rf.RAR_ENDARC_VOLNR, "VOLNR"), 73 | (rf.RAR_SKIP_IF_UNKNOWN, "SKIP"), 74 | (rf.RAR_LONG_BLOCK, "LONG"), 75 | ) 76 | 77 | file_bits = ( 78 | (rf.RAR_FILE_SPLIT_BEFORE, "SPLIT_BEFORE"), 79 | (rf.RAR_FILE_SPLIT_AFTER, "SPLIT_AFTER"), 80 | (rf.RAR_FILE_PASSWORD, "PASSWORD"), 81 | (rf.RAR_FILE_COMMENT, "COMMENT"), 82 | (rf.RAR_FILE_SOLID, "SOLID"), 83 | (rf.RAR_FILE_LARGE, "LARGE"), 84 | (rf.RAR_FILE_UNICODE, "UNICODE"), 85 | (rf.RAR_FILE_SALT, "SALT"), 86 | (rf.RAR_FILE_VERSION, "VERSION"), 87 | (rf.RAR_FILE_EXTTIME, "EXTTIME"), 88 | (rf.RAR_FILE_EXTFLAGS, "EXTFLAGS"), 89 | (rf.RAR_SKIP_IF_UNKNOWN, "SKIP"), 90 | (rf.RAR_LONG_BLOCK, "LONG"), 91 | ) 92 | 93 | generic_bits = ( 94 | (rf.RAR_SKIP_IF_UNKNOWN, "SKIP"), 95 | (rf.RAR_LONG_BLOCK, "LONG"), 96 | ) 97 | 98 | file_parms = ("D64", "D128", "D256", "D512", 99 | "D1024", "D2048", "D4096", "DIR") 100 | 101 | r5_block_flags = ( 102 | (rf.RAR5_BLOCK_FLAG_EXTRA_DATA, "EXTRA"), 103 | (rf.RAR5_BLOCK_FLAG_DATA_AREA, "DATA"), 104 | (rf.RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN, "SKIP"), 105 | (rf.RAR5_BLOCK_FLAG_SPLIT_BEFORE, "SPLIT_BEFORE"), 106 | (rf.RAR5_BLOCK_FLAG_SPLIT_AFTER, "SPLIT_AFTER"), 107 | (rf.RAR5_BLOCK_FLAG_DEPENDS_PREV, "DEPENDS"), 108 | (rf.RAR5_BLOCK_FLAG_KEEP_WITH_PARENT, "KEEP"), 109 | ) 110 | 111 | r5_main_flags = ( 112 | (rf.RAR5_MAIN_FLAG_ISVOL, "ISVOL"), 113 | (rf.RAR5_MAIN_FLAG_HAS_VOLNR, "VOLNR"), 114 | (rf.RAR5_MAIN_FLAG_SOLID, "SOLID"), 115 | (rf.RAR5_MAIN_FLAG_RECOVERY, "RECOVERY"), 116 | (rf.RAR5_MAIN_FLAG_LOCKED, "LOCKED"), 117 | ) 118 | 119 | r5_file_flags = ( 120 | (rf.RAR5_FILE_FLAG_ISDIR, "DIR"), 121 | (rf.RAR5_FILE_FLAG_HAS_MTIME, "MTIME"), 122 | (rf.RAR5_FILE_FLAG_HAS_CRC32, "CRC32"), 123 | (rf.RAR5_FILE_FLAG_UNKNOWN_SIZE, "NOSIZE"), 124 | ) 125 | 126 | r5_enc_flags = ( 127 | (rf.RAR5_ENC_FLAG_HAS_CHECKVAL, "CHECKVAL"), 128 | ) 129 | 130 | r5_endarc_flags = ( 131 | (rf.RAR5_ENDARC_FLAG_NEXT_VOL, "NEXTVOL"), 132 | ) 133 | 134 | r5_file_enc_flags = ( 135 | (rf.RAR5_XENC_CHECKVAL, "CHECKVAL"), 136 | (rf.RAR5_XENC_TWEAKED, "TWEAKED"), 137 | ) 138 | 139 | r5_file_redir_types = { 140 | rf.RAR5_XREDIR_UNIX_SYMLINK: "UNIX_SYMLINK", 141 | rf.RAR5_XREDIR_WINDOWS_SYMLINK: "WINDOWS_SYMLINK", 142 | rf.RAR5_XREDIR_WINDOWS_JUNCTION: "WINDOWS_JUNCTION", 143 | rf.RAR5_XREDIR_HARD_LINK: "HARD_LINK", 144 | rf.RAR5_XREDIR_FILE_COPY: "FILE_COPY", 145 | } 146 | 147 | r5_file_redir_flags = ( 148 | (rf.RAR5_XREDIR_ISDIR, "DIR"), 149 | ) 150 | 151 | 152 | dos_mode_bits = ( 153 | (0x01, "READONLY"), 154 | (0x02, "HIDDEN"), 155 | (0x04, "SYSTEM"), 156 | (0x08, "VOLUME_ID"), 157 | (0x10, "DIRECTORY"), 158 | (0x20, "ARCHIVE"), 159 | (0x40, "DEVICE"), 160 | (0x80, "NORMAL"), 161 | (0x0100, "TEMPORARY"), 162 | (0x0200, "SPARSE_FILE"), 163 | (0x0400, "REPARSE_POINT"), 164 | (0x0800, "COMPRESSED"), 165 | (0x1000, "OFFLINE"), 166 | (0x2000, "NOT_CONTENT_INDEXED"), 167 | (0x4000, "ENCRYPTED"), 168 | (0x8000, "INTEGRITY_STREAM"), 169 | (0x00010000, "VIRTUAL"), 170 | (0x00020000, "NO_SCRUB_DATA"), 171 | (0x00040000, "RECALL_ON_OPEN"), 172 | (0x00080000, "PINNED"), 173 | (0x00100000, "UNPINNED"), 174 | (0x00400000, "RECALL_ON_DATA_ACCESS"), 175 | (0x20000000, "STRICTLY_SEQUENTIAL"), 176 | ) 177 | 178 | 179 | def xprint(m, *args): 180 | """Print string to stdout. 181 | """ 182 | if args: 183 | m = m % args 184 | print(m) 185 | 186 | 187 | def tohex(data): 188 | """Return hex string.""" 189 | return binascii.hexlify(data).decode("ascii") 190 | 191 | 192 | def render_flags(flags, bit_list): 193 | """Show bit names. 194 | """ 195 | res = [] 196 | known = 0 197 | for bit in bit_list: 198 | known = known | bit[0] 199 | if flags & bit[0]: 200 | res.append(bit[1]) 201 | unknown = flags & ~known 202 | n = 0 203 | while unknown: 204 | if unknown & 1: 205 | res.append("UNK_%04x" % (1 << n)) 206 | unknown = unknown >> 1 207 | n += 1 208 | 209 | if not res: 210 | return "-" 211 | 212 | return ",".join(res) 213 | 214 | 215 | def get_file_flags(flags): 216 | """Show flag names and handle dict size. 217 | """ 218 | res = render_flags(flags & ~rf.RAR_FILE_DICTMASK, file_bits) 219 | 220 | xf = (flags & rf.RAR_FILE_DICTMASK) >> 5 221 | res += "," + file_parms[xf] 222 | return res 223 | 224 | 225 | def fmt_time(t): 226 | """Format time. 227 | """ 228 | if t is None: 229 | return "(-)" 230 | if isinstance(t, datetime): 231 | return t.isoformat("T") 232 | return "%04d-%02d-%02d %02d:%02d:%02d" % t 233 | 234 | 235 | def show_item(h): 236 | """Show any RAR3/5 record. 237 | """ 238 | if isinstance(h, rf.Rar3Info): 239 | show_item_v3(h) 240 | elif isinstance(h, rf.Rar5Info): 241 | show_item_v5(h) 242 | else: 243 | xprint("Unknown info record") 244 | 245 | 246 | def show_rftype(h): 247 | return "".join([ 248 | h.is_file() and "F" or "-", 249 | h.is_dir() and "D" or "-", 250 | h.is_symlink() and "L" or "-", 251 | ]) 252 | 253 | 254 | def modex3(v): 255 | return [v & 4 and "r" or "-", v & 2 and "w" or "-", v & 1 and "x" or "-"] 256 | 257 | 258 | def unix_mode(mode): 259 | perms = modex3(mode >> 6) + modex3(mode >> 3) + modex3(mode) 260 | if mode & 0x0800: 261 | perms[2] = perms[2] == "x" and "s" or "S" 262 | if mode & 0x0400: 263 | perms[5] = perms[5] == "x" and "s" or "S" 264 | if mode & 0x0200: 265 | perms[8] = perms[8] == "x" and "t" or "-" 266 | rest = mode & 0xF000 267 | if rest == 0x4000: 268 | perms.insert(0, "d") 269 | elif rest == 0xA000: 270 | perms.insert(0, "l") 271 | elif rest == 0x8000: 272 | # common 273 | perms.insert(0, "-") 274 | elif rest == 0: 275 | perms.insert(0, "-") 276 | else: 277 | perms.insert(0, "?") 278 | perms.append("(0x%04x)" % rest) 279 | return "".join(perms) 280 | 281 | 282 | def show_mode(h): 283 | if h.host_os in (rf.RAR_OS_UNIX, rf.RAR_OS_BEOS): 284 | s_mode = unix_mode(h.mode) 285 | elif h.host_os in (rf.RAR_OS_MSDOS, rf.RAR_OS_WIN32, rf.RAR_OS_OS2): 286 | s_mode = render_flags(h.mode, dos_mode_bits) 287 | else: 288 | s_mode = "0x%x" % h.mode 289 | return s_mode 290 | 291 | 292 | def show_item_v3(h): 293 | """Show any RAR3 record. 294 | """ 295 | st = rar3_type(h.type) 296 | xprint("%s: hdrlen=%d datlen=%d is=%s", 297 | st, h.header_size, h.add_size, show_rftype(h)) 298 | if h.type in (rf.RAR_BLOCK_FILE, rf.RAR_BLOCK_SUB): 299 | s_mode = show_mode(h) 300 | xprint(" flags=0x%04x:%s", h.flags, get_file_flags(h.flags)) 301 | if h.host_os >= 0 and h.host_os < len(os_list): 302 | s_os = os_list[h.host_os] 303 | else: 304 | s_os = "?" 305 | if h.flags & rf.RAR_FILE_UNICODE: 306 | s_namecmp = " namecmp=%d/%d" % (len(h.orig_filename), h._name_size) 307 | else: 308 | s_namecmp = "" 309 | xprint(" os=%d:%s ver=%d mode=%s meth=%c cmp=%d dec=%d vol=%d%s", 310 | h.host_os, s_os, 311 | h.extract_version, s_mode, h.compress_type, 312 | h.compress_size, h.file_size, h.volume, s_namecmp) 313 | ucrc = (h.CRC + (1 << 32)) & ((1 << 32) - 1) 314 | xprint(" crc=0x%08x (%d) date_time=%s", ucrc, h.CRC, fmt_time(h.date_time)) 315 | xprint(" name=%s", h.filename) 316 | if h.mtime: 317 | xprint(" mtime=%s", fmt_time(h.mtime)) 318 | if h.ctime: 319 | xprint(" ctime=%s", fmt_time(h.ctime)) 320 | if h.atime: 321 | xprint(" atime=%s", fmt_time(h.atime)) 322 | if h.arctime: 323 | xprint(" arctime=%s", fmt_time(h.arctime)) 324 | elif h.type == rf.RAR_BLOCK_MAIN: 325 | xprint(" flags=0x%04x:%s", h.flags, render_flags(h.flags, main_bits)) 326 | elif h.type == rf.RAR_BLOCK_ENDARC: 327 | xprint(" flags=0x%04x:%s", h.flags, render_flags(h.flags, endarc_bits)) 328 | if h.flags & rf.RAR_ENDARC_DATACRC: 329 | xprint(" datacrc=0x%08x", h.endarc_datacrc) 330 | if h.flags & rf.RAR_ENDARC_DATACRC: 331 | xprint(" volnr=%d", h.endarc_volnr) 332 | elif h.type == rf.RAR_BLOCK_MARK: 333 | xprint(" flags=0x%04x:", h.flags) 334 | else: 335 | xprint(" flags=0x%04x:%s", h.flags, render_flags(h.flags, generic_bits)) 336 | 337 | if h.comment is not None: 338 | cm = repr(h.comment) 339 | if cm[0] == "u": 340 | cm = cm[1:] 341 | xprint(" comment=%s", cm) 342 | 343 | 344 | def show_item_v5(h): 345 | """Show any RAR5 record. 346 | """ 347 | st = rar5_type(h.block_type) 348 | xprint("%s: hdrlen=%d datlen=%d hdr_extra=%d is=%s", st, h.header_size, 349 | h.compress_size, h.block_extra_size, show_rftype(h)) 350 | xprint(" block_flags=0x%04x:%s", h.block_flags, render_flags(h.block_flags, r5_block_flags)) 351 | if h.block_type in (rf.RAR5_BLOCK_FILE, rf.RAR5_BLOCK_SERVICE): 352 | xprint(" name=%s", h.filename) 353 | s_mode = show_mode(h) 354 | if h.file_host_os == rf.RAR5_OS_UNIX: 355 | s_os = "UNIX" 356 | else: 357 | s_os = "WINDOWS" 358 | xprint(" file_flags=0x%04x:%s", h.file_flags, render_flags(h.file_flags, r5_file_flags)) 359 | 360 | cmp_flags = h.file_compress_flags 361 | xprint(" cmp_algo=%d cmp_meth=%d dict=%d solid=%r", 362 | cmp_flags & 0x3f, 363 | (cmp_flags >> 7) & 0x07, 364 | cmp_flags >> 10, 365 | cmp_flags & rf.RAR5_COMPR_SOLID > 0) 366 | xprint(" os=%d:%s mode=%s cmp=%r dec=%r vol=%r", 367 | h.file_host_os, s_os, s_mode, 368 | h.compress_size, h.file_size, h.volume) 369 | if h.CRC is not None: 370 | xprint(" crc=0x%08x (%d)", h.CRC, h.CRC) 371 | if h.blake2sp_hash is not None: 372 | xprint(" blake2sp=%s", tohex(h.blake2sp_hash)) 373 | if h.date_time is not None: 374 | xprint(" date_time=%s", fmt_time(h.date_time)) 375 | if h.mtime: 376 | xprint(" mtime=%s", fmt_time(h.mtime)) 377 | if h.ctime: 378 | xprint(" ctime=%s", fmt_time(h.ctime)) 379 | if h.atime: 380 | xprint(" atime=%s", fmt_time(h.atime)) 381 | if h.arctime: 382 | xprint(" arctime=%s", fmt_time(h.arctime)) 383 | if h.flags & rf.RAR_FILE_PASSWORD: 384 | enc_algo, enc_flags, kdf_count, salt, iv, checkval = h.file_encryption 385 | algo_name = "AES256" if enc_algo == rf.RAR5_XENC_CIPHER_AES256 else "UnknownAlgo" 386 | xprint(" algo=%d:%s enc_flags=%04x:%s kdf_lg=%d kdf_count=%d salt=%s iv=%s checkval=%s", 387 | enc_algo, algo_name, enc_flags, render_flags(enc_flags, r5_file_enc_flags), 388 | kdf_count, 1 << kdf_count, tohex(salt), tohex(iv), 389 | checkval and tohex(checkval) or "-") 390 | if h.file_redir: 391 | redir_type, redir_flags, redir_name = h.file_redir 392 | xprint(" redir: type=%s flags=%d:%s destination=%s", 393 | r5_file_redir_types.get(redir_type, "Unknown"), 394 | redir_flags, render_flags(redir_flags, r5_file_redir_flags), 395 | redir_name) 396 | if h.file_owner: 397 | uname, gname, uid, gid = h.file_owner 398 | xprint(" owner: name=%r group=%r uid=%r gid=%r", 399 | uname, gname, uid, gid) 400 | if h.file_version: 401 | flags, version = h.file_version 402 | xprint(" version: flags=%r version=%r", flags, version) 403 | elif h.block_type == rf.RAR5_BLOCK_MAIN: 404 | xprint(" flags=0x%04x:%s", h.flags, render_flags(h.main_flags, r5_main_flags)) 405 | elif h.block_type == rf.RAR5_BLOCK_ENDARC: 406 | xprint(" flags=0x%04x:%s", h.flags, render_flags(h.endarc_flags, r5_endarc_flags)) 407 | elif h.block_type == rf.RAR5_BLOCK_ENCRYPTION: 408 | algo_name = "AES256" if h.encryption_algo == rf.RAR5_XENC_CIPHER_AES256 else "UnknownAlgo" 409 | xprint(" algo=%d:%s flags=0x%04x:%s", h.encryption_algo, algo_name, h.flags, 410 | render_flags(h.encryption_flags, r5_enc_flags)) 411 | xprint(" kdf_lg=%d kdf_count=%d", h.encryption_kdf_count, 1 << h.encryption_kdf_count) 412 | xprint(" salt=%s", tohex(h.encryption_salt)) 413 | else: 414 | xprint(" - missing info -") 415 | 416 | if h.comment is not None: 417 | cm = repr(h.comment) 418 | if cm[0] == "u": 419 | cm = cm[1:] 420 | xprint(" comment=%s", cm) 421 | 422 | 423 | cf_show_comment = 0 424 | cf_verbose = 0 425 | cf_charset = None 426 | cf_extract = 0 427 | cf_test_read = 0 428 | cf_test_unrar = 0 429 | cf_test_memory = 0 430 | 431 | 432 | def check_crc(f, inf, desc): 433 | """Compare result crc to expected value. 434 | """ 435 | exp = inf._md_expect 436 | if exp is None: 437 | return 438 | ucrc = f._md_context.digest() 439 | if ucrc != exp: 440 | print("crc error - %s - exp=%r got=%r" % (desc, exp, ucrc)) 441 | 442 | 443 | def test_read_long(r, inf): 444 | """Test read and readinto. 445 | """ 446 | md_class = inf._md_class or rf.NoHashContext 447 | bctx = md_class() 448 | inf_orig = r.getinfo_orig(inf.filename) 449 | f = r.open(inf.filename) 450 | total = 0 451 | while 1: 452 | data = f.read(8192) 453 | if not data: 454 | break 455 | bctx.update(data) 456 | total += len(data) 457 | if total != inf.file_size: 458 | xprint("\n *** %s has corrupt file: %s ***", r.rarfile, inf.filename) 459 | xprint(" *** short read: got=%d, need=%d ***\n", total, inf.file_size) 460 | check_crc(f, inf_orig, "read") 461 | bhash = bctx.hexdigest() 462 | if cf_verbose > 1: 463 | if f._md_context.digest() == inf_orig._md_expect: 464 | #xprint(" checkhash: %r", bhash) 465 | pass 466 | else: 467 | xprint(" checkhash: %r got=%r exp=%r cls=%r\n", 468 | bhash, f._md_context.digest(), inf._md_expect, inf._md_class) 469 | 470 | # test .seek() & .readinto() 471 | if cf_test_read > 1: 472 | f.seek(0, 0) 473 | 474 | total = 0 475 | buf = bytearray(1024) 476 | while 1: 477 | res = f.readinto(buf) 478 | if not res: 479 | break 480 | total += res 481 | if inf.file_size != total: 482 | xprint(" *** readinto failed: got=%d, need=%d ***\n", total, inf.file_size) 483 | #check_crc(f, inf, "readinto") 484 | f.close() 485 | 486 | 487 | def test_read(r, inf): 488 | """Test file read.""" 489 | test_read_long(r, inf) 490 | 491 | 492 | def test_real(fn, pwd): 493 | """Actual archive processing. 494 | """ 495 | xprint("Archive: %s", fn) 496 | 497 | cb = None 498 | if cf_verbose > 1: 499 | cb = show_item 500 | 501 | rfarg = fn 502 | if cf_test_memory: 503 | rfarg = io.BytesIO(open(fn, "rb").read()) 504 | 505 | # check if rar 506 | if not rf.is_rarfile(rfarg): 507 | xprint(" --- %s is not a RAR file ---", fn) 508 | return 509 | 510 | # open 511 | r = rf.RarFile(rfarg, charset=cf_charset, info_callback=cb) 512 | # set password 513 | if r.needs_password(): 514 | if pwd: 515 | r.setpassword(pwd) 516 | else: 517 | xprint(" --- %s requires password ---", fn) 518 | return 519 | 520 | # show comment 521 | if cf_show_comment and r.comment: 522 | for ln in r.comment.split("\n"): 523 | xprint(" %s", ln) 524 | elif cf_verbose > 0 and r.comment: 525 | cm = repr(r.comment) 526 | if cm[0] == "u": 527 | cm = cm[1:] 528 | xprint(" comment=%s", cm) 529 | 530 | # process 531 | for n in r.namelist(): 532 | inf = r.getinfo(n) 533 | if cf_verbose == 1: 534 | show_item(inf) 535 | if cf_test_read and inf.is_file(): 536 | test_read(r, inf) 537 | 538 | if cf_extract: 539 | r.extractall() 540 | for inf in r.infolist(): 541 | r.extract(inf) 542 | 543 | if cf_test_unrar: 544 | r.testrar() 545 | 546 | 547 | def test(fn, pwd): 548 | """Process one archive with error handling. 549 | """ 550 | try: 551 | test_real(fn, pwd) 552 | except rf.NeedFirstVolume as ex: 553 | xprint(" --- %s is middle part of multi-vol archive (%s)---", fn, str(ex)) 554 | except rf.Error: 555 | exc, msg, tb = sys.exc_info() 556 | xprint("\n *** %s: %s ***\n", exc.__name__, msg) 557 | del tb 558 | except IOError: 559 | exc, msg, tb = sys.exc_info() 560 | xprint("\n *** %s: %s ***\n", exc.__name__, msg) 561 | del tb 562 | 563 | 564 | def main(): 565 | """Program entry point. 566 | """ 567 | global cf_verbose, cf_show_comment, cf_charset 568 | global cf_extract, cf_test_read, cf_test_unrar 569 | global cf_test_memory 570 | 571 | cf_backend = None 572 | pwd = None 573 | 574 | # parse args 575 | try: 576 | opts, args = getopt.getopt(sys.argv[1:], "p:C:hvcxtRMb:") 577 | except getopt.error as ex: 578 | print(str(ex), file=sys.stderr) 579 | sys.exit(1) 580 | 581 | for o, v in opts: 582 | if o == "-p": 583 | pwd = v 584 | elif o == "-h": 585 | xprint(usage) 586 | return 587 | elif o == "-v": 588 | cf_verbose += 1 589 | elif o == "-c": 590 | cf_show_comment = 1 591 | elif o == "-x": 592 | cf_extract = 1 593 | elif o == "-t": 594 | cf_test_read += 1 595 | elif o == "-T": 596 | cf_test_unrar = 1 597 | elif o == "-M": 598 | cf_test_memory = 1 599 | elif o == "-C": 600 | cf_charset = v 601 | elif o == "-b": 602 | cf_backend = v 603 | else: 604 | raise ValueError("unhandled switch: " + o) 605 | 606 | args2 = [] 607 | for a in args: 608 | if a[0] == "@": 609 | for ln in open(a[1:], "r", encoding="utf8"): 610 | fn = ln[:-1] 611 | args2.append(fn) 612 | else: 613 | args2.append(a) 614 | args = args2 615 | 616 | if not args: 617 | xprint(usage) 618 | 619 | if cf_backend: 620 | cf_backend = {"7z": "sevenzip", "7zz": "sevenzip2"}.get(cf_backend, cf_backend) 621 | conf = {"unrar": False, "unar": False, "bsdtar": False, "sevenzip": False, "sevenzip2": False} 622 | assert cf_backend in conf, f"unknown backend: {cf_backend}" 623 | conf[cf_backend] = True 624 | rf.tool_setup(force=True, **conf) 625 | 626 | for fn in args: 627 | test(fn, pwd) 628 | 629 | 630 | if __name__ == "__main__": 631 | try: 632 | main() 633 | except KeyboardInterrupt: 634 | pass 635 | 636 | -------------------------------------------------------------------------------- /etc/requirements.build.txt: -------------------------------------------------------------------------------- 1 | setuptools>=67 2 | wheel>=0.41 3 | twine==4.0.2 4 | tox==4.8.0 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | [bdist_wheel] 3 | universal = 0 4 | 5 | [tool:pytest] 6 | testpaths = test 7 | 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup script for rarfile. 2 | """ 3 | 4 | import re 5 | 6 | from setuptools import setup 7 | 8 | vrx = r"""^__version__ *= *['"]([^'"]+)['"]""" 9 | src = open("rarfile.py").read() 10 | ver = re.search(vrx, src, re.M).group(1) 11 | 12 | ldesc = open("README.rst").read().strip() 13 | sdesc = ldesc.split('\n')[0].split(' - ')[1].strip() 14 | 15 | setup( 16 | name="rarfile", 17 | version=ver, 18 | description=sdesc, 19 | long_description=ldesc, 20 | author="Marko Kreen", 21 | license="ISC", 22 | author_email="markokr@gmail.com", 23 | url="https://github.com/markokr/rarfile", 24 | py_modules=['rarfile'], 25 | keywords=['rar', 'unrar', 'archive'], 26 | python_requires=">=3.6", 27 | classifiers=[ 28 | "Development Status :: 5 - Production/Stable", 29 | "Intended Audience :: Developers", 30 | "License :: OSI Approved :: ISC License (ISCL)", 31 | "Operating System :: OS Independent", 32 | "Programming Language :: Python :: 3", 33 | "Topic :: Software Development :: Libraries :: Python Modules", 34 | "Topic :: System :: Archiving :: Compression", 35 | ] 36 | ) 37 | 38 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/files/ctime0.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/ctime0.rar -------------------------------------------------------------------------------- /test/files/ctime0.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/ctime0.rar 2 | FILE: hdrlen=46 datlen=0 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=0 dec=0 vol=0 5 | crc=0x00000000 (0) date_time=2011-05-10 21:28:47 6 | name=afile.txt 7 | mtime=2011-05-10T21:28:47.899345100 8 | -------------------------------------------------------------------------------- /test/files/ctime1.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/ctime1.rar -------------------------------------------------------------------------------- /test/files/ctime1.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/ctime1.rar 2 | FILE: hdrlen=50 datlen=0 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=0 dec=0 vol=0 5 | crc=0x00000000 (0) date_time=2011-05-10 21:28:47 6 | name=afile.txt 7 | mtime=2011-05-10T21:28:47.899345100 8 | ctime=2011-05-10T21:28:47 9 | -------------------------------------------------------------------------------- /test/files/ctime2.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/ctime2.rar -------------------------------------------------------------------------------- /test/files/ctime2.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/ctime2.rar 2 | FILE: hdrlen=51 datlen=0 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=0 dec=0 vol=0 5 | crc=0x00000000 (0) date_time=2011-05-10 21:28:47 6 | name=afile.txt 7 | mtime=2011-05-10T21:28:47.899345100 8 | ctime=2011-05-10T21:28:47.897843200 9 | -------------------------------------------------------------------------------- /test/files/ctime3.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/ctime3.rar -------------------------------------------------------------------------------- /test/files/ctime3.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/ctime3.rar 2 | FILE: hdrlen=52 datlen=0 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=0 dec=0 vol=0 5 | crc=0x00000000 (0) date_time=2011-05-10 21:28:47 6 | name=afile.txt 7 | mtime=2011-05-10T21:28:47.899345100 8 | ctime=2011-05-10T21:28:47.899328 9 | -------------------------------------------------------------------------------- /test/files/ctime4.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/ctime4.rar -------------------------------------------------------------------------------- /test/files/ctime4.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/ctime4.rar 2 | FILE: hdrlen=53 datlen=0 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=0 dec=0 vol=0 5 | crc=0x00000000 (0) date_time=2011-05-10 21:28:47 6 | name=afile.txt 7 | mtime=2011-05-10T21:28:47.899345100 8 | ctime=2011-05-10T21:28:47.899345100 9 | -------------------------------------------------------------------------------- /test/files/ctime5.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/ctime5.rar -------------------------------------------------------------------------------- /test/files/ctime5.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/ctime5.rar 2 | R5_FILE: hdrlen=60 datlen=2 hdr_extra=27 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=timed.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=2 dec=2 vol=0 8 | crc=0xea5f4713 (3932112659) 9 | date_time=2020-07-30 20:26:59 10 | mtime=2020-07-30T20:26:59.677675904+00:00 11 | ctime=2020-07-30T20:28:19.398867888+00:00 12 | atime=2020-07-30T20:27:10.121196721+00:00 13 | -------------------------------------------------------------------------------- /test/files/rar15-comment-lock.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar15-comment-lock.rar -------------------------------------------------------------------------------- /test/files/rar15-comment-lock.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar15-comment-lock.rar 2 | comment='RARcomment -----' 3 | FILE: hdrlen=72 datlen=7 is=F-- 4 | flags=0x8008:COMMENT,LONG,D64 5 | os=0:DOS ver=15 mode=ARCHIVE meth=3 cmp=7 dec=7 vol=0 6 | crc=0xe27f07a9 (3799975849) date_time=2010-11-03 19:49:32 7 | name=FILE1.TXT 8 | comment='file1comment -----' 9 | FILE: hdrlen=72 datlen=8 is=F-- 10 | flags=0x8008:COMMENT,LONG,D64 11 | os=0:DOS ver=15 mode=ARCHIVE meth=0 cmp=8 dec=8 vol=0 12 | crc=0x3c4306f7 (1011025655) date_time=2010-11-03 19:49:38 13 | name=FILE2.TXT 14 | comment='file2comment -----' 15 | -------------------------------------------------------------------------------- /test/files/rar15-comment.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar15-comment.rar -------------------------------------------------------------------------------- /test/files/rar15-comment.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar15-comment.rar 2 | comment='RARcomment -----' 3 | FILE: hdrlen=72 datlen=7 is=F-- 4 | flags=0x8008:COMMENT,LONG,D64 5 | os=0:DOS ver=15 mode=ARCHIVE meth=3 cmp=7 dec=7 vol=0 6 | crc=0xe27f07a9 (3799975849) date_time=2010-11-03 19:49:32 7 | name=FILE1.TXT 8 | comment='file1comment -----' 9 | FILE: hdrlen=72 datlen=8 is=F-- 10 | flags=0x8008:COMMENT,LONG,D64 11 | os=0:DOS ver=15 mode=ARCHIVE meth=0 cmp=8 dec=8 vol=0 12 | crc=0x3c4306f7 (1011025655) date_time=2010-11-03 19:49:38 13 | name=FILE2.TXT 14 | comment='file2comment -----' 15 | -------------------------------------------------------------------------------- /test/files/rar202-comment-nopsw.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar202-comment-nopsw.rar -------------------------------------------------------------------------------- /test/files/rar202-comment-nopsw.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar202-comment-nopsw.rar 2 | comment='RARcomment' 3 | FILE: hdrlen=66 datlen=7 is=F-- 4 | flags=0x8008:COMMENT,LONG,D64 5 | os=0:DOS ver=20 mode=ARCHIVE meth=0 cmp=7 dec=7 vol=0 6 | crc=0x7a197dba (2048490938) date_time=2010-11-03 00:27:28 7 | name=FILE1.TXT 8 | comment='file1comment' 9 | FILE: hdrlen=66 datlen=7 is=F-- 10 | flags=0x8008:COMMENT,LONG,D64 11 | os=0:DOS ver=20 mode=ARCHIVE meth=0 cmp=7 dec=7 vol=0 12 | crc=0x785fc3e3 (2019541987) date_time=2010-11-03 00:27:34 13 | name=FILE2.TXT 14 | comment='file2comment' 15 | -------------------------------------------------------------------------------- /test/files/rar202-comment-psw.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar202-comment-psw.rar -------------------------------------------------------------------------------- /test/files/rar202-comment-psw.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar202-comment-psw.rar 2 | comment='RARcomment' 3 | FILE: hdrlen=66 datlen=32 is=F-- 4 | flags=0x800c:PASSWORD,COMMENT,LONG,D64 5 | os=0:DOS ver=20 mode=ARCHIVE meth=3 cmp=32 dec=7 vol=0 6 | crc=0x7a197dba (2048490938) date_time=2010-11-03 00:27:28 7 | name=FILE1.TXT 8 | comment='file1comment' 9 | FILE: hdrlen=66 datlen=32 is=F-- 10 | flags=0x800c:PASSWORD,COMMENT,LONG,D64 11 | os=0:DOS ver=20 mode=ARCHIVE meth=3 cmp=32 dec=7 vol=0 12 | crc=0x785fc3e3 (2019541987) date_time=2010-11-03 00:27:34 13 | name=FILE2.TXT 14 | comment='file2comment' 15 | -------------------------------------------------------------------------------- /test/files/rar3-comment-hpsw.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-comment-hpsw.rar -------------------------------------------------------------------------------- /test/files/rar3-comment-hpsw.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-comment-hpsw.rar 2 | comment='RARcomment\n' 3 | FILE: hdrlen=51 datlen=16 is=F-- 4 | flags=0x9424:PASSWORD,SALT,EXTTIME,LONG,D128 5 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=16 dec=0 vol=0 6 | crc=0x00000000 (0) date_time=2010-11-02 10:03:25 7 | name=file1.txt 8 | mtime=2010-11-02T10:03:25 9 | comment='Comment1v2\n' 10 | FILE: hdrlen=51 datlen=16 is=F-- 11 | flags=0x9424:PASSWORD,SALT,EXTTIME,LONG,D128 12 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=16 dec=0 vol=0 13 | crc=0x00000000 (0) date_time=2010-11-02 10:03:25 14 | name=file2.txt 15 | mtime=2010-11-02T10:03:25 16 | comment='Comment2v2\n' 17 | -------------------------------------------------------------------------------- /test/files/rar3-comment-plain.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-comment-plain.rar -------------------------------------------------------------------------------- /test/files/rar3-comment-plain.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-comment-plain.rar 2 | comment='RARcomment\n' 3 | FILE: hdrlen=43 datlen=8 is=F-- 4 | flags=0x9020:EXTTIME,LONG,D128 5 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=8 dec=0 vol=0 6 | crc=0x00000000 (0) date_time=2010-11-02 10:03:25 7 | name=file1.txt 8 | mtime=2010-11-02T10:03:25 9 | comment='Comment1v2\n' 10 | FILE: hdrlen=43 datlen=8 is=F-- 11 | flags=0x9020:EXTTIME,LONG,D128 12 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=8 dec=0 vol=0 13 | crc=0x00000000 (0) date_time=2010-11-02 10:03:25 14 | name=file2.txt 15 | mtime=2010-11-02T10:03:25 16 | comment='Comment2v2\n' 17 | -------------------------------------------------------------------------------- /test/files/rar3-comment-psw.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-comment-psw.rar -------------------------------------------------------------------------------- /test/files/rar3-comment-psw.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-comment-psw.rar 2 | comment='RARcomment\n' 3 | FILE: hdrlen=51 datlen=16 is=F-- 4 | flags=0x9424:PASSWORD,SALT,EXTTIME,LONG,D128 5 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=16 dec=0 vol=0 6 | crc=0x00000000 (0) date_time=2010-11-02 10:03:25 7 | name=file1.txt 8 | mtime=2010-11-02T10:03:25 9 | comment='Comment1v2\n' 10 | FILE: hdrlen=51 datlen=16 is=F-- 11 | flags=0x9424:PASSWORD,SALT,EXTTIME,LONG,D128 12 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=16 dec=0 vol=0 13 | crc=0x00000000 (0) date_time=2010-11-02 10:03:25 14 | name=file2.txt 15 | mtime=2010-11-02T10:03:25 16 | comment='Comment2v2\n' 17 | -------------------------------------------------------------------------------- /test/files/rar3-old.r00: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-old.r00 -------------------------------------------------------------------------------- /test/files/rar3-old.r01: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-old.r01 -------------------------------------------------------------------------------- /test/files/rar3-old.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-old.rar -------------------------------------------------------------------------------- /test/files/rar3-old.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-old.rar 2 | FILE: hdrlen=50 datlen=102310 is=F-- 3 | flags=0x9022:SPLIT_AFTER,EXTTIME,LONG,D128 4 | os=3:UNIX ver=20 mode=-rw-rw-r-- meth=0 cmp=205000 dec=205000 vol=0 5 | crc=0x509ad74c (1352324940) date_time=2016-05-24 11:42:37 6 | name=vols/bigfile.txt 7 | mtime=2016-05-24T11:42:37 8 | FILE: hdrlen=52 datlen=2050 is=F-- 9 | flags=0x9020:EXTTIME,LONG,D128 10 | os=3:UNIX ver=20 mode=-rw-rw-r-- meth=0 cmp=2050 dec=2050 vol=2 11 | crc=0xd08a1f86 (3498712966) date_time=2016-05-24 11:42:43 12 | name=vols/smallfile.txt 13 | mtime=2016-05-24T11:42:43 14 | -------------------------------------------------------------------------------- /test/files/rar3-owner.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-owner.rar -------------------------------------------------------------------------------- /test/files/rar3-owner.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-owner.rar 2 | FILE: hdrlen=47 datlen=2 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=2 dec=2 vol=0 5 | crc=0x6751fc53 (1733426259) date_time=2020-07-30 22:48:11 6 | name=owner1.txt 7 | mtime=2020-07-30T22:48:11.364898100 8 | FILE: hdrlen=47 datlen=2 is=F-- 9 | flags=0x9020:EXTTIME,LONG,D128 10 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=2 dec=2 vol=0 11 | crc=0x4c7caf90 (1283239824) date_time=2020-07-30 22:48:16 12 | name=owner2.txt 13 | mtime=2020-07-30T22:48:16.347603 14 | -------------------------------------------------------------------------------- /test/files/rar3-readonly-unix.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-readonly-unix.rar -------------------------------------------------------------------------------- /test/files/rar3-readonly-unix.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-readonly-unix.rar 2 | FILE: hdrlen=55 datlen=9 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=3:UNIX ver=29 mode=-r--r--r-- meth=0 cmp=9 dec=9 vol=0 5 | crc=0x818d2276 (2173510262) date_time=2020-07-26 22:11:42 6 | name=ro_dir/ro_file.txt 7 | mtime=2020-07-26T22:11:42.312616300 8 | FILE: hdrlen=43 datlen=0 is=-D- 9 | flags=0x90e0:EXTTIME,LONG,DIR 10 | os=3:UNIX ver=20 mode=dr-xr-xr-x meth=0 cmp=0 dec=0 vol=0 11 | crc=0x00000000 (0) date_time=2020-07-26 22:11:42 12 | name=ro_dir/ 13 | mtime=2020-07-26T22:11:42.328617 14 | -------------------------------------------------------------------------------- /test/files/rar3-readonly-win.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-readonly-win.rar -------------------------------------------------------------------------------- /test/files/rar3-readonly-win.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-readonly-win.rar 2 | FILE: hdrlen=55 datlen=9 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=READONLY,ARCHIVE meth=0 cmp=9 dec=9 vol=0 5 | crc=0x818d2276 (2173510262) date_time=2020-07-26 22:11:42 6 | name=ro_dir/ro_file.txt 7 | mtime=2020-07-26T22:11:42.312616300 8 | FILE: hdrlen=43 datlen=0 is=-D- 9 | flags=0x90e0:EXTTIME,LONG,DIR 10 | os=2:WIN ver=20 mode=READONLY,DIRECTORY meth=0 cmp=0 dec=0 vol=0 11 | crc=0x00000000 (0) date_time=2020-07-26 22:11:42 12 | name=ro_dir/ 13 | mtime=2020-07-26T22:11:42.328617 14 | -------------------------------------------------------------------------------- /test/files/rar3-seektest.sfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-seektest.sfx -------------------------------------------------------------------------------- /test/files/rar3-solid.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-solid.rar -------------------------------------------------------------------------------- /test/files/rar3-solid.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-solid.rar 2 | FILE: hdrlen=44 datlen=92 is=F-- 3 | flags=0x9080:EXTTIME,LONG,D1024 4 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=92 dec=2048 vol=0 5 | crc=0xc5b7e6a2 (3317163682) date_time=2011-06-12 12:53:33 6 | name=stest1.txt 7 | mtime=2011-06-12T12:53:33 8 | FILE: hdrlen=44 datlen=14 is=F-- 9 | flags=0x9090:SOLID,EXTTIME,LONG,D1024 10 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=3 cmp=14 dec=2048 vol=0 11 | crc=0xc5b7e6a2 (3317163682) date_time=2011-06-12 12:53:33 12 | name=stest2.txt 13 | mtime=2011-06-12T12:53:33 14 | -------------------------------------------------------------------------------- /test/files/rar3-subdirs.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-subdirs.rar -------------------------------------------------------------------------------- /test/files/rar3-subdirs.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-subdirs.rar 2 | FILE: hdrlen=55 datlen=6 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=6 dec=6 vol=0 5 | crc=0xc904a4c7 (3372524743) date_time=2020-07-20 21:01:44 6 | name=sub/dir2/file2.txt 7 | mtime=2020-07-20T21:01:44.319218800 8 | FILE: hdrlen=63 datlen=8 is=F-- 9 | flags=0x9020:EXTTIME,LONG,D128 10 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=8 dec=8 vol=0 11 | crc=0x71b7247d (1907827837) date_time=2020-07-20 21:02:17 12 | name=sub/with space/long fn.txt 13 | mtime=2020-07-20T21:02:17.499892 14 | FILE: hdrlen=87 datlen=5 is=F-- 15 | flags=0x9220:UNICODE,EXTTIME,LONG,D128 16 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=5 dec=5 vol=0 namecmp=26/50 17 | crc=0x2fec89c1 (804030913) date_time=2020-07-20 21:07:00 18 | name=sub/üȵĩöḋè/file.txt 19 | mtime=2020-07-20T21:07:00.141758600 20 | FILE: hdrlen=55 datlen=6 is=F-- 21 | flags=0x9020:EXTTIME,LONG,D128 22 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=6 dec=6 vol=0 23 | crc=0xe229f704 (3794401028) date_time=2020-07-20 21:01:33 24 | name=sub/dir1/file1.txt 25 | mtime=2020-07-20T21:01:33.875008200 26 | FILE: hdrlen=45 datlen=0 is=-D- 27 | flags=0x90e0:EXTTIME,LONG,DIR 28 | os=3:UNIX ver=20 mode=drwxr-xr-x meth=0 cmp=0 dec=0 vol=0 29 | crc=0x00000000 (0) date_time=2020-07-20 21:01:44 30 | name=sub/dir2/ 31 | mtime=2020-07-20T21:01:44.335219100 32 | FILE: hdrlen=51 datlen=0 is=-D- 33 | flags=0x90e0:EXTTIME,LONG,DIR 34 | os=3:UNIX ver=20 mode=drwxr-xr-x meth=0 cmp=0 dec=0 vol=0 35 | crc=0x00000000 (0) date_time=2020-07-20 21:02:17 36 | name=sub/with space/ 37 | mtime=2020-07-20T21:02:17.511892300 38 | FILE: hdrlen=46 datlen=0 is=-D- 39 | flags=0x90e0:EXTTIME,LONG,DIR 40 | os=3:UNIX ver=20 mode=drwxr-xr-x meth=0 cmp=0 dec=0 vol=0 41 | crc=0x00000000 (0) date_time=2020-07-20 21:01:09 42 | name=sub/empty/ 43 | mtime=2020-07-20T21:01:09.182513600 44 | FILE: hdrlen=67 datlen=0 is=-D- 45 | flags=0x92e0:UNICODE,EXTTIME,LONG,DIR 46 | os=3:UNIX ver=20 mode=drwxr-xr-x meth=0 cmp=0 dec=0 vol=0 namecmp=17/30 47 | crc=0x00000000 (0) date_time=2020-07-20 21:07:00 48 | name=sub/üȵĩöḋè/ 49 | mtime=2020-07-20T21:07:00.153758800 50 | FILE: hdrlen=45 datlen=0 is=-D- 51 | flags=0x90e0:EXTTIME,LONG,DIR 52 | os=3:UNIX ver=20 mode=drwxr-xr-x meth=0 cmp=0 dec=0 vol=0 53 | crc=0x00000000 (0) date_time=2020-07-20 21:01:33 54 | name=sub/dir1/ 55 | mtime=2020-07-20T21:01:33.887008500 56 | FILE: hdrlen=40 datlen=0 is=-D- 57 | flags=0x90e0:EXTTIME,LONG,DIR 58 | os=3:UNIX ver=20 mode=drwxr-xr-x meth=0 cmp=0 dec=0 vol=0 59 | crc=0x00000000 (0) date_time=2020-07-20 21:06:42 60 | name=sub/ 61 | mtime=2020-07-20T21:06:42.241383800 62 | -------------------------------------------------------------------------------- /test/files/rar3-symlink-unix.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-symlink-unix.rar -------------------------------------------------------------------------------- /test/files/rar3-symlink-unix.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-symlink-unix.rar 2 | FILE: hdrlen=46 datlen=8 is=--L 3 | flags=0x90c0:EXTTIME,LONG,D4096 4 | os=3:UNIX ver=20 mode=lrwxrwxrwx meth=0 cmp=8 dec=8 vol=0 5 | crc=0x8b387909 (2335734025) date_time=2020-07-26 23:13:58 6 | name=data_link 7 | mtime=2020-07-26T23:13:58.125453100 8 | FILE: hdrlen=45 datlen=5 is=F-- 9 | flags=0x90c0:EXTTIME,LONG,D4096 10 | os=3:UNIX ver=29 mode=-rw-rw-r-- meth=0 cmp=5 dec=5 vol=0 11 | crc=0xe6c1c582 (3871458690) date_time=2020-07-26 23:13:33 12 | name=data.txt 13 | mtime=2020-07-26T23:13:33.568312800 14 | FILE: hdrlen=48 datlen=12 is=--L 15 | flags=0x90c0:EXTTIME,LONG,D4096 16 | os=3:UNIX ver=20 mode=lrwxrwxrwx meth=0 cmp=12 dec=12 vol=0 17 | crc=0xcfb67605 (3484841477) date_time=2020-07-26 23:32:08 18 | name=random_link 19 | mtime=2020-07-26T23:32:08.896575800 20 | -------------------------------------------------------------------------------- /test/files/rar3-versions.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-versions.rar -------------------------------------------------------------------------------- /test/files/rar3-versions.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-versions.rar 2 | FILE: hdrlen=50 datlen=2 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=0 cmp=2 dec=2 vol=0 5 | crc=0x55679ed1 (1432854225) date_time=2020-07-30 22:52:46 6 | name=versioned.txt 7 | mtime=2020-07-30T22:52:46.436647500 8 | -------------------------------------------------------------------------------- /test/files/rar3-vols.part1.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-vols.part1.rar -------------------------------------------------------------------------------- /test/files/rar3-vols.part1.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-vols.part1.rar 2 | FILE: hdrlen=50 datlen=102310 is=F-- 3 | flags=0x9022:SPLIT_AFTER,EXTTIME,LONG,D128 4 | os=3:UNIX ver=20 mode=-rw-rw-r-- meth=0 cmp=205000 dec=205000 vol=0 5 | crc=0x509ad74c (1352324940) date_time=2016-05-24 11:42:37 6 | name=vols/bigfile.txt 7 | mtime=2016-05-24T11:42:37 8 | FILE: hdrlen=52 datlen=2050 is=F-- 9 | flags=0x9020:EXTTIME,LONG,D128 10 | os=3:UNIX ver=20 mode=-rw-rw-r-- meth=0 cmp=2050 dec=2050 vol=2 11 | crc=0xd08a1f86 (3498712966) date_time=2016-05-24 11:42:43 12 | name=vols/smallfile.txt 13 | mtime=2016-05-24T11:42:43 14 | -------------------------------------------------------------------------------- /test/files/rar3-vols.part2.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-vols.part2.rar -------------------------------------------------------------------------------- /test/files/rar3-vols.part2.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-vols.part2.rar 2 | --- test/files/rar3-vols.part2.rar is middle part of multi-vol archive (Need to start from first volume (current: 1))--- 3 | -------------------------------------------------------------------------------- /test/files/rar3-vols.part3.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar3-vols.part3.rar -------------------------------------------------------------------------------- /test/files/rar3-vols.part3.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar3-vols.part3.rar 2 | --- test/files/rar3-vols.part3.rar is middle part of multi-vol archive (Need to start from first volume (current: 2))--- 3 | -------------------------------------------------------------------------------- /test/files/rar5-blake.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-blake.rar -------------------------------------------------------------------------------- /test/files/rar5-blake.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-blake.rar 2 | comment='RAR5 archive - blake\n' 3 | R5_FILE: hdrlen=76 datlen=55 hdr_extra=46 is=F-- 4 | block_flags=0x0003:EXTRA,DATA 5 | name=stest1.txt 6 | file_flags=0x0000:- 7 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 8 | os=1:UNIX mode=-rw-r--r-- cmp=55 dec=2048 vol=0 9 | blake2sp=7cd5c1ac31f0cf58844a57fb9072c44768dbea1456e37c21e491f4853982ede0 10 | date_time=2011-06-12 09:53:33 11 | mtime=2011-06-12T09:53:33+00:00 12 | R5_FILE: hdrlen=92 datlen=2048 hdr_extra=62 is=F-- 13 | block_flags=0x0003:EXTRA,DATA 14 | name=stest2.txt 15 | file_flags=0x0000:- 16 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 17 | os=1:UNIX mode=-rw-r--r-- cmp=2048 dec=2048 vol=0 18 | blake2sp=7cd5c1ac31f0cf58844a57fb9072c44768dbea1456e37c21e491f4853982ede0 19 | date_time=2011-06-12 09:53:33 20 | mtime=2011-06-12T09:53:33+00:00 21 | ctime=2016-05-22T09:12:33+00:00 22 | atime=2016-05-22T09:12:37+00:00 23 | -------------------------------------------------------------------------------- /test/files/rar5-crc.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-crc.rar -------------------------------------------------------------------------------- /test/files/rar5-crc.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-crc.rar 2 | comment='RAR5 archive - crc\n' 3 | R5_FILE: hdrlen=45 datlen=55 hdr_extra=11 is=F-- 4 | block_flags=0x0003:EXTRA,DATA 5 | name=stest1.txt 6 | file_flags=0x0004:CRC32 7 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 8 | os=1:UNIX mode=-rw-r--r-- cmp=55 dec=2048 vol=0 9 | crc=0xc5b7e6a2 (3317163682) 10 | date_time=2011-06-12 09:53:33 11 | mtime=2011-06-12T09:53:33+00:00 12 | R5_FILE: hdrlen=61 datlen=2048 hdr_extra=27 is=F-- 13 | block_flags=0x0003:EXTRA,DATA 14 | name=stest2.txt 15 | file_flags=0x0004:CRC32 16 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 17 | os=1:UNIX mode=-rw-r--r-- cmp=2048 dec=2048 vol=0 18 | crc=0xc5b7e6a2 (3317163682) 19 | date_time=2011-06-12 09:53:33 20 | mtime=2011-06-12T09:53:33+00:00 21 | ctime=2016-05-22T09:12:33+00:00 22 | atime=2016-05-22T09:12:37+00:00 23 | -------------------------------------------------------------------------------- /test/files/rar5-crc.sfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-crc.sfx -------------------------------------------------------------------------------- /test/files/rar5-dups.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-dups.rar -------------------------------------------------------------------------------- /test/files/rar5-dups.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-dups.rar 2 | R5_FILE: hdrlen=43 datlen=55 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=stest1.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 7 | os=0:WINDOWS mode=ARCHIVE cmp=55 dec=2048 vol=0 8 | crc=0xc5b7e6a2 (3317163682) 9 | date_time=2011-06-12 09:53:33 10 | mtime=2011-06-12T09:53:33+00:00 11 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 12 | block_flags=0x0003:EXTRA,DATA 13 | name=stest2.txt 14 | file_flags=0x0004:CRC32 15 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 16 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 17 | crc=0x00000000 (0) 18 | date_time=2011-06-12 09:53:33 19 | mtime=2011-06-12T09:53:33+00:00 20 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 21 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 22 | block_flags=0x0003:EXTRA,DATA 23 | name=stest3.txt 24 | file_flags=0x0004:CRC32 25 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 26 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 27 | crc=0x00000000 (0) 28 | date_time=2011-06-12 09:53:33 29 | mtime=2011-06-12T09:53:33+00:00 30 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 31 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 32 | block_flags=0x0003:EXTRA,DATA 33 | name=stest4.txt 34 | file_flags=0x0004:CRC32 35 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 36 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 37 | crc=0x00000000 (0) 38 | date_time=2011-06-12 09:53:33 39 | mtime=2011-06-12T09:53:33+00:00 40 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 41 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 42 | block_flags=0x0003:EXTRA,DATA 43 | name=stest5.txt 44 | file_flags=0x0004:CRC32 45 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 46 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 47 | crc=0x00000000 (0) 48 | date_time=2011-06-12 09:53:33 49 | mtime=2011-06-12T09:53:33+00:00 50 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 51 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 52 | block_flags=0x0003:EXTRA,DATA 53 | name=stest6.txt 54 | file_flags=0x0004:CRC32 55 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 56 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 57 | crc=0x00000000 (0) 58 | date_time=2011-06-12 09:53:33 59 | mtime=2011-06-12T09:53:33+00:00 60 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 61 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 62 | block_flags=0x0003:EXTRA,DATA 63 | name=stest7.txt 64 | file_flags=0x0004:CRC32 65 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 66 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 67 | crc=0x00000000 (0) 68 | date_time=2011-06-12 09:53:33 69 | mtime=2011-06-12T09:53:33+00:00 70 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 71 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 72 | block_flags=0x0003:EXTRA,DATA 73 | name=stest8.txt 74 | file_flags=0x0004:CRC32 75 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 76 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 77 | crc=0x00000000 (0) 78 | date_time=2011-06-12 09:53:33 79 | mtime=2011-06-12T09:53:33+00:00 80 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 81 | R5_FILE: hdrlen=58 datlen=0 hdr_extra=26 is=F-- 82 | block_flags=0x0003:EXTRA,DATA 83 | name=stest9.txt 84 | file_flags=0x0004:CRC32 85 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 86 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=2048 vol=0 87 | crc=0x00000000 (0) 88 | date_time=2011-06-12 09:53:33 89 | mtime=2011-06-12T09:53:33+00:00 90 | redir: type=FILE_COPY flags=0:- destination=stest1.txt 91 | -------------------------------------------------------------------------------- /test/files/rar5-hlink.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-hlink.rar -------------------------------------------------------------------------------- /test/files/rar5-hlink.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-hlink.rar 2 | R5_FILE: hdrlen=37 datlen=55 hdr_extra=0 is=F-- 3 | block_flags=0x0002:DATA 4 | name=stest1.txt 5 | file_flags=0x0006:MTIME,CRC32 6 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 7 | os=1:UNIX mode=-rw-rw-r-- cmp=55 dec=2048 vol=0 8 | crc=0xc5b7e6a2 (3317163682) 9 | date_time=2011-06-12 09:53:33 10 | mtime=2011-06-12T09:53:33+00:00 11 | R5_FILE: hdrlen=53 datlen=0 hdr_extra=15 is=F-- 12 | block_flags=0x0003:EXTRA,DATA 13 | name=stest2.txt 14 | file_flags=0x0006:MTIME,CRC32 15 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 16 | os=1:UNIX mode=-rw-rw-r-- cmp=0 dec=2048 vol=0 17 | crc=0x00000000 (0) 18 | date_time=2011-06-12 09:53:33 19 | mtime=2011-06-12T09:53:33+00:00 20 | redir: type=HARD_LINK flags=0:- destination=stest1.txt 21 | R5_FILE: hdrlen=53 datlen=0 hdr_extra=15 is=F-- 22 | block_flags=0x0003:EXTRA,DATA 23 | name=stest3.txt 24 | file_flags=0x0006:MTIME,CRC32 25 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 26 | os=1:UNIX mode=-rw-rw-r-- cmp=0 dec=2048 vol=0 27 | crc=0x00000000 (0) 28 | date_time=2011-06-12 09:53:33 29 | mtime=2011-06-12T09:53:33+00:00 30 | redir: type=HARD_LINK flags=0:- destination=stest1.txt 31 | -------------------------------------------------------------------------------- /test/files/rar5-hpsw.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-hpsw.rar -------------------------------------------------------------------------------- /test/files/rar5-hpsw.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-hpsw.rar 2 | comment='RAR5 archive - hdr-password\n' 3 | R5_FILE: hdrlen=94 datlen=64 hdr_extra=60 is=F-- 4 | block_flags=0x0003:EXTRA,DATA 5 | name=stest1.txt 6 | file_flags=0x0004:CRC32 7 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 8 | os=1:UNIX mode=-rw-r--r-- cmp=64 dec=2048 vol=0 9 | crc=0xc5b7e6a2 (3317163682) 10 | date_time=2011-06-12 09:53:33 11 | mtime=2011-06-12T09:53:33+00:00 12 | algo=0:AES256 enc_flags=0001:CHECKVAL kdf_lg=15 kdf_count=32768 salt=a798cbfb5c85ea540a42d4d4c2872790 iv=ef62e5dedb427780eb0de68de9c00a88 checkval=1d29a48fc21e63ea4a5f40d3 13 | R5_FILE: hdrlen=110 datlen=2048 hdr_extra=76 is=F-- 14 | block_flags=0x0003:EXTRA,DATA 15 | name=stest2.txt 16 | file_flags=0x0004:CRC32 17 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 18 | os=1:UNIX mode=-rw-r--r-- cmp=2048 dec=2048 vol=0 19 | crc=0xc5b7e6a2 (3317163682) 20 | date_time=2011-06-12 09:53:33 21 | mtime=2011-06-12T09:53:33+00:00 22 | ctime=2016-05-22T09:12:33+00:00 23 | atime=2016-05-22T09:12:37+00:00 24 | algo=0:AES256 enc_flags=0001:CHECKVAL kdf_lg=15 kdf_count=32768 salt=a798cbfb5c85ea540a42d4d4c2872790 iv=0c41c02fa9c09e63bb0dda405e7f10b8 checkval=1d29a48fc21e63ea4a5f40d3 25 | -------------------------------------------------------------------------------- /test/files/rar5-owner.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-owner.rar -------------------------------------------------------------------------------- /test/files/rar5-owner.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-owner.rar 2 | R5_FILE: hdrlen=56 datlen=2 hdr_extra=22 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=owner1.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=2 dec=2 vol=0 8 | crc=0x6751fc53 (1733426259) 9 | date_time=2020-07-30 19:48:11 10 | mtime=2020-07-30T19:48:11.364898178+00:00 11 | owner: name=b'bin' group=b'sys' uid=None gid=None 12 | R5_FILE: hdrlen=52 datlen=2 hdr_extra=18 is=F-- 13 | block_flags=0x0003:EXTRA,DATA 14 | name=owner2.txt 15 | file_flags=0x0004:CRC32 16 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 17 | os=1:UNIX mode=-rw-r--r-- cmp=2 dec=2 vol=0 18 | crc=0x4c7caf90 (1283239824) 19 | date_time=2020-07-30 19:48:16 20 | mtime=2020-07-30T19:48:16.347603034+00:00 21 | owner: name=None group=None uid=400 gid=500 22 | -------------------------------------------------------------------------------- /test/files/rar5-psw-blake.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-psw-blake.rar -------------------------------------------------------------------------------- /test/files/rar5-psw-blake.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-psw-blake.rar 2 | comment='RAR5 archive - nohdr-password-blake\n' 3 | R5_FILE: hdrlen=125 datlen=64 hdr_extra=95 is=F-- 4 | block_flags=0x0003:EXTRA,DATA 5 | name=stest1.txt 6 | file_flags=0x0000:- 7 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 8 | os=1:UNIX mode=-rw-r--r-- cmp=64 dec=2048 vol=0 9 | blake2sp=5cdbd3f49f594a35edb63c923b64abdadba3bf1468b6259ee1e1c54ba2f0d65e 10 | date_time=2011-06-12 09:53:33 11 | mtime=2011-06-12T09:53:33+00:00 12 | algo=0:AES256 enc_flags=0003:CHECKVAL,TWEAKED kdf_lg=15 kdf_count=32768 salt=9db5988f74a009519a4073bdfda0e046 iv=4209dcd38ab7be9299ab3e74d3abc760 checkval=3978909499ddfdb80dfc899e 13 | R5_FILE: hdrlen=142 datlen=2048 hdr_extra=111 is=F-- 14 | block_flags=0x0003:EXTRA,DATA 15 | name=stest2.txt 16 | file_flags=0x0000:- 17 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 18 | os=1:UNIX mode=-rw-r--r-- cmp=2048 dec=2048 vol=0 19 | blake2sp=5cdbd3f49f594a35edb63c923b64abdadba3bf1468b6259ee1e1c54ba2f0d65e 20 | date_time=2011-06-12 09:53:33 21 | mtime=2011-06-12T09:53:33+00:00 22 | ctime=2016-05-22T09:12:33+00:00 23 | atime=2016-05-22T09:12:37+00:00 24 | algo=0:AES256 enc_flags=0003:CHECKVAL,TWEAKED kdf_lg=15 kdf_count=32768 salt=9db5988f74a009519a4073bdfda0e046 iv=e420cdee55a90d84a6341ba90d41d6e0 checkval=3978909499ddfdb80dfc899e 25 | -------------------------------------------------------------------------------- /test/files/rar5-psw.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-psw.rar -------------------------------------------------------------------------------- /test/files/rar5-psw.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-psw.rar 2 | comment='RAR5 archive - nohdr-password\n' 3 | R5_FILE: hdrlen=94 datlen=64 hdr_extra=60 is=F-- 4 | block_flags=0x0003:EXTRA,DATA 5 | name=stest1.txt 6 | file_flags=0x0004:CRC32 7 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 8 | os=1:UNIX mode=-rw-r--r-- cmp=64 dec=2048 vol=0 9 | crc=0xba28eeea (3123244778) 10 | date_time=2011-06-12 09:53:33 11 | mtime=2011-06-12T09:53:33+00:00 12 | algo=0:AES256 enc_flags=0003:CHECKVAL,TWEAKED kdf_lg=15 kdf_count=32768 salt=7e982cdd1ae21c36c7a391da8d088a68 iv=a03aad26f4827b87d54c725ce73b6967 checkval=8151cff63649c16e186a220f 13 | R5_FILE: hdrlen=110 datlen=2048 hdr_extra=76 is=F-- 14 | block_flags=0x0003:EXTRA,DATA 15 | name=stest2.txt 16 | file_flags=0x0004:CRC32 17 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 18 | os=1:UNIX mode=-rw-r--r-- cmp=2048 dec=2048 vol=0 19 | crc=0xba28eeea (3123244778) 20 | date_time=2011-06-12 09:53:33 21 | mtime=2011-06-12T09:53:33+00:00 22 | ctime=2016-05-22T09:12:33+00:00 23 | atime=2016-05-22T09:12:37+00:00 24 | algo=0:AES256 enc_flags=0003:CHECKVAL,TWEAKED kdf_lg=15 kdf_count=32768 salt=7e982cdd1ae21c36c7a391da8d088a68 iv=df83fa0cd86e88b8b6f851467d7949d2 checkval=8151cff63649c16e186a220f 25 | -------------------------------------------------------------------------------- /test/files/rar5-quick-open.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-quick-open.rar -------------------------------------------------------------------------------- /test/files/rar5-quick-open.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-quick-open.rar 2 | R5_FILE: hdrlen=37 datlen=55 hdr_extra=0 is=F-- 3 | block_flags=0x0002:DATA 4 | name=stest1.txt 5 | file_flags=0x0006:MTIME,CRC32 6 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=55 dec=2048 vol=0 8 | crc=0xc5b7e6a2 (3317163682) 9 | date_time=2011-06-12 09:53:33 10 | mtime=2011-06-12T09:53:33+00:00 11 | R5_FILE: hdrlen=37 datlen=55 hdr_extra=0 is=F-- 12 | block_flags=0x0002:DATA 13 | name=stest2.txt 14 | file_flags=0x0006:MTIME,CRC32 15 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 16 | os=1:UNIX mode=-rw-r--r-- cmp=55 dec=2048 vol=0 17 | crc=0xc5b7e6a2 (3317163682) 18 | date_time=2011-06-12 09:53:33 19 | mtime=2011-06-12T09:53:33+00:00 20 | -------------------------------------------------------------------------------- /test/files/rar5-readonly-unix.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-readonly-unix.rar -------------------------------------------------------------------------------- /test/files/rar5-readonly-unix.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-readonly-unix.rar 2 | R5_FILE: hdrlen=53 datlen=9 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=ro_dir/ro_file.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=1:UNIX mode=-r--r--r-- cmp=9 dec=9 vol=0 8 | crc=0x818d2276 (2173510262) 9 | date_time=2020-07-26 19:11:42 10 | mtime=2020-07-26T19:11:42.312616322+00:00 11 | R5_FILE: hdrlen=39 datlen=0 hdr_extra=11 is=-D- 12 | block_flags=0x0003:EXTRA,DATA 13 | name=ro_dir/ 14 | file_flags=0x0005:DIR,CRC32 15 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 16 | os=1:UNIX mode=dr-xr-xr-x cmp=0 dec=0 vol=0 17 | crc=0x00000000 (0) 18 | date_time=2020-07-26 19:11:42 19 | mtime=2020-07-26T19:11:42.328617082+00:00 20 | -------------------------------------------------------------------------------- /test/files/rar5-readonly-win.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-readonly-win.rar -------------------------------------------------------------------------------- /test/files/rar5-readonly-win.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-readonly-win.rar 2 | FILE: hdrlen=55 datlen=9 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=2:WIN ver=29 mode=READONLY,ARCHIVE meth=0 cmp=9 dec=9 vol=0 5 | crc=0x818d2276 (2173510262) date_time=2020-07-26 22:11:42 6 | name=ro_dir/ro_file.txt 7 | mtime=2020-07-26T22:11:42.312616300 8 | FILE: hdrlen=43 datlen=0 is=-D- 9 | flags=0x90e0:EXTTIME,LONG,DIR 10 | os=2:WIN ver=20 mode=READONLY,DIRECTORY meth=0 cmp=0 dec=0 vol=0 11 | crc=0x00000000 (0) date_time=2020-07-26 22:11:42 12 | name=ro_dir/ 13 | mtime=2020-07-26T22:11:42.328617 14 | -------------------------------------------------------------------------------- /test/files/rar5-solid-qo.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-solid-qo.rar -------------------------------------------------------------------------------- /test/files/rar5-solid-qo.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-solid-qo.rar 2 | R5_FILE: hdrlen=45 datlen=59 hdr_extra=0 is=F-- 3 | block_flags=0x0002:DATA 4 | name=somedir/stest1.txt 5 | file_flags=0x0006:MTIME,CRC32 6 | cmp_algo=0 cmp_meth=3 dict=3 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=59 dec=2048 vol=0 8 | crc=0xc5b7e6a2 (3317163682) 9 | date_time=2016-05-22 09:48:36 10 | mtime=2016-05-22T09:48:36+00:00 11 | R5_FILE: hdrlen=37 datlen=12 hdr_extra=0 is=F-- 12 | block_flags=0x0002:DATA 13 | name=stest1.txt 14 | file_flags=0x0006:MTIME,CRC32 15 | cmp_algo=0 cmp_meth=3 dict=3 solid=True 16 | os=1:UNIX mode=-rw-r--r-- cmp=12 dec=2048 vol=0 17 | crc=0xc5b7e6a2 (3317163682) 18 | date_time=2011-06-12 09:53:33 19 | mtime=2011-06-12T09:53:33+00:00 20 | R5_FILE: hdrlen=45 datlen=12 hdr_extra=0 is=F-- 21 | block_flags=0x0002:DATA 22 | name=somedir/stest2.txt 23 | file_flags=0x0006:MTIME,CRC32 24 | cmp_algo=0 cmp_meth=3 dict=3 solid=True 25 | os=1:UNIX mode=-rw-r--r-- cmp=12 dec=2048 vol=0 26 | crc=0xc5b7e6a2 (3317163682) 27 | date_time=2016-05-22 09:48:36 28 | mtime=2016-05-22T09:48:36+00:00 29 | R5_FILE: hdrlen=37 datlen=12 hdr_extra=0 is=F-- 30 | block_flags=0x0002:DATA 31 | name=stest2.txt 32 | file_flags=0x0006:MTIME,CRC32 33 | cmp_algo=0 cmp_meth=3 dict=3 solid=True 34 | os=1:UNIX mode=-rw-r--r-- cmp=12 dec=2048 vol=0 35 | crc=0xc5b7e6a2 (3317163682) 36 | date_time=2011-06-12 09:53:33 37 | mtime=2011-06-12T09:53:33+00:00 38 | -------------------------------------------------------------------------------- /test/files/rar5-solid.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-solid.rar -------------------------------------------------------------------------------- /test/files/rar5-solid.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-solid.rar 2 | R5_FILE: hdrlen=33 datlen=58 hdr_extra=0 is=F-- 3 | block_flags=0x0002:DATA 4 | name=stest1.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=3 dict=3 solid=False 7 | os=1:UNIX mode=-rw-rw-rw- cmp=58 dec=2048 vol=0 8 | crc=0xc5b7e6a2 (3317163682) 9 | R5_FILE: hdrlen=33 datlen=13 hdr_extra=0 is=F-- 10 | block_flags=0x0002:DATA 11 | name=stest2.txt 12 | file_flags=0x0004:CRC32 13 | cmp_algo=0 cmp_meth=3 dict=3 solid=True 14 | os=1:UNIX mode=-rw-rw-rw- cmp=13 dec=2048 vol=0 15 | crc=0xc5b7e6a2 (3317163682) 16 | -------------------------------------------------------------------------------- /test/files/rar5-subdirs.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-subdirs.rar -------------------------------------------------------------------------------- /test/files/rar5-subdirs.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-subdirs.rar 2 | R5_FILE: hdrlen=53 datlen=6 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=sub/dir2/file2.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=6 dec=6 vol=0 8 | crc=0xc904a4c7 (3372524743) 9 | date_time=2020-07-20 18:01:44 10 | mtime=2020-07-20T18:01:44.319218821+00:00 11 | R5_FILE: hdrlen=61 datlen=8 hdr_extra=11 is=F-- 12 | block_flags=0x0003:EXTRA,DATA 13 | name=sub/with space/long fn.txt 14 | file_flags=0x0004:CRC32 15 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 16 | os=1:UNIX mode=-rw-r--r-- cmp=8 dec=8 vol=0 17 | crc=0x71b7247d (1907827837) 18 | date_time=2020-07-20 18:02:17 19 | mtime=2020-07-20T18:02:17.499892070+00:00 20 | R5_FILE: hdrlen=61 datlen=5 hdr_extra=11 is=F-- 21 | block_flags=0x0003:EXTRA,DATA 22 | name=sub/üȵĩöḋè/file.txt 23 | file_flags=0x0004:CRC32 24 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 25 | os=1:UNIX mode=-rw-r--r-- cmp=5 dec=5 vol=0 26 | crc=0x2fec89c1 (804030913) 27 | date_time=2020-07-20 18:07:00 28 | mtime=2020-07-20T18:07:00.141758621+00:00 29 | R5_FILE: hdrlen=53 datlen=6 hdr_extra=11 is=F-- 30 | block_flags=0x0003:EXTRA,DATA 31 | name=sub/dir1/file1.txt 32 | file_flags=0x0004:CRC32 33 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 34 | os=1:UNIX mode=-rw-r--r-- cmp=6 dec=6 vol=0 35 | crc=0xe229f704 (3794401028) 36 | date_time=2020-07-20 18:01:33 37 | mtime=2020-07-20T18:01:33.875008284+00:00 38 | R5_FILE: hdrlen=41 datlen=0 hdr_extra=11 is=-D- 39 | block_flags=0x0003:EXTRA,DATA 40 | name=sub/dir2/ 41 | file_flags=0x0005:DIR,CRC32 42 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 43 | os=1:UNIX mode=drwxr-xr-x cmp=0 dec=0 vol=0 44 | crc=0x00000000 (0) 45 | date_time=2020-07-20 18:01:44 46 | mtime=2020-07-20T18:01:44.335219144+00:00 47 | R5_FILE: hdrlen=47 datlen=0 hdr_extra=11 is=-D- 48 | block_flags=0x0003:EXTRA,DATA 49 | name=sub/with space/ 50 | file_flags=0x0005:DIR,CRC32 51 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 52 | os=1:UNIX mode=drwxr-xr-x cmp=0 dec=0 vol=0 53 | crc=0x00000000 (0) 54 | date_time=2020-07-20 18:02:17 55 | mtime=2020-07-20T18:02:17.511892315+00:00 56 | R5_FILE: hdrlen=42 datlen=0 hdr_extra=11 is=-D- 57 | block_flags=0x0003:EXTRA,DATA 58 | name=sub/empty/ 59 | file_flags=0x0005:DIR,CRC32 60 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 61 | os=1:UNIX mode=drwxr-xr-x cmp=0 dec=0 vol=0 62 | crc=0x00000000 (0) 63 | date_time=2020-07-20 18:01:09 64 | mtime=2020-07-20T18:01:09.182513654+00:00 65 | R5_FILE: hdrlen=50 datlen=0 hdr_extra=11 is=-D- 66 | block_flags=0x0003:EXTRA,DATA 67 | name=sub/üȵĩöḋè/ 68 | file_flags=0x0005:DIR,CRC32 69 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 70 | os=1:UNIX mode=drwxr-xr-x cmp=0 dec=0 vol=0 71 | crc=0x00000000 (0) 72 | date_time=2020-07-20 18:07:00 73 | mtime=2020-07-20T18:07:00.153758873+00:00 74 | R5_FILE: hdrlen=41 datlen=0 hdr_extra=11 is=-D- 75 | block_flags=0x0003:EXTRA,DATA 76 | name=sub/dir1/ 77 | file_flags=0x0005:DIR,CRC32 78 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 79 | os=1:UNIX mode=drwxr-xr-x cmp=0 dec=0 vol=0 80 | crc=0x00000000 (0) 81 | date_time=2020-07-20 18:01:33 82 | mtime=2020-07-20T18:01:33.887008525+00:00 83 | R5_FILE: hdrlen=36 datlen=0 hdr_extra=11 is=-D- 84 | block_flags=0x0003:EXTRA,DATA 85 | name=sub/ 86 | file_flags=0x0005:DIR,CRC32 87 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 88 | os=1:UNIX mode=drwxr-xr-x cmp=0 dec=0 vol=0 89 | crc=0x00000000 (0) 90 | date_time=2020-07-20 18:06:42 91 | mtime=2020-07-20T18:06:42.241383868+00:00 92 | -------------------------------------------------------------------------------- /test/files/rar5-symlink-unix.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-symlink-unix.rar -------------------------------------------------------------------------------- /test/files/rar5-symlink-unix.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-symlink-unix.rar 2 | R5_FILE: hdrlen=55 datlen=0 hdr_extra=24 is=--L 3 | block_flags=0x0003:EXTRA,DATA 4 | name=data_link 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=8 solid=False 7 | os=1:UNIX mode=lrwxrwxrwx cmp=0 dec=8 vol=0 8 | crc=0x00000000 (0) 9 | date_time=2020-07-26 20:13:58 10 | mtime=2020-07-26T20:13:58.125453113+00:00 11 | redir: type=UNIX_SYMLINK flags=0:- destination=data.txt 12 | R5_FILE: hdrlen=43 datlen=5 hdr_extra=11 is=F-- 13 | block_flags=0x0003:EXTRA,DATA 14 | name=data.txt 15 | file_flags=0x0004:CRC32 16 | cmp_algo=0 cmp_meth=0 dict=8 solid=False 17 | os=1:UNIX mode=-rw-rw-r-- cmp=5 dec=5 vol=0 18 | crc=0xe6c1c582 (3871458690) 19 | date_time=2020-07-26 20:13:33 20 | mtime=2020-07-26T20:13:33.568312816+00:00 21 | R5_FILE: hdrlen=61 datlen=0 hdr_extra=28 is=--L 22 | block_flags=0x0003:EXTRA,DATA 23 | name=random_link 24 | file_flags=0x0004:CRC32 25 | cmp_algo=0 cmp_meth=0 dict=8 solid=False 26 | os=1:UNIX mode=lrwxrwxrwx cmp=0 dec=12 vol=0 27 | crc=0x00000000 (0) 28 | date_time=2020-07-26 20:32:08 29 | mtime=2020-07-26T20:32:08.896575845+00:00 30 | redir: type=UNIX_SYMLINK flags=0:- destination=../random123 31 | -------------------------------------------------------------------------------- /test/files/rar5-symlink-win.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-symlink-win.rar -------------------------------------------------------------------------------- /test/files/rar5-symlink-win.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-symlink-win.rar 2 | R5_FILE: hdrlen=49 datlen=9 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=content/file.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=8 solid=False 7 | os=0:WINDOWS mode=ARCHIVE cmp=9 dec=9 vol=0 8 | crc=0x35f39d4d (905157965) 9 | date_time=2020-07-27 17:39:15 10 | mtime=2020-07-27T17:39:15.672228900+00:00 11 | R5_FILE: hdrlen=64 datlen=0 hdr_extra=29 is=--L 12 | block_flags=0x0003:EXTRA,DATA 13 | name=links/bad_link 14 | file_flags=0x0004:CRC32 15 | cmp_algo=0 cmp_meth=0 dict=8 solid=False 16 | os=0:WINDOWS mode=ARCHIVE,REPARSE_POINT cmp=0 dec=0 vol=0 17 | crc=0x00000000 (0) 18 | date_time=2020-07-27 17:43:43 19 | mtime=2020-07-27T17:43:43.625091100+00:00 20 | redir: type=WINDOWS_SYMLINK flags=0:- destination=../../missing 21 | R5_FILE: hdrlen=71 datlen=0 hdr_extra=35 is=--L 22 | block_flags=0x0003:EXTRA,DATA 23 | name=links/file_link 24 | file_flags=0x0004:CRC32 25 | cmp_algo=0 cmp_meth=0 dict=8 solid=False 26 | os=0:WINDOWS mode=ARCHIVE,REPARSE_POINT cmp=0 dec=0 vol=0 27 | crc=0x00000000 (0) 28 | date_time=2020-07-27 17:40:02 29 | mtime=2020-07-27T17:40:02.484275100+00:00 30 | redir: type=WINDOWS_SYMLINK flags=0:- destination=../content/file.txt 31 | R5_FILE: hdrlen=43 datlen=0 hdr_extra=11 is=-D- 32 | block_flags=0x0003:EXTRA,DATA 33 | name=content/dir1/ 34 | file_flags=0x0005:DIR,CRC32 35 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 36 | os=0:WINDOWS mode=DIRECTORY cmp=0 dec=0 vol=0 37 | crc=0x00000000 (0) 38 | date_time=2020-07-27 17:39:19 39 | mtime=2020-07-27T17:39:19.969920300+00:00 40 | R5_FILE: hdrlen=43 datlen=0 hdr_extra=11 is=-D- 41 | block_flags=0x0003:EXTRA,DATA 42 | name=content/dir2/ 43 | file_flags=0x0005:DIR,CRC32 44 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 45 | os=0:WINDOWS mode=DIRECTORY cmp=0 dec=0 vol=0 46 | crc=0x00000000 (0) 47 | date_time=2020-07-27 17:39:21 48 | mtime=2020-07-27T17:39:21.062348800+00:00 49 | R5_FILE: hdrlen=91 datlen=0 hdr_extra=52 is=--L 50 | block_flags=0x0003:EXTRA,DATA 51 | name=links/dir_junction 52 | file_flags=0x0005:DIR,CRC32 53 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 54 | os=0:WINDOWS mode=DIRECTORY,REPARSE_POINT cmp=0 dec=0 vol=0 55 | crc=0x00000000 (0) 56 | date_time=2020-07-27 17:40:48 57 | mtime=2020-07-27T17:40:48.345425800+00:00 58 | redir: type=WINDOWS_JUNCTION flags=1:DIR destination=/??/C:/Users/User/stuff/content/dir2 59 | R5_FILE: hdrlen=66 datlen=0 hdr_extra=31 is=--L 60 | block_flags=0x0003:EXTRA,DATA 61 | name=links/dir_link 62 | file_flags=0x0005:DIR,CRC32 63 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 64 | os=0:WINDOWS mode=DIRECTORY,REPARSE_POINT cmp=0 dec=0 vol=0 65 | crc=0x00000000 (0) 66 | date_time=2020-07-27 17:40:28 67 | mtime=2020-07-27T17:40:28.452838+00:00 68 | redir: type=WINDOWS_SYMLINK flags=1:DIR destination=../content/dir1 69 | R5_FILE: hdrlen=38 datlen=0 hdr_extra=11 is=-D- 70 | block_flags=0x0003:EXTRA,DATA 71 | name=content/ 72 | file_flags=0x0005:DIR,CRC32 73 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 74 | os=0:WINDOWS mode=DIRECTORY cmp=0 dec=0 vol=0 75 | crc=0x00000000 (0) 76 | date_time=2020-07-27 17:43:17 77 | mtime=2020-07-27T17:43:17.077898200+00:00 78 | R5_FILE: hdrlen=36 datlen=0 hdr_extra=11 is=-D- 79 | block_flags=0x0003:EXTRA,DATA 80 | name=links/ 81 | file_flags=0x0005:DIR,CRC32 82 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 83 | os=0:WINDOWS mode=DIRECTORY cmp=0 dec=0 vol=0 84 | crc=0x00000000 (0) 85 | date_time=2020-07-27 17:43:43 86 | mtime=2020-07-27T17:43:43.625091100+00:00 87 | -------------------------------------------------------------------------------- /test/files/rar5-times.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-times.rar -------------------------------------------------------------------------------- /test/files/rar5-times.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-times.rar 2 | R5_FILE: hdrlen=45 datlen=55 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=stest1.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=3 dict=0 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=55 dec=2048 vol=0 8 | crc=0xc5b7e6a2 (3317163682) 9 | date_time=2011-06-12 09:53:33 10 | mtime=2011-06-12T09:53:33+00:00 11 | atime=2016-05-22T09:12:36+00:00 12 | -------------------------------------------------------------------------------- /test/files/rar5-times2.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-times2.rar -------------------------------------------------------------------------------- /test/files/rar5-times2.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-times2.rar 2 | R5_FILE: hdrlen=42 datlen=0 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=afile.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=0:WINDOWS mode=ARCHIVE cmp=0 dec=0 vol=0 8 | crc=0x00000000 (0) 9 | date_time=2011-05-10 18:28:47 10 | mtime=2011-05-10T18:28:47.899345100+00:00 11 | -------------------------------------------------------------------------------- /test/files/rar5-versions.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-versions.rar -------------------------------------------------------------------------------- /test/files/rar5-versions.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-versions.rar 2 | R5_FILE: hdrlen=48 datlen=2 hdr_extra=11 is=F-- 3 | block_flags=0x0003:EXTRA,DATA 4 | name=versioned.txt 5 | file_flags=0x0004:CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=1:UNIX mode=-rw-r--r-- cmp=2 dec=2 vol=0 8 | crc=0x55679ed1 (1432854225) 9 | date_time=2020-07-30 19:52:46 10 | mtime=2020-07-30T19:52:46.436647570+00:00 11 | -------------------------------------------------------------------------------- /test/files/rar5-vols.part1.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-vols.part1.rar -------------------------------------------------------------------------------- /test/files/rar5-vols.part1.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-vols.part1.rar 2 | R5_FILE: hdrlen=45 datlen=205000 hdr_extra=0 is=F-- 3 | block_flags=0x0012:DATA,SPLIT_AFTER 4 | name=vols/bigfile.txt 5 | file_flags=0x0006:MTIME,CRC32 6 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 7 | os=1:UNIX mode=-rw-rw-r-- cmp=205000 dec=205000 vol=0 8 | crc=0x509ad74c (1352324940) 9 | date_time=2016-05-24 08:42:37 10 | mtime=2016-05-24T08:42:37+00:00 11 | R5_FILE: hdrlen=45 datlen=2050 hdr_extra=0 is=F-- 12 | block_flags=0x0002:DATA 13 | name=vols/smallfile.txt 14 | file_flags=0x0006:MTIME,CRC32 15 | cmp_algo=0 cmp_meth=0 dict=0 solid=False 16 | os=1:UNIX mode=-rw-rw-r-- cmp=2050 dec=2050 vol=2 17 | crc=0xd08a1f86 (3498712966) 18 | date_time=2016-05-24 08:42:43 19 | mtime=2016-05-24T08:42:43+00:00 20 | -------------------------------------------------------------------------------- /test/files/rar5-vols.part2.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-vols.part2.rar -------------------------------------------------------------------------------- /test/files/rar5-vols.part2.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-vols.part2.rar 2 | --- test/files/rar5-vols.part2.rar is middle part of multi-vol archive (Need to start from first volume (current: 1))--- 3 | -------------------------------------------------------------------------------- /test/files/rar5-vols.part3.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/rar5-vols.part3.rar -------------------------------------------------------------------------------- /test/files/rar5-vols.part3.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/rar5-vols.part3.rar 2 | --- test/files/rar5-vols.part3.rar is middle part of multi-vol archive (Need to start from first volume (current: 2))--- 3 | -------------------------------------------------------------------------------- /test/files/seektest.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/seektest.rar -------------------------------------------------------------------------------- /test/files/seektest.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/seektest.rar 2 | FILE: hdrlen=44 datlen=90 is=F-- 3 | flags=0x9020:EXTTIME,LONG,D128 4 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=5 cmp=90 dec=2048 vol=0 5 | crc=0xc5b7e6a2 (3317163682) date_time=2011-06-12 12:53:33 6 | name=stest1.txt 7 | mtime=2011-06-12T12:53:33 8 | FILE: hdrlen=44 datlen=2048 is=F-- 9 | flags=0x9020:EXTTIME,LONG,D128 10 | os=3:UNIX ver=20 mode=-rw-r--r-- meth=0 cmp=2048 dec=2048 vol=0 11 | crc=0xc5b7e6a2 (3317163682) date_time=2011-06-12 12:53:33 12 | name=stest2.txt 13 | mtime=2011-06-12T12:53:33 14 | -------------------------------------------------------------------------------- /test/files/unicode.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/unicode.rar -------------------------------------------------------------------------------- /test/files/unicode.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/unicode.rar 2 | FILE: hdrlen=54 datlen=17 is=F-- 3 | flags=0x8080:LONG,D1024 4 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=5 cmp=17 dec=2 vol=0 5 | crc=0x6751fc53 (1733426259) date_time=2011-07-06 16:48:04 6 | name=уииоотивл.txt 7 | FILE: hdrlen=52 datlen=13 is=F-- 8 | flags=0x8090:SOLID,LONG,D1024 9 | os=3:UNIX ver=29 mode=-rw-r--r-- meth=5 cmp=13 dec=2 vol=0 10 | crc=0x6751fc53 (1733426259) date_time=2011-07-06 16:48:04 11 | name=𝐀𝐁𝐁𝐂.txt 12 | -------------------------------------------------------------------------------- /test/files/unicode2.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokr/rarfile/db1df339574e76dafb8457e848a09c3c074b03a0/test/files/unicode2.rar -------------------------------------------------------------------------------- /test/files/unicode2.rar.exp: -------------------------------------------------------------------------------- 1 | Archive: test/files/unicode2.rar 2 | FILE: hdrlen=62 datlen=2 is=F-- 3 | flags=0x8220:UNICODE,LONG,D128 4 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=2 dec=2 vol=0 namecmp=12/30 5 | crc=0x6751fc53 (1733426259) date_time=2011-07-06 16:48:04 6 | name=𝐀𝐁𝐁𝐂.txt 7 | FILE: hdrlen=59 datlen=2 is=F-- 8 | flags=0x8220:UNICODE,LONG,D128 9 | os=2:WIN ver=29 mode=ARCHIVE meth=0 cmp=2 dec=2 vol=0 namecmp=13/27 10 | crc=0x6751fc53 (1733426259) date_time=2011-07-06 16:48:04 11 | name=уииоотивл.txt 12 | -------------------------------------------------------------------------------- /test/run_dump.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | PYTHON="$1" 4 | tag="$2" 5 | 6 | test -n "$tag" || { echo "usage: $0 PY TAG"; exit 1; } 7 | 8 | PYTHONIOENCODING=utf8; export PYTHONIOENCODING 9 | #PYTHONUTF8=1; export PYTHONUTF8 10 | 11 | mkdir -p tmp 12 | diffs="tmp/output.$tag.diffs" 13 | rm -f "$diffs" 14 | 15 | quiet="" 16 | quiet="1" 17 | 18 | vprintf=printf 19 | vecho=echo 20 | 21 | if test -n "$quiet"; then 22 | echo "[$tag] testing structure dump" 23 | vprintf=true 24 | vecho=true 25 | fi 26 | 27 | result=0 28 | for f in test/files/*.rar; do 29 | $vprintf "%s -> %-30s .. " "$tag" "$f" 30 | "$PYTHON" dumprar.py -v -ppassword "$f" > "$f.$tag" 31 | if diff -uw "$f.exp" "$f.$tag" > /dev/null; then 32 | $vecho "ok" 33 | rm -f "$f.$tag" 34 | else 35 | $vecho "FAIL" 36 | errmsg="FAILED" 37 | case "$f" in 38 | *-hpsw.rar) errmsg="failed-nocrypto";; 39 | *) result=1;; 40 | esac 41 | if test -n "$quiet"; then 42 | printf "[%s] %-30s .. ${errmsg}\n" "$tag" "$f" 43 | fi 44 | echo "#### $py ####" >> "$diffs" 45 | diff -uw "$f.exp" "$f.$tag" >> "$diffs" 46 | fi 47 | done 48 | 49 | test "$result" = "0" || echo "Diffs: ${diffs}" 50 | 51 | exit $result 52 | 53 | -------------------------------------------------------------------------------- /test/run_dump_all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | JAVA_OPTIONS="-Dpython.path=`pwd`/.." 4 | export JAVA_OPTIONS 5 | 6 | plist="python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 pypy3.9 pypy3.10" 7 | 8 | result=0 9 | for py in $plist; do 10 | if which $py > /dev/null; then 11 | ./test/run_dump.sh "$py" "$py" || result=1 12 | echo "" 13 | else 14 | echo $py not available 15 | echo "" 16 | fi 17 | done 18 | 19 | -------------------------------------------------------------------------------- /test/test_api.py: -------------------------------------------------------------------------------- 1 | """API tests. 2 | """ 3 | 4 | import io 5 | import os 6 | from pathlib import Path 7 | 8 | import pytest 9 | 10 | import rarfile 11 | 12 | # 13 | # test start 14 | # 15 | 16 | def test_not_rar(): 17 | with pytest.raises(rarfile.NotRarFile): 18 | rarfile.RarFile("rarfile.py", "r") 19 | with pytest.raises(rarfile.NotRarFile): 20 | with open("rarfile.py", "rb") as f: 21 | rarfile.RarFile(f, "r") 22 | 23 | 24 | def test_bad_arc_mode_w(): 25 | with pytest.raises(NotImplementedError): 26 | rarfile.RarFile("test/files/rar3-comment-plain.rar", "w") 27 | 28 | 29 | def test_bad_arc_mode_rb(): 30 | with pytest.raises(NotImplementedError): 31 | rarfile.RarFile("test/files/rar3-comment-plain.rar", "rb") 32 | 33 | 34 | def test_bad_errs(): 35 | with pytest.raises(ValueError): 36 | rarfile.RarFile("test/files/rar3-comment-plain.rar", "r", errors="foo") 37 | 38 | 39 | def test_errors_param(): 40 | with open("test/files/rar3-comment-plain.rar", "rb") as f: 41 | data = f.read() 42 | buf = io.BytesIO(data[:17]) 43 | with rarfile.RarFile(buf, "r", errors="stop") as rf: 44 | assert rf.namelist() == [] 45 | with pytest.raises(rarfile.BadRarFile): 46 | rarfile.RarFile(buf, "r", errors="strict") 47 | 48 | 49 | def test_bad_open_mode_w(): 50 | rf = rarfile.RarFile("test/files/rar3-comment-plain.rar") 51 | with pytest.raises(NotImplementedError): 52 | rf.open("qwe", "w") 53 | 54 | 55 | def test_bad_open_psw(): 56 | rf = rarfile.RarFile("test/files/rar3-comment-psw.rar") 57 | with pytest.raises(rarfile.PasswordRequired): 58 | rf.open("file1.txt") 59 | 60 | 61 | def test_bad_filelike(): 62 | with pytest.raises(ValueError): 63 | rarfile.is_rarfile(bytearray(10)) 64 | 65 | 66 | def test_open_psw_late_rar3(): 67 | rf = rarfile.RarFile("test/files/rar3-comment-psw.rar") 68 | d1 = rf.open("file1.txt", "r", "password").read() 69 | d2 = rf.open("file1.txt", "r", b"password").read() 70 | assert d1 == d2 71 | 72 | 73 | def test_open_psw_late_rar5(): 74 | rf = rarfile.RarFile("test/files/rar5-psw.rar") 75 | rf.open("stest1.txt", "r", "password").read() 76 | rf.open("stest1.txt", "r", b"password").read() 77 | 78 | 79 | def test_open_pathlib_path(): 80 | rf = rarfile.RarFile("test/files/rar5-psw.rar") 81 | rf.open(Path("stest1.txt"), "r", "password").read() 82 | 83 | 84 | def test_read_psw_late_rar3(): 85 | rf = rarfile.RarFile("test/files/rar3-comment-psw.rar") 86 | rf.read("file1.txt", "password") 87 | rf.read("file1.txt", b"password") 88 | 89 | 90 | def test_read_psw_late_rar5(): 91 | rf = rarfile.RarFile("test/files/rar5-psw.rar") 92 | rf.read("stest1.txt", "password") 93 | rf.read("stest1.txt", b"password") 94 | 95 | 96 | def test_open_psw_late(): 97 | rf = rarfile.RarFile("test/files/rar5-psw.rar") 98 | with pytest.raises(rarfile.BadRarFile): 99 | rf.read("stest1.txt", "password222") 100 | 101 | 102 | def test_create_from_pathlib_path(): 103 | # Make sure we can open both relative and absolute Paths 104 | rarfile.RarFile(Path("test/files/rar5-psw.rar")) 105 | rarfile.RarFile(Path("test/files/rar5-psw.rar").resolve()) 106 | 107 | 108 | def test_detection(): 109 | assert rarfile.is_rarfile("test/files/ctime4.rar.exp") is False 110 | assert rarfile.is_rarfile("test/files/ctime4.rar") is True 111 | assert rarfile.is_rarfile("test/files/rar5-crc.rar") is True 112 | 113 | assert rarfile.is_rarfile(Path("test/files/rar5-crc.rar")) is True 114 | 115 | assert rarfile.is_rarfile("test/files/_missing_.rar") is False 116 | 117 | 118 | def test_getinfo(): 119 | with rarfile.RarFile("test/files/rar5-crc.rar") as rf: 120 | inf = rf.getinfo("stest1.txt") 121 | assert isinstance(inf, rarfile.RarInfo) 122 | assert rf.getinfo(inf) is inf 123 | with pytest.raises(rarfile.NoRarEntry): 124 | rf.getinfo("missing.txt") 125 | 126 | def test_signature_error(): 127 | with pytest.raises(rarfile.NotRarFile): 128 | rarfile.RarFile("test/files/ctime4.rar.exp") 129 | 130 | 131 | def test_signature_error_mem(): 132 | data = io.BytesIO(b"x" * 40) 133 | with pytest.raises(rarfile.NotRarFile): 134 | rarfile.RarFile(data) 135 | 136 | 137 | def test_with(): 138 | with rarfile.RarFile("test/files/rar5-crc.rar") as rf: 139 | data = rf.read("stest1.txt") 140 | with rf.open("stest1.txt") as f: 141 | dst = io.BytesIO() 142 | while True: 143 | buf = f.read(7) 144 | if not buf: 145 | break 146 | dst.write(buf) 147 | assert dst.getvalue() == data 148 | 149 | 150 | def test_readline(): 151 | def load_readline(rf, fn): 152 | with rf.open(fn) as f: 153 | tr = io.TextIOWrapper(io.BufferedReader(f)) 154 | res = [] 155 | while True: 156 | ln = tr.readline() 157 | if not ln: 158 | break 159 | res.append(ln) 160 | return res 161 | 162 | rf = rarfile.RarFile("test/files/seektest.rar") 163 | v1 = load_readline(rf, "stest1.txt") 164 | v2 = load_readline(rf, "stest2.txt") 165 | assert len(v1) == 512 166 | assert v1 == v2 167 | 168 | 169 | def run_parallel(rfile, entry): 170 | buf1, buf2 = [], [] 171 | rf = rarfile.RarFile(rfile) 172 | count = 0 173 | with rf.open(entry) as f1: 174 | with rf.open(entry) as f2: 175 | for _ in range(10000): 176 | res1 = f1.read(10) 177 | res2 = f2.read(10) 178 | if res1: 179 | buf1.append(res1) 180 | count += len(res1) 181 | if res2: 182 | buf2.append(res2) 183 | count += len(res2) 184 | if not res1 and not res2: 185 | break 186 | 187 | assert buf1 188 | assert buf1 == buf2 189 | 190 | 191 | def test_parallel_file_compressed(): 192 | run_parallel("test/files/seektest.rar", "stest1.txt") 193 | 194 | 195 | def test_parallel_file_direct(): 196 | run_parallel("test/files/seektest.rar", "stest2.txt") 197 | 198 | 199 | def test_parallel_fd_compressed(): 200 | with open("test/files/seektest.rar", "rb") as f: 201 | memfile = io.BytesIO(f.read()) 202 | run_parallel(memfile, "stest1.txt") 203 | 204 | 205 | def test_parallel_fd_direct(): 206 | with open("test/files/seektest.rar", "rb") as f: 207 | memfile = io.BytesIO(f.read()) 208 | run_parallel(memfile, "stest2.txt") 209 | 210 | 211 | def test_printdir(capsys): 212 | rf = rarfile.RarFile("test/files/seektest.rar") 213 | rf.printdir() 214 | res = capsys.readouterr() 215 | assert res.out == "stest1.txt\nstest2.txt\n" 216 | 217 | 218 | def test_testrar(): 219 | rf = rarfile.RarFile("test/files/seektest.rar") 220 | rf.testrar() 221 | 222 | 223 | def test_iter(): 224 | rf = rarfile.RarFile("test/files/seektest.rar") 225 | n1 = rf.namelist() 226 | n2 = [m.filename for m in rf] 227 | assert n1 == n2 228 | 229 | 230 | def test_testrar_mem(): 231 | with open("test/files/seektest.rar", "rb") as f: 232 | arc = f.read() 233 | rf = rarfile.RarFile(io.BytesIO(arc)) 234 | rf.testrar() 235 | 236 | 237 | def test_extract(tmp_path): 238 | ex1 = tmp_path / "extract1" 239 | ex2 = tmp_path / "extract2" 240 | ex3 = tmp_path / "extract3" 241 | os.makedirs(str(ex1)) 242 | os.makedirs(str(ex2)) 243 | os.makedirs(str(ex3)) 244 | rf = rarfile.RarFile("test/files/seektest.rar") 245 | 246 | rf.extractall(str(ex1)) 247 | assert os.path.isfile(str(ex1 / "stest1.txt")) is True 248 | assert os.path.isfile(str(ex1 / "stest2.txt")) is True 249 | 250 | rf.extract("stest1.txt", str(ex2)) 251 | assert os.path.isfile(str(ex2 / "stest1.txt")) is True 252 | assert os.path.isfile(str(ex2 / "stest2.txt")) is False 253 | 254 | inf = rf.getinfo("stest2.txt") 255 | rf.extract(inf, str(ex3)) 256 | assert os.path.isfile(str(ex3 / "stest1.txt")) is False 257 | assert os.path.isfile(str(ex3 / "stest2.txt")) is True 258 | 259 | rf.extractall(str(ex2), ["stest1.txt"]) 260 | assert os.path.isfile(str(ex2 / "stest1.txt")) is True 261 | 262 | rf.extractall(str(ex3), [rf.getinfo("stest2.txt")]) 263 | assert os.path.isfile(str(ex3 / "stest2.txt")) is True 264 | 265 | ex4 = tmp_path / "extract4" 266 | os.makedirs(str(ex4)) 267 | rf.extractall(ex4) 268 | assert os.path.isfile(str(ex4 / "stest1.txt")) is True 269 | assert os.path.isfile(str(ex4 / "stest2.txt")) is True 270 | 271 | 272 | def test_extract_mem(tmp_path): 273 | ex1 = tmp_path / "extract11" 274 | ex2 = tmp_path / "extract22" 275 | ex3 = tmp_path / "extract33" 276 | os.makedirs(str(ex1)) 277 | os.makedirs(str(ex2)) 278 | os.makedirs(str(ex3)) 279 | 280 | with open("test/files/seektest.rar", "rb") as f: 281 | arc = f.read() 282 | rf = rarfile.RarFile(io.BytesIO(arc)) 283 | 284 | rf.extractall(str(ex1)) 285 | assert os.path.isfile(str(ex1 / "stest1.txt")) is True 286 | assert os.path.isfile(str(ex1 / "stest2.txt")) is True 287 | 288 | rf.extract("stest1.txt", str(ex2)) 289 | assert os.path.isfile(str(ex2 / "stest1.txt")) is True 290 | assert os.path.isfile(str(ex2 / "stest2.txt")) is False 291 | 292 | inf = rf.getinfo("stest2.txt") 293 | rf.extract(inf, str(ex3)) 294 | assert os.path.isfile(str(ex3 / "stest1.txt")) is False 295 | assert os.path.isfile(str(ex3 / "stest2.txt")) is True 296 | 297 | 298 | def get_rftype(h): 299 | assert h.is_dir() == h.isdir() 300 | return "".join([ 301 | h.is_file() and "F" or "-", 302 | h.is_dir() and "D" or "-", 303 | h.is_symlink() and "L" or "-", 304 | ]) 305 | 306 | 307 | def test_infocb(): 308 | infos = [] 309 | 310 | def info_cb(info): 311 | infos.append((info.type, info.needs_password(), get_rftype(info), info._must_disable_hack())) 312 | 313 | rf = rarfile.RarFile("test/files/seektest.rar", info_callback=info_cb) 314 | assert infos == [ 315 | (rarfile.RAR_BLOCK_MAIN, False, "---", False), 316 | (rarfile.RAR_BLOCK_FILE, False, "F--", False), 317 | (rarfile.RAR_BLOCK_FILE, False, "F--", False), 318 | (rarfile.RAR_BLOCK_ENDARC, False, "---", False)] 319 | rf.close() 320 | 321 | infos = [] 322 | rf = rarfile.RarFile("test/files/rar5-solid-qo.rar", info_callback=info_cb) 323 | assert infos == [ 324 | (rarfile.RAR_BLOCK_MAIN, False, "---", True), 325 | (rarfile.RAR_BLOCK_FILE, False, "F--", False), 326 | (rarfile.RAR_BLOCK_FILE, False, "F--", True), 327 | (rarfile.RAR_BLOCK_FILE, False, "F--", True), 328 | (rarfile.RAR_BLOCK_FILE, False, "F--", True), 329 | (rarfile.RAR_BLOCK_SUB, False, "---", False), 330 | (rarfile.RAR_BLOCK_ENDARC, False, "---", False)] 331 | rf.close() 332 | 333 | 334 | # pylint: disable=singleton-comparison 335 | def test_rarextfile(): 336 | with rarfile.RarFile("test/files/seektest.rar") as rf: 337 | for fn in ("stest1.txt", "stest2.txt"): 338 | with rf.open(fn) as f: 339 | assert f.tell() == 0 340 | assert f.writable() == False 341 | assert f.seekable() == True 342 | assert f.readable() == True 343 | assert f.readall() == rf.read(fn) 344 | 345 | 346 | def test_is_rarfile(): 347 | with rarfile.RarFile("test/files/seektest.rar") as rf: 348 | for fn in ("stest1.txt", "stest2.txt"): 349 | with rf.open(fn) as f: 350 | assert f.tell() == 0 351 | assert f.writable() == False 352 | assert f.seekable() == True 353 | assert f.readable() == True 354 | assert f.readall() == rf.read(fn) 355 | 356 | 357 | def test_part_only(): 358 | info_list = [] 359 | def info_cb(info): 360 | info_list.append(info) 361 | 362 | with pytest.raises(rarfile.NeedFirstVolume): 363 | with rarfile.RarFile("test/files/rar3-vols.part2.rar") as rf: 364 | pass 365 | with rarfile.RarFile("test/files/rar3-vols.part2.rar", part_only=True, info_callback=info_cb) as rf: 366 | assert len(info_list) == 3 367 | 368 | with pytest.raises(rarfile.NeedFirstVolume): 369 | with rarfile.RarFile("test/files/rar5-vols.part2.rar") as rf: 370 | pass 371 | info_list = [] 372 | with rarfile.RarFile("test/files/rar5-vols.part2.rar", part_only=True, info_callback=info_cb) as rf: 373 | assert len(info_list) == 5 374 | 375 | 376 | def test_volume_info(): 377 | info_list = [] 378 | def info_cb(info): 379 | info_list.append(info) 380 | with rarfile.RarFile("test/files/rar3-vols.part1.rar", info_callback=info_cb) as rf: 381 | assert len(info_list) == 10 382 | info_list = [] 383 | with rarfile.RarFile("test/files/rar5-vols.part1.rar", info_callback=info_cb) as rf: 384 | assert len(info_list) == 16 385 | 386 | 387 | def test_is_solid(): 388 | with rarfile.RarFile("test/files/rar3-comment-plain.rar") as rf: 389 | assert not rf.is_solid() 390 | with rarfile.RarFile("test/files/rar3-solid.rar") as rf: 391 | assert rf.is_solid() 392 | with rarfile.RarFile("test/files/rar5-crc.rar") as rf: 393 | assert not rf.is_solid() 394 | with rarfile.RarFile("test/files/rar5-solid.rar") as rf: 395 | assert rf.is_solid() 396 | 397 | -------------------------------------------------------------------------------- /test/test_compat.py: -------------------------------------------------------------------------------- 1 | """Test zipfile compat. 2 | """ 3 | 4 | import inspect 5 | import sys 6 | import zipfile 7 | 8 | import pytest 9 | 10 | import rarfile 11 | 12 | # dont fail on new python by default 13 | _VERS = [(3, 6), (3, 7), (3, 8)] 14 | 15 | _UNSUPPORTED = sys.version_info[:2] not in _VERS 16 | 17 | _ignore = set([ 18 | "detach", 19 | "peek", 20 | "read1", 21 | "readinto1", 22 | 23 | "seek", 24 | 25 | # no kwargs 26 | "readinto", 27 | "readline", 28 | "truncate", 29 | "write", 30 | 31 | # random 32 | "FileHeader", 33 | "from_file", 34 | "testzip", 35 | "writestr", 36 | ]) 37 | 38 | 39 | def load_cls_names(maincls): 40 | assert inspect.isclass(maincls) 41 | res = {} 42 | for cls in inspect.getmro(maincls): 43 | for name, val in inspect.getmembers(cls): 44 | if name not in res: 45 | res[name] = val 46 | return res 47 | 48 | 49 | def cleansig(sig): 50 | res = str(sig).replace(", /", "") 51 | if "*" in res: 52 | res = res.split(", *", 1)[0] + ")" 53 | return res 54 | 55 | 56 | def compare(rmaincls, zmaincls): 57 | znames = load_cls_names(zmaincls) 58 | rnames = load_cls_names(rmaincls) 59 | for name, zval in znames.items(): 60 | if not inspect.isroutine(zval) or name[0] == "_" or name in _ignore: 61 | continue 62 | assert name in rnames, "member not found: \"%s\"" % name 63 | 64 | rval = rnames[name] 65 | zsig = inspect.signature(zval) 66 | rsig = inspect.signature(rval) 67 | 68 | zsigstr = cleansig(zsig) 69 | rsigstr = cleansig(rsig) 70 | assert zsigstr == rsigstr, "sig differs: %s.%s%s != %s.%s%s" % ( 71 | rmaincls.__name__, name, rsigstr, 72 | zmaincls.__name__, name, zsigstr) 73 | 74 | 75 | @pytest.mark.skipif(_UNSUPPORTED, reason="Unsupported for sig checks") 76 | def test_cmp_zipfile(): 77 | compare(rarfile.RarFile, zipfile.ZipFile) 78 | 79 | 80 | @pytest.mark.skipif(_UNSUPPORTED, reason="Unsupported for sig checks") 81 | def test_cmp_zipextfile(): 82 | compare(rarfile.RarExtFile, zipfile.ZipExtFile) 83 | 84 | 85 | @pytest.mark.skipif(_UNSUPPORTED, reason="Unsupported for sig checks") 86 | def test_cmp_zipinfo(): 87 | compare(rarfile.RarInfo, zipfile.ZipInfo) 88 | 89 | -------------------------------------------------------------------------------- /test/test_crypto.py: -------------------------------------------------------------------------------- 1 | """Crypto tests. 2 | """ 3 | 4 | from __future__ import division, print_function 5 | 6 | from binascii import unhexlify 7 | 8 | import pytest 9 | 10 | import rarfile 11 | 12 | try: 13 | from cryptography.hazmat.backends import default_backend 14 | from cryptography.hazmat.primitives.ciphers import ( 15 | Cipher, algorithms, modes, 16 | ) 17 | def aes_encrypt(key, iv, data): 18 | ciph = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend()) 19 | enc = ciph.encryptor() 20 | return enc.update(data) 21 | except ImportError: 22 | pass 23 | 24 | 25 | @pytest.mark.skipif(not rarfile._have_crypto, reason="No crypto") 26 | def test_aes128_cbc(): 27 | data = b"0123456789abcdef" * 2 28 | key = b"\x02" * 16 29 | iv = b"\x80" * 16 30 | 31 | #encdata = aes_encrypt(key, iv, data) 32 | encdata = unhexlify("4b0d438b4a1b972bd4ab81cd64674dcce4b0158090fbe616f455354284d53502") 33 | 34 | ctx = rarfile.AES_CBC_Decrypt(key, iv) 35 | assert ctx.decrypt(encdata) == data 36 | 37 | 38 | @pytest.mark.skipif(not rarfile._have_crypto, reason="No crypto") 39 | def test_aes256_cbc(): 40 | data = b"0123456789abcdef" * 2 41 | key = b"\x52" * 32 42 | iv = b"\x70" * 16 43 | 44 | #encdata = aes_encrypt(key, iv, data) 45 | encdata = unhexlify("24988f387592e4d95b6eaab013137a221f81b25aa7ecde0ef4f4d7a95f92c250") 46 | 47 | ctx = rarfile.AES_CBC_Decrypt(key, iv) 48 | assert ctx.decrypt(encdata) == data 49 | 50 | -------------------------------------------------------------------------------- /test/test_extract.py: -------------------------------------------------------------------------------- 1 | """Extract tests. 2 | """ 3 | 4 | import io 5 | import os 6 | import sys 7 | from datetime import datetime 8 | 9 | import pytest 10 | 11 | import rarfile 12 | 13 | 14 | def get_props(rf, name): 15 | inf = rf.getinfo(name) 16 | return "".join([ 17 | inf.is_file() and "F" or "-", 18 | inf.is_dir() and "D" or "-", 19 | inf.is_symlink() and "L" or "-", 20 | ]) 21 | 22 | 23 | def san_unix(fn): 24 | return rarfile.sanitize_filename(fn, "/", False) 25 | 26 | 27 | def san_win32(fn): 28 | return rarfile.sanitize_filename(fn, "/", True) 29 | 30 | 31 | def test_sanitize_unix(): 32 | assert san_unix("asd/asd") == "asd/asd" 33 | assert san_unix("asd/../asd") == "asd/asd" 34 | assert san_unix("c:/a/x") == r"c:/a/x" 35 | assert san_unix("z/./../a /b./c") == "z/a /b./c" 36 | assert san_unix("z<>*?:") == "z____:" 37 | 38 | 39 | def test_sanitize_win32(): 40 | assert san_win32("asd/asd") == "asd/asd" 41 | assert san_win32("asd/../asd") == "asd/asd" 42 | assert san_win32("c:/a/x") == "a/x" 43 | assert san_win32("z/./../a /b./c") == "z/a_/b_/c" 44 | assert san_win32("z<>*?:\\^") == "z_______" 45 | 46 | 47 | def checktime(fn, exp_mtime): 48 | # cannot check subsecond precision as filesystem may not support it 49 | cut = len("0000-00-00 00:00:00") 50 | st = os.stat(fn) 51 | got_mtime = datetime.fromtimestamp(st.st_mtime, exp_mtime.tzinfo) 52 | exp_stamp = exp_mtime.isoformat(" ", "seconds")[:cut] 53 | got_stamp = got_mtime.isoformat(" ", "seconds")[:cut] 54 | assert exp_stamp == got_stamp 55 | 56 | 57 | def checkfile(fn, data, mtime): 58 | with open(fn, "r", encoding="utf8") as f: 59 | got = f.read() 60 | assert got.strip() == data 61 | 62 | checktime(fn, mtime) 63 | 64 | 65 | def check_subdir(rf, tmp_path): 66 | # pre-mkdir 67 | ext1 = tmp_path / "ext1" 68 | inf = rf.getinfo("sub/dir1/file1.txt") 69 | os.mkdir(ext1) 70 | rf.extract(inf, ext1) 71 | assert sorted(os.listdir(tmp_path)) == ["ext1"] 72 | assert os.listdir(ext1 / "sub") == ["dir1"] 73 | checkfile(ext1 / "sub/dir1/file1.txt", "file1", inf.mtime) 74 | 75 | # no mkdir 76 | ext2 = tmp_path / "ext2" 77 | inf = rf.getinfo("sub/dir2/file2.txt") 78 | rf.extract("sub/dir2/file2.txt", ext2) 79 | assert sorted(os.listdir(tmp_path)) == ["ext1", "ext2"] 80 | assert os.listdir(ext2 / "sub") == ["dir2"] 81 | checkfile(ext2 / "sub/dir2/file2.txt", "file2", inf.mtime) 82 | 83 | # spaced 84 | ext3 = tmp_path / "ext3" 85 | inf = rf.getinfo("sub/with space/long fn.txt") 86 | rf.extract("sub/with space/long fn.txt", ext3) 87 | checkfile(ext3 / "sub/with space/long fn.txt", "long fn", inf.mtime) 88 | 89 | # unicode 90 | ext4 = tmp_path / "ext4" 91 | inf = rf.getinfo("sub/üȵĩöḋè/file.txt") 92 | rf.extract("sub/üȵĩöḋè/file.txt", ext4) 93 | checkfile(ext4 / "sub/üȵĩöḋè/file.txt", "file", inf.mtime) 94 | 95 | # dir only 96 | ext5 = tmp_path / "ext5" 97 | inf = rf.getinfo("sub/dir2") 98 | rf.extract("sub/dir2", ext5) 99 | assert os.listdir(ext5 / "sub") == ["dir2"] 100 | assert os.listdir(ext5 / "sub/dir2") == [] 101 | checktime(ext5 / "sub/dir2", inf.mtime) 102 | 103 | # cwd 104 | ext6 = tmp_path / "ext6" 105 | os.mkdir(ext6) 106 | old = os.getcwd() 107 | try: 108 | os.chdir(ext6) 109 | rf.extract("sub/dir1") 110 | assert os.listdir(".") == ["sub"] 111 | assert os.listdir("sub") == ["dir1"] 112 | assert os.listdir("sub/dir1") == [] 113 | finally: 114 | os.chdir(old) 115 | 116 | # errors 117 | with pytest.raises(io.UnsupportedOperation): 118 | rf.open("sub/dir1") 119 | 120 | 121 | @pytest.mark.parametrize("fn", [ 122 | "test/files/rar3-subdirs.rar", 123 | "test/files/rar5-subdirs.rar", 124 | ]) 125 | def test_subdirs(fn, tmp_path): 126 | with rarfile.RarFile(fn) as rf: 127 | check_subdir(rf, tmp_path) 128 | 129 | 130 | @pytest.mark.parametrize("fn", [ 131 | "test/files/rar3-readonly-unix.rar", 132 | "test/files/rar3-readonly-win.rar", 133 | "test/files/rar5-readonly-unix.rar", 134 | "test/files/rar5-readonly-win.rar", 135 | ]) 136 | def test_readonly(fn, tmp_path): 137 | with rarfile.RarFile(fn) as rf: 138 | assert get_props(rf, "ro_dir") == "-D-" 139 | assert get_props(rf, "ro_dir/ro_file.txt") == "F--" 140 | 141 | rf.extractall(tmp_path) 142 | 143 | assert os.access(tmp_path / "ro_dir/ro_file.txt", os.R_OK) 144 | assert not os.access(tmp_path / "ro_dir/ro_file.txt", os.W_OK) 145 | 146 | if sys.platform != "win32": 147 | assert os.access(tmp_path / "ro_dir", os.R_OK) 148 | assert not os.access(tmp_path / "ro_dir", os.W_OK) 149 | 150 | 151 | @pytest.mark.parametrize("fn", [ 152 | "test/files/rar3-symlink-unix.rar", 153 | "test/files/rar5-symlink-unix.rar", 154 | ]) 155 | def test_symlink(fn, tmp_path): 156 | with rarfile.RarFile(fn) as rf: 157 | assert get_props(rf, "data.txt") == "F--" 158 | assert get_props(rf, "data_link") == "--L" 159 | assert get_props(rf, "random_link") == "--L" 160 | 161 | rf.extractall(tmp_path) 162 | 163 | assert sorted(os.listdir(tmp_path)) == ["data.txt", "data_link", "random_link"] 164 | 165 | data = rf.getinfo("data.txt") 166 | data_link = rf.getinfo("data_link") 167 | random_link = rf.getinfo("random_link") 168 | 169 | assert not data.is_symlink() 170 | assert data_link.is_symlink() 171 | assert random_link.is_symlink() 172 | 173 | assert rf.read(data) == b"data\n" 174 | assert rf.read(data_link) == b"data.txt" 175 | assert rf.read(random_link) == b"../random123" 176 | 177 | assert os.path.isfile(tmp_path / "data.txt") 178 | assert os.path.islink(tmp_path / "data_link") 179 | assert os.path.islink(tmp_path / "random_link") 180 | 181 | # str - work around pypy3 bug 182 | assert os.readlink(str(tmp_path / "data_link")) == "data.txt" 183 | assert os.readlink(str(tmp_path / "random_link")) == "../random123" 184 | 185 | 186 | def test_symlink_win(tmp_path): 187 | fn = "test/files/rar5-symlink-win.rar" 188 | with rarfile.RarFile(fn) as rf: 189 | assert get_props(rf, "content/dir1") == "-D-" 190 | assert get_props(rf, "content/dir2") == "-D-" 191 | assert get_props(rf, "content/file.txt") == "F--" 192 | assert get_props(rf, "links/bad_link") == "--L" 193 | assert get_props(rf, "links/dir_junction") == "--L" 194 | assert get_props(rf, "links/dir_link") == "--L" 195 | assert get_props(rf, "links/file_link") == "--L" 196 | 197 | with pytest.warns(rarfile.UnsupportedWarning): 198 | rf.extractall(tmp_path) 199 | 200 | assert sorted(os.listdir(tmp_path)) == ["content", "links"] 201 | assert sorted(os.listdir(tmp_path / "content")) == ["dir1", "dir2", "file.txt"] 202 | assert sorted(os.listdir(tmp_path / "links")) == ["bad_link", "dir_link", "file_link"] 203 | 204 | assert os.path.islink(tmp_path / "links/bad_link") 205 | assert os.path.islink(tmp_path / "links/dir_link") 206 | assert os.path.islink(tmp_path / "links/file_link") 207 | 208 | @pytest.mark.parametrize("fn", [ 209 | "test/files/rar3-old.rar", 210 | "test/files/rar3-vols.part1.rar", 211 | "test/files/rar5-vols.part1.rar", 212 | ]) 213 | def test_vols(fn, tmp_path): 214 | with rarfile.RarFile(fn) as rf: 215 | rarfile.FORCE_TOOL = True 216 | try: 217 | rf.extractall(str(tmp_path)) 218 | finally: 219 | rarfile.FORCE_TOOL = False 220 | 221 | 222 | assert sorted(os.listdir(tmp_path)) == ["vols"] 223 | assert sorted(os.listdir(tmp_path / "vols")) == ["bigfile.txt", "smallfile.txt"] 224 | 225 | assert os.path.isfile(tmp_path / "vols" / "bigfile.txt") 226 | assert os.path.isfile(tmp_path / "vols" / "smallfile.txt") 227 | 228 | -------------------------------------------------------------------------------- /test/test_format.py: -------------------------------------------------------------------------------- 1 | """Format details. 2 | """ 3 | 4 | from datetime import datetime 5 | 6 | import pytest 7 | 8 | import rarfile 9 | 10 | 11 | def render_date(dt): 12 | if isinstance(dt, datetime): 13 | return dt.isoformat("T") 14 | elif isinstance(dt, tuple): 15 | return "%04d-%02d-%02d %02d:%02d:%02d" % dt 16 | else: 17 | return dt 18 | 19 | 20 | def mkitem(**kwargs): 21 | res = {} 22 | for k, v in kwargs.items(): 23 | if v is not None: 24 | res[k] = v 25 | return res 26 | 27 | 28 | def dumparc(rf): 29 | res = [] 30 | for item in rf.infolist(): 31 | info = mkitem(fn=item.filename, 32 | file_size=item.file_size, 33 | compress_size=item.compress_size, 34 | CRC=item.CRC, 35 | date_time=render_date(item.date_time), 36 | arctime=render_date(item.arctime), 37 | mtime=render_date(item.mtime), 38 | atime=render_date(item.atime), 39 | ctime=render_date(item.ctime), 40 | comment=item.comment, 41 | extract_version=item.extract_version, 42 | compress_type=item.compress_type, 43 | mode=item.mode, 44 | host_os=item.host_os) 45 | res.append(info) 46 | return res 47 | 48 | 49 | def diffs(a, b): 50 | if len(a) != len(b): 51 | return "Different lengths" 52 | problems = [] 53 | for i, xa in enumerate(a): 54 | xb = b[i] 55 | for k in xa: 56 | if k not in xb: 57 | problems.append("NewKey(%d,%s)=%r" % (i, k, xa[k])) 58 | for k in xb: 59 | if k not in xa: 60 | problems.append("MissingKey(%d,%s)=%r" % (i, k, xb[k])) 61 | for k in xa: 62 | if k in xb and xa[k] != xb[k]: 63 | problems.append("ErrValue(%d,%s):got=%r/exp=%r" % (i, k, xa[k], xb[k])) 64 | return "; ".join(problems) 65 | 66 | 67 | def cmp_struct(a, b): 68 | assert a == b, diffs(a, b) 69 | 70 | # 71 | # test start 72 | # 73 | 74 | 75 | @pytest.mark.skipif(not rarfile._have_crypto, reason="No crypto") 76 | def test_rar3_header_encryption(): 77 | r = rarfile.RarFile("test/files/rar3-comment-hpsw.rar", "r") 78 | assert r.needs_password() is True 79 | assert r.comment is None 80 | assert r.namelist() == [] 81 | 82 | r.setpassword("password") 83 | assert r.needs_password() is True 84 | assert r.namelist() == ["file1.txt", "file2.txt"] 85 | assert r.comment is not None 86 | assert r.comment == "RARcomment\n" 87 | 88 | 89 | @pytest.mark.skipif(not rarfile._have_crypto, reason="No crypto") 90 | def test_rar5_header_encryption(): 91 | r = rarfile.RarFile("test/files/rar5-hpsw.rar") 92 | assert r.needs_password() is True 93 | assert r.comment is None 94 | assert r.namelist() == [] 95 | 96 | r.setpassword("password") 97 | assert r.needs_password() is True 98 | assert r.namelist() == ["stest1.txt", "stest2.txt"] 99 | assert r.comment is not None 100 | assert r.comment == "RAR5 archive - hdr-password\n" 101 | r.close() 102 | 103 | 104 | def get_vol_info(extver=20, tz="", hr="11"): 105 | return [ 106 | mkitem(CRC=1352324940, 107 | date_time="2016-05-24 %s:42:37%s" % (hr, ""), 108 | mtime="2016-05-24T%s:42:37%s" % (hr, tz), 109 | compress_type=48, 110 | compress_size=205000, 111 | extract_version=extver, 112 | file_size=205000, 113 | mode=33204, 114 | host_os=3, 115 | fn="vols/bigfile.txt"), 116 | mkitem(CRC=3498712966, 117 | date_time="2016-05-24 %s:42:43%s" % (hr, ""), 118 | mtime="2016-05-24T%s:42:43%s" % (hr, tz), 119 | extract_version=extver, 120 | compress_type=48, 121 | compress_size=2050, 122 | file_size=2050, 123 | mode=33204, 124 | host_os=3, 125 | fn="vols/smallfile.txt")] 126 | 127 | 128 | def test_rar3_vols(): 129 | r = rarfile.RarFile("test/files/rar3-vols.part1.rar") 130 | assert r.needs_password() is False 131 | assert r.comment is None 132 | assert r.strerror() is None 133 | cmp_struct(dumparc(r), get_vol_info()) 134 | assert r.volumelist() == [ 135 | "test/files/rar3-vols.part1.rar", 136 | "test/files/rar3-vols.part2.rar", 137 | "test/files/rar3-vols.part3.rar"] 138 | with pytest.raises(rarfile.NeedFirstVolume): 139 | rarfile.RarFile("test/files/rar3-vols.part2.rar") 140 | 141 | 142 | def test_rar3_oldvols(): 143 | r = rarfile.RarFile("test/files/rar3-old.rar") 144 | assert r.needs_password() is False 145 | assert r.comment is None 146 | assert r.strerror() is None 147 | cmp_struct(dumparc(r), get_vol_info()) 148 | assert r.volumelist() == [ 149 | "test/files/rar3-old.rar", 150 | "test/files/rar3-old.r00", 151 | "test/files/rar3-old.r01"] 152 | with pytest.raises(rarfile.NeedFirstVolume): 153 | rarfile.RarFile("test/files/rar3-old.r00") 154 | 155 | 156 | def test_rar5_vols(): 157 | r = rarfile.RarFile("test/files/rar5-vols.part1.rar") 158 | assert r.needs_password() is False 159 | assert r.comment is None 160 | assert r.strerror() is None 161 | cmp_struct(dumparc(r), get_vol_info(50, "+00:00", "08")) 162 | assert r.volumelist() == [ 163 | "test/files/rar5-vols.part1.rar", 164 | "test/files/rar5-vols.part2.rar", 165 | "test/files/rar5-vols.part3.rar"] 166 | with pytest.raises(rarfile.NeedFirstVolume): 167 | rarfile.RarFile("test/files/rar5-vols.part2.rar") 168 | 169 | 170 | def expect_ctime(mtime, ctime): 171 | return [mkitem( 172 | mtime=mtime, 173 | date_time=mtime.split(".")[0].replace("T", " "), 174 | ctime=ctime, 175 | compress_size=0, 176 | file_size=0, 177 | CRC=0, 178 | fn="afile.txt", 179 | extract_version=29, 180 | compress_type=48, 181 | mode=32, 182 | host_os=2)] 183 | 184 | 185 | def test_rar3_ctime0(): 186 | r = rarfile.RarFile("test/files/ctime0.rar") 187 | cmp_struct(dumparc(r), expect_ctime("2011-05-10T21:28:47.899345100", None)) 188 | 189 | 190 | def test_rar3_ctime1(): 191 | r = rarfile.RarFile("test/files/ctime1.rar") 192 | cmp_struct(dumparc(r), expect_ctime("2011-05-10T21:28:47.899345100", "2011-05-10T21:28:47")) 193 | 194 | 195 | def test_rar3_ctime2(): 196 | r = rarfile.RarFile("test/files/ctime2.rar") 197 | cmp_struct(dumparc(r), expect_ctime("2011-05-10T21:28:47.899345100", "2011-05-10T21:28:47.897843200")) 198 | 199 | 200 | def test_rar3_ctime3(): 201 | r = rarfile.RarFile("test/files/ctime3.rar") 202 | cmp_struct(dumparc(r), expect_ctime("2011-05-10T21:28:47.899345100", "2011-05-10T21:28:47.899328")) 203 | 204 | 205 | def test_rar3_ctime4(): 206 | r = rarfile.RarFile("test/files/ctime4.rar") 207 | cmp_struct(dumparc(r), expect_ctime("2011-05-10T21:28:47.899345100", "2011-05-10T21:28:47.899345100")) 208 | 209 | 210 | def test_rar5_ctime5(): 211 | r = rarfile.RarFile("test/files/ctime5.rar") 212 | inf = r.getinfo("timed.txt") 213 | assert inf.mtime.isoformat() == "2020-07-30T20:26:59.677675904+00:00" 214 | assert inf.ctime.isoformat() == "2020-07-30T20:28:19.398867888+00:00" 215 | assert inf.atime.isoformat() == "2020-07-30T20:27:10.121196721+00:00" 216 | 217 | 218 | def test_rar5_times(): 219 | r = rarfile.RarFile("test/files/rar5-times.rar") 220 | cmp_struct(dumparc(r), [mkitem( 221 | fn="stest1.txt", 222 | file_size=2048, 223 | compress_size=55, 224 | compress_type=rarfile.RAR_M3, 225 | extract_version=50, 226 | host_os=rarfile.RAR_OS_UNIX, 227 | mode=33188, 228 | date_time="2011-06-12 09:53:33", 229 | mtime="2011-06-12T09:53:33+00:00", 230 | atime="2016-05-22T09:12:36+00:00", 231 | CRC=3317163682 232 | )]) 233 | 234 | 235 | def test_oldvols(): 236 | assert rarfile._next_oldvol("archive") == "archive.r00" 237 | assert rarfile._next_oldvol("archive.rar/foo") == "archive.rar/foo.r00" 238 | assert rarfile._next_oldvol("archive.arr") == "archive.a00" 239 | assert rarfile._next_oldvol("archive.brar") == "archive.b00" 240 | assert rarfile._next_oldvol("qq00.part0.rar") == "qq00.part0.r00" 241 | assert rarfile._next_oldvol("qq00.part0.r00") == "qq00.part0.r01" 242 | assert rarfile._next_oldvol("qq00.part0.r29") == "qq00.part0.r30" 243 | assert rarfile._next_oldvol("qq00.part0.r99") == "qq00.part0.s00" 244 | 245 | 246 | def test_newvols(): 247 | assert rarfile._next_newvol("qq00.part0.rar") == "qq00.part1.rar" 248 | assert rarfile._next_newvol("qq00.part09.rar") == "qq00.part10.rar" 249 | assert rarfile._next_newvol("qq00.part99.rar") == "qq00.part100.rar" 250 | assert rarfile._next_newvol("part20") == "part21.rar" 251 | assert rarfile._next_newvol("qq00.part3.exe") == "qq00.part4.rar" 252 | assert rarfile._next_newvol("qq00.part5.sfx") == "qq00.part6.rar" 253 | assert rarfile._next_newvol("qq00.part6.bin") == "qq00.part7.bin" 254 | assert rarfile._next_newvol("99") == "100.rar" 255 | assert rarfile._next_newvol("dir/99.rar") == "dir/100.rar" 256 | with pytest.raises(rarfile.BadRarName): 257 | rarfile._next_newvol("qq00.part7.rar/foo") 258 | with pytest.raises(rarfile.BadRarName): 259 | rarfile._next_newvol("foo") 260 | 261 | 262 | def test_newvols_err(): 263 | with pytest.raises(rarfile.BadRarName): 264 | rarfile._next_newvol("xx.rar") 265 | 266 | 267 | @pytest.mark.parametrize("fn", ["test/files/rar3-versions.rar", "test/files/rar5-versions.rar"]) 268 | def test_versions(fn): 269 | with rarfile.RarFile(fn) as rf: 270 | assert rf.namelist() == ["versioned.txt"] 271 | 272 | -------------------------------------------------------------------------------- /test/test_hashing.py: -------------------------------------------------------------------------------- 1 | """Hashing tests. 2 | """ 3 | 4 | import hashlib 5 | from binascii import hexlify, unhexlify 6 | 7 | import rarfile 8 | from rarfile import Blake2SP, CRC32Context, NoHashContext, Rar3Sha1 9 | 10 | 11 | def tohex(data): 12 | """Return hex string.""" 13 | return hexlify(data).decode("ascii") 14 | 15 | 16 | def test_nohash(): 17 | assert NoHashContext("").hexdigest() is None 18 | assert NoHashContext("asd").hexdigest() is None 19 | md = NoHashContext() 20 | md.update("asd") 21 | assert md.digest() is None 22 | 23 | 24 | def test_crc32(): 25 | assert CRC32Context(b"").hexdigest() == "00000000" 26 | assert CRC32Context(b"Hello").hexdigest() == "f7d18982" 27 | assert CRC32Context(b"Bye").hexdigest() == "4f7ad7d4" 28 | 29 | md = CRC32Context() 30 | md.update(b"He") 31 | md.update(b"ll") 32 | md.update(b"o") 33 | assert md.hexdigest() == "f7d18982" 34 | 35 | 36 | def xblake2sp(xdata): 37 | data = unhexlify(xdata) 38 | md = Blake2SP() 39 | md.update(data) 40 | return md.hexdigest() 41 | 42 | 43 | def xblake2sp_slow(xdata): 44 | data = unhexlify(xdata) 45 | md = Blake2SP() 46 | buf = memoryview(data) 47 | pos = 0 48 | while pos < len(buf): 49 | md.update(buf[pos: pos + 3]) 50 | pos += 3 51 | return md.hexdigest() 52 | 53 | 54 | def test_blake2sp(): 55 | assert Blake2SP(b"").hexdigest() == "dd0e891776933f43c7d032b08a917e25741f8aa9a12c12e1cac8801500f2ca4f" 56 | assert Blake2SP(b"Hello").hexdigest() == "0d6bae0db99f99183d060f7994bb94b45c6490b2a0a628b8b1346ebea8ec1d66" 57 | 58 | assert xblake2sp("") == "dd0e891776933f43c7d032b08a917e25741f8aa9a12c12e1cac8801500f2ca4f" 59 | assert xblake2sp("00") == "a6b9eecc25227ad788c99d3f236debc8da408849e9a5178978727a81457f7239" 60 | 61 | long1 = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031" 62 | assert xblake2sp(long1) == "270affa6426f1a515c9b76dfc27d181fc2fd57d082a3ba2c1eef071533a6dfb7" 63 | 64 | long2 = long1 * 20 65 | assert xblake2sp(long2) == "24a78d92592d0761a3681f32935225ca55ffb8eb16b55ab9481c89c59a985ff3" 66 | assert xblake2sp_slow(long2) == "24a78d92592d0761a3681f32935225ca55ffb8eb16b55ab9481c89c59a985ff3" 67 | 68 | 69 | def test_rar3_sha1(): 70 | for n in range(0, 200): 71 | data = bytearray(range(n)) 72 | h1 = hashlib.sha1(data).hexdigest() 73 | h2 = Rar3Sha1(data).hexdigest() 74 | assert h1 == h2 75 | 76 | data = bytearray([(i & 255) for i in range(2000)]) 77 | x1 = hashlib.sha1() 78 | x2 = Rar3Sha1() 79 | for step in (3, 17, 67, 128, 157): 80 | pos = 0 81 | while pos < len(data): 82 | pos2 = pos + step 83 | if pos2 > len(data): 84 | pos2 = len(data) 85 | x1.update(data[pos:pos2]) 86 | x2.update(data[pos:pos2]) 87 | assert x1.hexdigest() == x2.hexdigest() 88 | pos = pos2 89 | 90 | 91 | def test_rar3_s2k(): 92 | exp = ("a160cb31cb262e9231c0b6fc984fbb0d", "aa54a659fb0c359b30f353a6343fb11d") 93 | key, iv = rarfile.rar3_s2k(b"password", unhexlify("00FF00")) 94 | assert (tohex(key), tohex(iv)) == exp 95 | key, iv = rarfile.rar3_s2k("password", unhexlify("00FF00")) 96 | assert (tohex(key), tohex(iv)) == exp 97 | 98 | exp = ("ffff33ffaf31987c899ccc2f965a8927", "bdff6873721b247afa4f978448a5aeef") 99 | key, iv = rarfile.rar3_s2k("p" * 28, unhexlify("1122334455667788")) 100 | assert (tohex(key), tohex(iv)) == exp 101 | exp = ("306cafde28f1ea78c9427c3ec642c0db", "173ecdf574c0bfe9e7c23bdfd96fa435") 102 | key, iv = rarfile.rar3_s2k("p" * 29, unhexlify("1122334455667788")) 103 | assert (tohex(key), tohex(iv)) == exp 104 | 105 | -------------------------------------------------------------------------------- /test/test_korrupt.py: -------------------------------------------------------------------------------- 1 | """test corrupt file parsing. 2 | """ 3 | 4 | import glob 5 | import io 6 | 7 | import rarfile 8 | 9 | 10 | def try_read(tmpfn): 11 | if not rarfile.is_rarfile(tmpfn): 12 | return 13 | rarfile.RarFile(tmpfn, errors="stop") 14 | try: 15 | rf = rarfile.RarFile(tmpfn, errors="strict") 16 | if rf.needs_password(): 17 | rf.setpassword("password") 18 | except rarfile.Error: 19 | return 20 | for fn in rf.namelist(): 21 | try: 22 | rf.read(fn) 23 | except rarfile.Error: 24 | pass 25 | 26 | 27 | def process_rar(rarfn, quick=False): 28 | with open(rarfn, "rb") as f: 29 | data = f.read() 30 | for n in range(len(data)): 31 | bad = io.BytesIO(data[:n]) 32 | if not rarfile.is_rarfile(bad): 33 | return 34 | try_read(bad) 35 | 36 | crap = b"\x00\xff\x01\x80\x7f" 37 | if quick: 38 | crap = b"\xff" 39 | for n in range(1, len(data)): 40 | for i in range(len(crap)): 41 | c = crap[i:i + 1] 42 | bad = data[:n - 1] + c + data[n:] 43 | try_read(io.BytesIO(bad)) 44 | 45 | 46 | def test_corrupt_quick_rar3(): 47 | process_rar("test/files/rar3-comment-plain.rar", True) 48 | 49 | 50 | def test_corrupt_quick_rar5(): 51 | process_rar("test/files/rar5-times.rar", True) 52 | 53 | 54 | def test_corrupt_all(): 55 | test_rar_list = glob.glob("test/files/*.rar") 56 | test_rar_list = [] 57 | for rar in test_rar_list: 58 | process_rar(rar) 59 | 60 | 61 | if __name__ == "__main__": 62 | test_corrupt_quick_rar5() 63 | 64 | -------------------------------------------------------------------------------- /test/test_reading.py: -------------------------------------------------------------------------------- 1 | """Read all test files. 2 | """ 3 | 4 | import io 5 | from glob import glob 6 | 7 | import pytest 8 | 9 | import rarfile 10 | 11 | ARCHIVE_COMMENTS = { 12 | "rar15-comment-lock.rar": "RARcomment -----", 13 | "rar15-comment.rar": "RARcomment -----", 14 | "rar202-comment-nopsw.rar": "RARcomment", 15 | "rar202-comment-psw.rar": "RARcomment", 16 | "rar3-comment-hpsw.rar": "RARcomment\n", 17 | "rar3-comment-plain.rar": "RARcomment\n", 18 | "rar3-comment-psw.rar": "RARcomment\n", 19 | "rar5-blake.rar": "RAR5 archive - blake\n", 20 | "rar5-crc.rar": "RAR5 archive - crc\n", 21 | "rar5-crc.sfx": "RAR5 archive - crc\n", 22 | "rar5-hpsw.rar": "RAR5 archive - hdr-password\n", 23 | "rar5-psw-blake.rar": "RAR5 archive - nohdr-password-blake\n", 24 | "rar5-psw.rar": "RAR5 archive - nohdr-password\n", 25 | } 26 | 27 | ARCHIVE_FILES = [ 28 | f.replace("\\", "/") 29 | for f in sorted(glob("test/files/*.rar")) 30 | if "hpsw" not in f 31 | and "unicode.rar" not in f 32 | ] 33 | 34 | 35 | def run_reading_normal(fn, comment): 36 | try: 37 | rf = rarfile.RarFile(fn) 38 | except rarfile.NeedFirstVolume: 39 | return 40 | if rf.needs_password(): 41 | rf.setpassword("password") 42 | assert rf.strerror() is None 43 | assert rf.comment == comment 44 | for ifn in rf.namelist(): 45 | if ifn.endswith("/"): 46 | continue 47 | 48 | info = rf.getinfo(ifn) 49 | if info.is_dir(): 50 | continue 51 | if info.is_symlink(): 52 | continue 53 | 54 | # full read 55 | rf.read(ifn) 56 | 57 | # read from stream 58 | item = rf.getinfo(ifn) 59 | f = rf.open(ifn) 60 | total = 0 61 | while True: 62 | buf = f.read(1024) 63 | if not buf: 64 | break 65 | total += len(buf) 66 | f.close() 67 | assert total == item.file_size, ifn 68 | 69 | # read from stream with readinto 70 | bbuf = bytearray(1024) 71 | with rf.open(ifn) as f: 72 | res = f.readinto(memoryview(bbuf)) 73 | if res == 0: 74 | break 75 | 76 | 77 | def run_reading_inmem(fn, comment): 78 | try: 79 | rf = rarfile.RarFile(fn) 80 | except rarfile.NeedFirstVolume: 81 | return 82 | if len(rf.volumelist()) > 1: 83 | return 84 | 85 | with io.open(fn, "rb") as f: 86 | buf = f.read() 87 | run_reading_normal(io.BytesIO(buf), comment) 88 | 89 | 90 | def run_reading(fn): 91 | basename = fn.split("/")[-1] 92 | comment = ARCHIVE_COMMENTS.get(basename) 93 | run_reading_normal(fn, comment) 94 | run_reading_inmem(fn, comment) 95 | 96 | 97 | @pytest.mark.parametrize("fn", ARCHIVE_FILES) 98 | def test_reading(fn): 99 | run_reading(fn) 100 | 101 | 102 | @pytest.mark.skipif(not rarfile._have_crypto, reason="No crypto") 103 | def test_reading_rar3_hpsw(): 104 | run_reading("test/files/rar3-comment-hpsw.rar") 105 | 106 | 107 | @pytest.mark.skipif(rarfile._have_crypto, reason="Has crypto") 108 | def test_reading_rar3_hpsw_nocrypto(): 109 | with pytest.raises(rarfile.NoCrypto): 110 | run_reading("test/files/rar3-comment-hpsw.rar") 111 | 112 | 113 | @pytest.mark.skipif(not rarfile._have_crypto, reason="No crypto") 114 | def test_reading_rar5_hpsw(): 115 | run_reading("test/files/rar5-hpsw.rar") 116 | 117 | 118 | @pytest.mark.skipif(rarfile._have_crypto, reason="Has crypto") 119 | def test_reading_rar5_hpsw_nocrypto(): 120 | with pytest.raises(rarfile.NoCrypto): 121 | run_reading("test/files/rar5-hpsw.rar") 122 | 123 | 124 | def test_reading_rar3_sfx(): 125 | assert rarfile.is_rarfile("test/files/rar3-seektest.sfx") is False 126 | assert rarfile.is_rarfile_sfx("test/files/rar3-seektest.sfx") is True 127 | run_reading("test/files/rar3-seektest.sfx") 128 | run_reading("test/files/rar3-seektest.sfx") 129 | 130 | 131 | def test_reading_rar5_crc_sfx(): 132 | assert rarfile.is_rarfile("test/files/rar5-crc.sfx") is False 133 | assert rarfile.is_rarfile_sfx("test/files/rar5-crc.sfx") is True 134 | run_reading("test/files/rar5-crc.sfx") 135 | 136 | -------------------------------------------------------------------------------- /test/test_seek.py: -------------------------------------------------------------------------------- 1 | """Test seeking on files. 2 | """ 3 | 4 | import io 5 | 6 | import pytest 7 | 8 | import rarfile 9 | 10 | ARC = "test/files/seektest.rar" 11 | 12 | _WHENCE = 0 13 | 14 | 15 | def do_seek(f, pos, lim, size=None): 16 | global _WHENCE 17 | ofs = pos * 4 18 | fsize = lim * 4 19 | 20 | if ofs < 0: 21 | exp = 0 22 | elif ofs > fsize: 23 | exp = fsize 24 | else: 25 | exp = ofs 26 | 27 | if size: 28 | cur = f.tell() 29 | if _WHENCE == 0: 30 | f.seek(ofs, _WHENCE) 31 | elif _WHENCE == 1: 32 | f.seek(ofs - cur, _WHENCE) 33 | else: 34 | assert _WHENCE == 2 35 | f.seek(ofs - size, _WHENCE) 36 | _WHENCE = (_WHENCE + 1) % 3 37 | else: 38 | f.seek(ofs) 39 | 40 | got = f.tell() 41 | 42 | assert got == exp 43 | ln = f.read(4) 44 | if got == fsize and ln: 45 | raise ValueError("unexpected read") 46 | if not ln and got < fsize: 47 | raise ValueError("unexpected read failure") 48 | if ln: 49 | spos = int(ln) 50 | assert spos * 4 == got 51 | 52 | 53 | def run_seek(rf, fn): 54 | inf = rf.getinfo(fn) 55 | cnt = int(inf.file_size / 4) 56 | f = rf.open(fn) 57 | 58 | with pytest.raises(ValueError): 59 | f.seek(0, -1) 60 | with pytest.raises(ValueError): 61 | f.seek(0, 3) 62 | 63 | do_seek(f, int(cnt / 2), cnt) 64 | do_seek(f, 0, cnt) 65 | 66 | for i in range(int(cnt / 2)): 67 | do_seek(f, i * 2, cnt, inf.file_size) 68 | 69 | for i in range(cnt): 70 | do_seek(f, i * 2 - int(cnt / 2), cnt, inf.file_size) 71 | 72 | for i in range(cnt + 10): 73 | do_seek(f, cnt - i - 5, cnt, inf.file_size) 74 | 75 | f.close() 76 | 77 | 78 | def run_arc(arc, desc): 79 | files = ["stest1.txt", "stest2.txt"] 80 | rf = rarfile.RarFile(arc) 81 | for fn in files: 82 | run_seek(rf, fn) 83 | 84 | 85 | def test_seek_filename(): 86 | run_arc(ARC, "fn") 87 | 88 | 89 | def test_seek_bytesio(): 90 | # filelike: io.BytesIO, io.open() 91 | with open(ARC, "rb") as f: 92 | data = f.read() 93 | run_arc(io.BytesIO(data), "io.BytesIO") 94 | 95 | 96 | def test_seek_open(): 97 | # filelike: file() 98 | with open(ARC, "rb") as f: 99 | run_arc(f, "open") 100 | 101 | 102 | def test_seek_ioopen(): 103 | # filelike: io.open() 104 | with io.open(ARC, "rb") as f: 105 | run_arc(f, "io.open") 106 | 107 | 108 | def run_seek_middle(fn, entry): 109 | rar = rarfile.RarFile(fn) 110 | file = rar.open(entry) 111 | assert file.read(1) == b"0" 112 | assert file.read(1) == b"0" 113 | assert file.read(1) == b"0" 114 | assert file.read(1) == b"\n" 115 | file.read() 116 | 117 | file.seek(0) 118 | assert file.read(1) == b"0" 119 | assert file.read(1) == b"0" 120 | assert file.read(1) == b"0" 121 | assert file.read(1) == b"\n" 122 | file.read() 123 | 124 | file.seek(2) 125 | assert file.read(1) == b"0" 126 | assert file.read(1) == b"\n" 127 | file.read() 128 | 129 | def test_seek_middle1(): 130 | run_seek_middle("test/files/seektest.rar", "stest1.txt") 131 | 132 | def test_seek_middle2(): 133 | run_seek_middle("test/files/seektest.rar", "stest2.txt") 134 | 135 | -------------------------------------------------------------------------------- /test/test_tool.py: -------------------------------------------------------------------------------- 1 | """Alt tool tests 2 | """ 3 | 4 | import sys 5 | import os 6 | 7 | import pytest 8 | 9 | import rarfile 10 | 11 | 12 | def have_tool(name): 13 | for dn in os.get_exec_path(): 14 | if os.path.isfile(os.path.join(dn, name)): 15 | return True 16 | if os.path.isfile(os.path.join(dn, name + ".exe")): 17 | return True 18 | return False 19 | 20 | 21 | def tool_setup(unrar=False, unar=False, bsdtar=False, sevenzip=False, sevenzip2=False): 22 | rarfile.FORCE_TOOL = True 23 | rarfile.tool_setup(unrar=unrar, unar=unar, bsdtar=bsdtar, 24 | sevenzip=sevenzip, sevenzip2=sevenzip2, 25 | force=True) 26 | 27 | 28 | def install_unrar_tool(): 29 | tool_setup(unrar=True) 30 | 31 | 32 | def install_unar_tool(): 33 | tool_setup(unar=True) 34 | 35 | 36 | def install_bsdtar_tool(): 37 | tool_setup(bsdtar=True) 38 | 39 | 40 | def install_7z_tool(): 41 | tool_setup(sevenzip=True) 42 | 43 | 44 | def install_7zz_tool(): 45 | tool_setup(sevenzip2=True) 46 | 47 | 48 | def uninstall_alt_tool(): 49 | rarfile.FORCE_TOOL = False 50 | rarfile.tool_setup(force=True) 51 | 52 | 53 | def test_read_rar3(): 54 | with rarfile.RarFile("test/files/seektest.rar") as rf: 55 | for fn in rf.namelist(): 56 | rf.read(fn) 57 | 58 | 59 | def test_read_vols(): 60 | with rarfile.RarFile("test/files/rar3-old.rar") as rf: 61 | for fn in rf.namelist(): 62 | rf.read(fn) # old 63 | with rarfile.RarFile("test/files/rar3-vols.part1.rar") as rf: 64 | for fn in rf.namelist(): 65 | rf.read(fn) # rar3-new 66 | with rarfile.RarFile("test/files/rar5-vols.part1.rar") as rf: 67 | for fn in rf.namelist(): 68 | rf.read(fn) # rar5 69 | 70 | 71 | def test_unrar_tool(): 72 | install_unrar_tool() 73 | try: 74 | test_read_rar3() 75 | test_read_vols() 76 | 77 | with rarfile.RarFile("test/files/rar3-comment-plain.rar") as rf: 78 | rf.read("file1.txt") 79 | rf.read("file2.txt") 80 | 81 | with rarfile.RarFile("test/files/rar3-comment-psw.rar") as rf: 82 | rf.setpassword("password") 83 | rf.read("file1.txt") 84 | finally: 85 | uninstall_alt_tool() 86 | 87 | 88 | @pytest.mark.skipif(sys.platform == "win32", reason="unar not available on Windows") 89 | @pytest.mark.skipif(not have_tool(rarfile.UNAR_TOOL), reason="unar not installed") 90 | def test_unar_tool(): 91 | install_unar_tool() 92 | try: 93 | test_read_rar3() 94 | test_read_vols() 95 | 96 | with rarfile.RarFile("test/files/rar3-comment-plain.rar") as rf: 97 | rf.read("file1.txt") 98 | rf.read("file2.txt") 99 | 100 | with rarfile.RarFile("test/files/rar3-comment-psw.rar") as rf: 101 | rf.setpassword("password") 102 | rf.read("file1.txt") 103 | finally: 104 | uninstall_alt_tool() 105 | 106 | 107 | @pytest.mark.skipif(not have_tool(rarfile.BSDTAR_TOOL), reason="bsdtar not installed") 108 | def test_bsdtar_tool(): 109 | install_bsdtar_tool() 110 | try: 111 | #test_read_rar3() 112 | #test_read_vols() 113 | 114 | with rarfile.RarFile("test/files/rar3-comment-plain.rar") as rf: 115 | rf.read("file1.txt") 116 | rf.read("file2.txt") 117 | 118 | with pytest.raises(rarfile.RarCannotExec): 119 | with rarfile.RarFile("test/files/rar3-comment-psw.rar") as rf: 120 | rf.setpassword("password") 121 | rf.read("file1.txt") 122 | finally: 123 | uninstall_alt_tool() 124 | 125 | 126 | @pytest.mark.skipif(not have_tool(rarfile.SEVENZIP_TOOL), reason="7z not installed") 127 | def test_7z_tool(): 128 | install_7z_tool() 129 | try: 130 | #test_read_rar3() 131 | test_read_vols() 132 | 133 | with rarfile.RarFile("test/files/rar3-comment-plain.rar") as rf: 134 | rf.read("file1.txt") 135 | rf.read("file2.txt") 136 | 137 | with rarfile.RarFile("test/files/rar3-comment-psw.rar") as rf: 138 | rf.setpassword("password") 139 | rf.read("file1.txt") 140 | finally: 141 | uninstall_alt_tool() 142 | 143 | 144 | @pytest.mark.skipif(not have_tool(rarfile.SEVENZIP2_TOOL), reason="7zz not installed") 145 | def test_7zz_tool(): 146 | install_7zz_tool() 147 | try: 148 | #test_read_rar3() 149 | test_read_vols() 150 | 151 | with rarfile.RarFile("test/files/rar3-comment-plain.rar") as rf: 152 | rf.read("file1.txt") 153 | rf.read("file2.txt") 154 | 155 | with rarfile.RarFile("test/files/rar3-comment-psw.rar") as rf: 156 | rf.setpassword("password") 157 | rf.read("file1.txt") 158 | finally: 159 | uninstall_alt_tool() 160 | 161 | 162 | # test popen errors 163 | 164 | def test_popen_fail(): 165 | with pytest.raises(rarfile.RarCannotExec): 166 | rarfile.custom_popen(["missing-unrar-exe"]) 167 | 168 | if sys.platform != "win32": 169 | with pytest.raises(rarfile.RarCannotExec): 170 | rarfile.custom_popen(["./test/files/rar5-blake.rar.exp"]) 171 | 172 | 173 | def test_check_returncode(): 174 | errmap = rarfile.UNRAR_CONFIG["errmap"] 175 | 176 | assert not rarfile.check_returncode(0, "", errmap) 177 | 178 | with pytest.raises(rarfile.RarFatalError): 179 | rarfile.check_returncode(2, "x", errmap) 180 | with pytest.raises(rarfile.RarUnknownError): 181 | rarfile.check_returncode(100, "", errmap) 182 | with pytest.raises(rarfile.RarUserBreak): 183 | rarfile.check_returncode(255, "", errmap) 184 | with pytest.raises(rarfile.RarSignalExit): 185 | rarfile.check_returncode(-11, "", errmap) 186 | 187 | errmap = rarfile.UNAR_CONFIG["errmap"] 188 | with pytest.raises(rarfile.RarUnknownError): 189 | rarfile.check_returncode(2, "", errmap) 190 | 191 | 192 | # own cli tests 193 | 194 | def cli(*args): 195 | try: 196 | rarfile.main(args) 197 | return 0 198 | except SystemExit as ex: 199 | return int(ex.code) 200 | except Exception as ex: 201 | sys.stderr.write(str(ex) + "\n") 202 | return 1 203 | 204 | 205 | def test_cli_list(capsys): 206 | assert cli("-l", "test/files/rar3-old.rar") == 0 207 | res = capsys.readouterr() 208 | assert "bigfile" in res.out 209 | 210 | 211 | def test_cli_testrar(capsys): 212 | assert cli("-t", "test/files/rar3-old.rar") == 0 213 | res = capsys.readouterr() 214 | assert not res.err 215 | 216 | 217 | def test_cli_extract(capsys, tmp_path): 218 | assert cli("-e", "test/files/rar3-old.rar", str(tmp_path)) == 0 219 | res = capsys.readouterr() 220 | assert not res.err 221 | 222 | 223 | def test_cli_help(capsys): 224 | assert cli("--help") == 0 225 | res = capsys.readouterr() 226 | assert "option" in res.out 227 | 228 | -------------------------------------------------------------------------------- /test/test_util.py: -------------------------------------------------------------------------------- 1 | 2 | # pylint: disable=comparison-with-itself,unneeded-not 3 | 4 | from datetime import datetime, timedelta, timezone 5 | 6 | import rarfile 7 | 8 | 9 | def test_load_vint(): 10 | assert rarfile.load_vint(b"\x00", 0) == (0, 1) 11 | assert rarfile.load_vint(b"\x80\x01", 0) == (1 << 7, 2) 12 | assert rarfile.load_vint(b"\x80\x80\x01", 0) == (1 << 14, 3) 13 | assert rarfile.load_vint(b"\x80\x80\x80\x01", 0) == (1 << 21, 4) 14 | assert rarfile.load_vint(b"\x80\x80\x80\x80\x01", 0) == (1 << 28, 5) 15 | assert rarfile.load_vint(b"\x80\x80\x80\x80\x80\x01", 0) == (1 << 35, 6) 16 | assert rarfile.load_vint(b"\x80" * 10 + b"\x01", 0) == (1 << 70, 11) 17 | 18 | 19 | def test_to_datetime(): 20 | assert rarfile.to_datetime((2020, 0, 0, 0, 0, 0)) == datetime(2020, 1, 1, 0, 0, 0) 21 | assert rarfile.to_datetime((2020, 60, 60, 60, 60, 60)) == datetime(2020, 12, 31, 23, 59, 59) 22 | assert rarfile.to_datetime((2020, 2, 30, 60, 60, 60)) == datetime(2020, 2, 28, 23, 59, 59) 23 | assert rarfile.to_datetime((2021, 2, 30, 60, 60, 60)) == datetime(2021, 2, 28, 23, 59, 59) 24 | 25 | 26 | def test_to_nsdatetime(): 27 | base = datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc) 28 | assert rarfile.to_nsdatetime(base, 0) is base 29 | 30 | res = rarfile.to_nsdatetime(base, 1000) 31 | assert res == base.replace(microsecond=1) 32 | assert isinstance(res, datetime) 33 | assert res.isoformat(" ") == "2020-01-01 00:00:00.000001+00:00" 34 | 35 | res = rarfile.to_nsdatetime(base, 1001) 36 | assert isinstance(res, datetime) 37 | assert isinstance(res, rarfile.nsdatetime) 38 | assert res.microsecond == 1 39 | assert res.nanosecond == 1001 40 | assert res.isoformat(" ") == "2020-01-01 00:00:00.000001001+00:00" 41 | assert res.isoformat(" ", "auto") == "2020-01-01 00:00:00.000001001+00:00" 42 | assert res.isoformat(" ", "microseconds") == "2020-01-01 00:00:00.000001+00:00" 43 | 44 | 45 | def test_nsdatetime_cmp(): 46 | nsdatetime = rarfile.nsdatetime 47 | 48 | n1 = nsdatetime(2000, 1, 1, 9, 15, 30, nanosecond=100200300, tzinfo=timezone.utc) 49 | n2 = nsdatetime(2000, 1, 1, 9, 15, 30, nanosecond=100200301, tzinfo=timezone.utc) 50 | n3 = nsdatetime(2000, 1, 1, 9, 15, 30, nanosecond=100200402, tzinfo=timezone.utc) 51 | 52 | d1 = datetime(2000, 1, 1, 9, 15, 30, 100100, timezone.utc) 53 | d2 = datetime(2000, 1, 1, 9, 15, 30, 100200, timezone.utc) 54 | d3 = datetime(2000, 1, 1, 9, 15, 30, 100300, timezone.utc) 55 | 56 | n2x = n2 + timedelta(seconds=0) 57 | assert not isinstance(n2x, nsdatetime) 58 | assert not hasattr(n2x, "_nanoseconds") 59 | assert n2x == d2 60 | assert hash(n2x) == hash(d2) 61 | assert hash(n2) != hash(d2) 62 | 63 | # compare nsdatetime only 64 | n1c = n1.replace() 65 | assert n1 == n1 66 | assert n1 == n1c 67 | assert n1 <= n1c 68 | assert n1 >= n1c 69 | assert n1 < n2 70 | assert n1 <= n2 71 | assert n1 != n2 72 | assert not n1 == n2 73 | assert n2 > n1 74 | assert n2 >= n1 75 | assert not n2 < n1 76 | assert not n1 > n2 77 | 78 | # mixed eq 79 | assert not d2 == n2 80 | assert not n2 == d2 81 | assert d2 != n2 82 | assert n2 != d2 83 | 84 | # mixed gt 85 | assert n2 > d2 86 | assert d3 > n2 87 | assert not d2 > n3 88 | assert not n2 > d3 89 | 90 | # mixed lt 91 | assert d1 < n2 < d3 92 | 93 | 94 | def test_nsdatetime_astimezone(): 95 | nsdatetime = rarfile.nsdatetime 96 | X1 = timezone(timedelta(hours=1), "X1") 97 | 98 | n1 = nsdatetime(2000, 1, 1, 9, 15, 30, nanosecond=100200402, tzinfo=timezone.utc) 99 | n2 = n1.astimezone(X1) 100 | assert n2.nanosecond == n1.nanosecond 101 | assert (n1.year, n1.month, n1.day) == (n2.year, n2.month, n2.day) 102 | assert (n1.hour, n1.minute, n1.second) == (n2.hour - 1, n2.minute, n2.second) 103 | 104 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | 2 | [tox] 3 | envlist = lint,docs,py3-pycryptodome,py3-cryptography,py3 4 | 5 | [package] 6 | name = rarfile 7 | deps = 8 | pycryptodome: pycryptodome==3.20.0 9 | cryptography: cryptography==42.0.5 10 | test_deps = 11 | #coverage==7.3.1 12 | coverage==7.4.4 13 | pytest==8.1.1 14 | pytest-cov==5.0.0 15 | pytest-xdist==3.5.0 16 | doc_deps = 17 | sphinx==7.2.6 18 | docutils==0.20.1 19 | lint_deps = 20 | pylint==3.1.0 21 | 22 | [testenv] 23 | deps = 24 | {[package]deps} 25 | {[package]test_deps} 26 | commands = 27 | pytest --cov=rarfile --cov-report=term --cov-report=html:{toxinidir}/cover/{envname} {posargs} 28 | sh ./test/run_dump.sh {envpython} {envname} 29 | allowlist_externals = sh 30 | 31 | [testenv:lint] 32 | basepython = python3 33 | deps = 34 | {[package]deps} 35 | {[package]lint_deps} 36 | {[package]test_deps} 37 | commands = 38 | pylint rarfile.py dumprar.py test 39 | 40 | [testenv:docs] 41 | basepython = python3 42 | deps = {[package]doc_deps} 43 | changedir = doc 44 | commands = 45 | sphinx-build -q -W -b html -d {envtmpdir}/doctrees . ../tmp/dochtml 46 | 47 | --------------------------------------------------------------------------------