diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..db626e64 --- /dev/null +++ b/.clang-format @@ -0,0 +1,88 @@ +--- +# Formatting style for php-rar C source files. +# Does NOT apply to unrar/ (see unrar/.clang-format). +Language: Cpp +BasedOnStyle: LLVM + +# Indentation: real tabs, width 4 (matches vim modeline: noet sw=4 ts=4) +UseTab: Always +TabWidth: 4 +IndentWidth: 4 +ContinuationIndentWidth: 4 + +# Disable line-length wrapping to preserve hand-formatted long lines +ColumnLimit: 0 + +# Brace style: +# - Functions: brace on its own line (Allman) +# - Structs/unions/enums/extern "C": brace attached (K&R) +# - Control statements: brace attached (K&R) +# - else/else if: on its own line (after closing }) +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + +# Preserve indentation of preprocessor directives (# include, # define inside #ifdef) +IndentPPDirectives: AfterHash +PPIndentWidth: 1 + +# Pointer star attached to variable name: char *ptr +PointerAlignment: Right +DerivePointerAlignment: false + +# Spaces +SpaceBeforeParens: ControlStatements +SpaceAfterCStyleCast: true +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpaceBeforeAssignmentOperators: true + +# Don't align backslashes in multi-line macros (just 1 space before \) +AlignEscapedNewlines: DontAlign + +# ZEND_BEGIN/END_ARG_INFO_EX act as block delimiters so ZEND_ARG_INFO lines +# inside them keep their indentation. +MacroBlockBegin: "^ZEND_BEGIN_ARG_INFO" +MacroBlockEnd: "^ZEND_END_ARG_INFO" + +# Align continuation arguments after opening paren; when ( is last on the line, +# fall back to a block indent (ContinuationIndentWidth). +AlignAfterOpenBracket: Align + +# Preserve existing include order +SortIncludes: Never + +# case labels are indented one level inside switch +IndentCaseLabels: true + +# Don't collapse or expand short constructs +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false + +# Alignment: leave hand-aligned blocks alone +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignTrailingComments: false + +# Blank lines +MaxEmptyLinesToKeep: 2 +KeepEmptyLinesAtTheStartOfBlocks: true diff --git a/.github/docker-image-shas.yml b/.github/docker-image-shas.yml new file mode 100644 index 00000000..976f8ac1 --- /dev/null +++ b/.github/docker-image-shas.yml @@ -0,0 +1,32 @@ +# Docker image SHA lock file. +# Maps image tags to their multi-arch OCI index digests (amd64 + arm64). +# Regenerate with: .github/scripts/update-docker-shas.sh + +ghcr.io/cataphract/php-minimal: + 7.0-debug: "sha256:c1a08b9c71b30c3ed4c31bb6ca9f4c81f82f4351c5e6e0c423a8f980ce3c89ee" + 7.0-release-zts: "sha256:9d9f1b1337822e1c069cf49c22f5f6b55ba806547cccbb4836e847fe8e32819a" + 7.1-debug: "sha256:b670b5de5dd8d0791a86b29e336b5a7d801fbdeaeadf9148e616cf5357b9035e" + 7.1-release-zts: "sha256:1f3164d4cdd57faa0911de43c4c77b7d18fdf84971db97ff2889f1a5ed8a0e5a" + 7.2-debug: "sha256:7c1d2b2248a64339d8b0ae7bcbe4156fc8e3903653ce1d86347bfeacb9679c0d" + 7.2-release-zts: "sha256:5e1f47179d730c22c8eada20f9cbedb034f3b97c89174ce4e6108e7d76bf7da5" + 7.3-debug: "sha256:b0b4c0a57e52011469c7999b330020d4ef27628eb8ac745e7e74796c8ff8601a" + 7.3-release-zts: "sha256:fe021f3979bc4c69be0d73ec74a30085915939a62f457557be91e003938d6e0a" + 7.4-debug: "sha256:d2d827524eae4a7d391b5f71b33477a1affabc74f674f9680b7838c3a5154d7f" + 7.4-release-zts: "sha256:6c5eacd2f493c1c07a68f451b72a62ed67ddb2406dab8e3dc82f20adc7e72bf7" + 8.0-debug: "sha256:d46c15e648497cf55bb6b4806c726c58714f5b63fcb1096e4a8ec433ec17c835" + 8.0-release-zts: "sha256:bcbaab72e7c0e704d7bdef01e8f91e1bc0636e6e98c0ab68cc2fc417e7a98a66" + 8.1-debug: "sha256:dbdb437df0a4acbbd11e020461f7cef2f0e949f46f9ca841c5f058012ec7cc98" + 8.1-release: "sha256:f401dba3b4249dc16acb90909951b88e3146dcd2e622203d95e80219258932c9" + 8.1-release-zts: "sha256:2b330ce54679f29e09ea77c984cd409618142cc20cab48b5a114cc1e5b09257c" + 8.2-debug: "sha256:efecf1cdbd09511aec12897a03682934243498c5bb2ba37874ae2100c1498011" + 8.2-release: "sha256:e09be007096a29d64427afa99ea4d10f7e1a91a8e2129aeaa854daf045986d8f" + 8.2-release-zts: "sha256:d67d1c370abdc5e629a2bec1ff4d308f0ccb7d3cbc7ecc3c7eee851cd6c2efaa" + 8.3-debug: "sha256:1b8e6ba17c837a4035f6981dd38f9f447be57a3d558ca8f18071c8390a62ce6c" + 8.3-release: "sha256:23abac21af8c95ccd6cd6a8b5fbb2d1dd67e122fc7630bbbfcc7b75e6a6fcc96" + 8.3-release-zts: "sha256:158060357f81b495a5420b1f9d488abd61d59306946b352e4fbfc4011b7a2d89" + 8.4-debug: "sha256:c863e2a60f144856f56d50ed1080bbe0377ca99760195f170c762ad46f0466e5" + 8.4-release: "sha256:571251d31267c6960846727f0788f1d9a593a02adf6a68899a0582c5635d5c50" + 8.4-release-zts: "sha256:6451c7104d874f8c4f7e0305e5baaecebbd74672a036c02de2cbb9d1a10c8906" + 8.5-debug: "sha256:014a16949dd0da0581cda6768bd7751f66b6ada3becdc949aeb5b8e1efb3c5cf" + 8.5-release: "sha256:bc58405b860c17ea5f36cbd466dc5c93b97283c148e3489ec78171d73727859c" + 8.5-release-zts: "sha256:dadd96c4740bc9606f2985be51fb0b85d768132f93786c4ae52940552f3cbdc6" diff --git a/.github/scripts/build-and-test.sh b/.github/scripts/build-and-test.sh new file mode 100755 index 00000000..01498f01 --- /dev/null +++ b/.github/scripts/build-and-test.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Build the extension and run the test suite. +# Expected to run inside a php-minimal container from the repo root. +set -euo pipefail + +# Clean up all generated files so stale objects from a previous PHP version or +# build variant don't silently survive into this build. +if [ -f Makefile ]; then + make -f Makefile distclean +fi +# Remove any leftover object files even if Makefile is gone (e.g. after a +# partial prior build that ended before distclean could run). +find . -name '*.lo' -o -name '*.o' | xargs rm -f 2>/dev/null || true +find . -name '.libs' -type d | xargs rm -rf 2>/dev/null || true + +phpize +./configure --with-php-config="$(which php-config)" +make -f Makefile -j"$(nproc)" + +# The generated Makefile silences and ignores errors on the `if` commands; +# undo that so test failures surface properly. +sed -i 's/-@if/@if/' Makefile + +ret=0 +TEST_PHP_EXECUTABLE="$(which php)" \ +TEST_PHP_JUNIT=report.xml \ +REPORT_EXIT_STATUS=1 \ +NO_INTERACTION=1 \ +TESTS="--set-timeout 300 --show-diff" \ + make -f Makefile test || ret=$? + +found=$(find tests -name '*.mem' | wc -l) +if [ "$found" -gt 0 ]; then + echo "Found $found memory leak(s):" + find tests -name '*.mem' -print -exec cat {} \; + ret=1 +fi + +exit "$ret" diff --git a/.github/scripts/update-docker-shas.sh b/.github/scripts/update-docker-shas.sh new file mode 100755 index 00000000..c7b0e295 --- /dev/null +++ b/.github/scripts/update-docker-shas.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Refresh docker-image-shas.yml with current OCI index digests. +# The index digest covers all platforms (amd64 + arm64); Docker resolves the +# right platform image from it at pull time. +# Justfile and tests.yml read from docker-image-shas.yml directly — no patching needed. +set -euo pipefail + +IMAGE="ghcr.io/cataphract/php-minimal" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOCK_FILE="$SCRIPT_DIR/../docker-image-shas.yml" + +TAGS=( + 7.0-debug 7.0-release-zts + 7.1-debug 7.1-release-zts + 7.2-debug 7.2-release-zts + 7.3-debug 7.3-release-zts + 7.4-debug 7.4-release-zts + 8.0-debug 8.0-release-zts + 8.1-debug 8.1-release 8.1-release-zts + 8.2-debug 8.2-release 8.2-release-zts + 8.3-debug 8.3-release 8.3-release-zts + 8.4-debug 8.4-release 8.4-release-zts + 8.5-debug 8.5-release 8.5-release-zts +) + +get_index_digest() { + local tag="$1" digest attempt + for attempt in 1 2 3; do + digest=$(docker buildx imagetools inspect "${IMAGE}:${tag}" \ + | awk '/^Digest:/ { print $2; exit }') + if [[ -n "$digest" ]]; then + echo "$digest" + return 0 + fi + echo "Attempt $attempt failed for $tag, retrying..." >&2 + sleep $((attempt * 5)) + done + echo "ERROR: could not fetch digest for $tag after 3 attempts" >&2 + return 1 +} + +# Collect all digests first so we fail fast before touching any file. +declare -A DIGESTS +for tag in "${TAGS[@]}"; do + echo "Fetching $tag ..." >&2 + DIGESTS[$tag]=$(get_index_digest "$tag") +done + +# ── Update docker-image-shas.yml ───────────────────────────────────────────── + +{ + echo "# Docker image SHA lock file." + echo "# Maps image tags to their multi-arch OCI index digests (amd64 + arm64)." + echo "# Regenerate with: .github/scripts/update-docker-shas.sh" + echo "" + echo "${IMAGE}:" + for tag in "${TAGS[@]}"; do + printf " %-20s \"%s\"\n" "${tag}:" "${DIGESTS[$tag]}" + done +} > "$LOCK_FILE" +echo "Updated $LOCK_FILE" >&2 diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml new file mode 100644 index 00000000..3c4db4aa --- /dev/null +++ b/.github/workflows/docker-images.yml @@ -0,0 +1,162 @@ +name: Build Docker images + +on: + push: + branches: [docker] + +env: + MUSL_ENV_IMAGE: ghcr.io/cataphract/musl-build-env + PHP_IMAGE: ghcr.io/cataphract/php-minimal + +jobs: + # ── Push: build musl-build-env for both arches ─────────────────────────── + musl-build-env: + name: musl-build-env / ${{ matrix.arch }} + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + matrix: + include: + - arch: x86_64 + platform: linux/amd64 + runner: ubuntu-latest + - arch: aarch64 + platform: linux/arm64 + runner: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: docker/musl-build-env + platforms: ${{ matrix.platform }} + push: true + tags: ${{ env.MUSL_ENV_IMAGE }}:latest-${{ matrix.arch }} + build-args: ARCH=${{ matrix.arch }} + cache-from: type=gha,scope=musl-build-env-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=musl-build-env-${{ matrix.arch }} + + # ── Push: build php-minimal for all PHP versions and variants ───────────── + php-minimal: + name: php-minimal ${{ matrix.php }}-${{ matrix.variant }} / ${{ matrix.arch }} + needs: musl-build-env + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + php: + - "7.0" + - "7.1" + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + - "8.3" + - "8.4" + - "8.5" + variant: + - debug + - release + - release-zts + arch: + - x86_64 + - aarch64 + include: + - arch: x86_64 + platform: linux/amd64 + runner: ubuntu-latest + - arch: aarch64 + platform: linux/arm64 + runner: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve PHP patch version and build flags + id: cfg + run: | + declare -A PHP_VERSIONS=( + [7.0]=7.0.33 [7.1]=7.1.33 [7.2]=7.2.34 [7.3]=7.3.33 [7.4]=7.4.33 + [8.0]=8.0.30 [8.1]=8.1.32 [8.2]=8.2.28 [8.3]=8.3.19 [8.4]=8.4.6 + [8.5]=8.5.0 + ) + patch="${PHP_VERSIONS[${{ matrix.php }}]}" + case "${{ matrix.variant }}" in + debug) debug=yes; zts=no ;; + release) debug=no; zts=no ;; + release-zts) debug=no; zts=yes ;; + esac + echo "patch=${patch}" >> "$GITHUB_OUTPUT" + echo "debug=${debug}" >> "$GITHUB_OUTPUT" + echo "zts=${zts}" >> "$GITHUB_OUTPUT" + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: docker/php-minimal + platforms: ${{ matrix.platform }} + push: true + tags: ${{ env.PHP_IMAGE }}:${{ matrix.php }}-${{ matrix.variant }}-${{ matrix.arch }} + build-args: | + BUILD_ENV_IMAGE=${{ env.MUSL_ENV_IMAGE }}:latest-${{ matrix.arch }} + PHP_VERSION=${{ steps.cfg.outputs.patch }} + ARCH=${{ matrix.arch }} + PHP_ENABLE_DEBUG=${{ steps.cfg.outputs.debug }} + PHP_ENABLE_ZTS=${{ steps.cfg.outputs.zts }} + cache-from: type=gha,scope=php-${{ matrix.php }}-${{ matrix.variant }}-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=php-${{ matrix.php }}-${{ matrix.variant }}-${{ matrix.arch }} + + # ── Push: create multi-arch manifests ──────────────────────────────────── + merge-manifests: + name: Create multi-arch manifests + needs: php-minimal + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge musl-build-env manifest + run: | + docker buildx imagetools create \ + -t ${{ env.MUSL_ENV_IMAGE }}:latest \ + ${{ env.MUSL_ENV_IMAGE }}:latest-x86_64 \ + ${{ env.MUSL_ENV_IMAGE }}:latest-aarch64 + + - name: Merge php-minimal manifests + run: | + for minor in 7.0 7.1 7.2 7.3 7.4 8.0 8.1 8.2 8.3 8.4 8.5; do + for variant in debug release release-zts; do + docker buildx imagetools create \ + -t ${{ env.PHP_IMAGE }}:${minor}-${variant} \ + ${{ env.PHP_IMAGE }}:${minor}-${variant}-x86_64 \ + ${{ env.PHP_IMAGE }}:${minor}-${variant}-aarch64 + done + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..547d4f82 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,191 @@ +name: Release binaries + +on: + push: + tags: + - 'v[0-9]*.[0-9]*.[0-9]*' + +permissions: + contents: read + +jobs: + check-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check version consistency + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + HEADER_VERSION=$(grep -oP '(?<=#define PHP_RAR_VERSION ")[^"]+' php_rar.h) + PACKAGE_VERSION=$(python3 -c " + import xml.etree.ElementTree as ET + t = ET.parse('package.xml') + ns = {'p': 'http://pear.php.net/dtd/package-2.0'} + print(t.find('p:version/p:release', ns).text) + ") + + echo "Tag version: $TAG_VERSION" + echo "Header version: $HEADER_VERSION" + echo "Package version: $PACKAGE_VERSION" + + if [ "$TAG_VERSION" != "$HEADER_VERSION" ]; then + echo "ERROR: Tag version ($TAG_VERSION) does not match PHP_RAR_VERSION in php_rar.h ($HEADER_VERSION)" + exit 1 + fi + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "ERROR: Tag version ($TAG_VERSION) does not match version in package.xml ($PACKAGE_VERSION)" + exit 1 + fi + echo "All versions match: $TAG_VERSION" + + create-draft-release: + needs: [check-version] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: 'true' + ref: ${{ github.ref }} + + - name: Create draft release from tag + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "${{ github.ref_name }}" --title "${{ github.ref_name }}" --draft --notes-from-tag + + linux-extension-matrix: + needs: [create-draft-release] + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + python3 <<'EOF' + import yaml, json, os + with open('.github/docker-image-shas.yml') as f: + data = yaml.safe_load(f) + image = 'ghcr.io/cataphract/php-minimal' + includes = [] + for tag, sha in data[image].items(): + ver, variant = tag.split('-', 1) + if variant not in ('release', 'release-zts'): + continue + if float(ver) < 8.1: + continue + for arch, runner in [('x86_64', 'ubuntu-latest'), ('aarch64', 'ubuntu-24.04-arm')]: + includes.append({'php': ver, 'variant': variant, 'image_sha': sha, + 'arch': arch, 'runner': runner}) + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('matrix=' + json.dumps({'include': includes}) + '\n') + EOF + + linux-build: + name: Linux PHP ${{ matrix.php }} (${{ matrix.variant }}, ${{ matrix.arch }}) + needs: [linux-extension-matrix] + runs-on: ${{ matrix.runner }} + permissions: + contents: write + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.linux-extension-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + + - name: Install patchelf + run: sudo apt-get install -y patchelf + + - name: Build rar.so in musl container + run: | + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -v "$GITHUB_WORKSPACE:/workspace" \ + -w /workspace \ + "ghcr.io/cataphract/php-minimal@${{ matrix.image_sha }}" \ + sh -c 'phpize && ./configure --with-php-config=$(which php-config) && make -j$(nproc)' + + - name: Remove musl DT_NEEDED from rar.so + run: patchelf --remove-needed "libc.musl-$(uname -m).so.1" modules/rar.so + + - name: Package and upload to release + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="${GITHUB_REF_NAME#v}" + ARCH="${{ matrix.arch }}" + [ "$ARCH" = "aarch64" ] && ARCH="arm64" + ZTS_SUFFIX="" + [[ "${{ matrix.variant }}" == "release-zts" ]] && ZTS_SUFFIX="-zts" + ARTIFACT="php_rar-${VERSION}_php${{ matrix.php }}-${ARCH}-linux${ZTS_SUFFIX}.zip" + zip -j "$ARTIFACT" modules/rar.so + gh release upload "${{ github.ref_name }}" "$ARTIFACT" --clobber + + windows-extension-matrix: + needs: [create-draft-release] + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.extension-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Get the extension matrix + id: extension-matrix + uses: php/php-windows-builder/extension-matrix@v1 + with: + php-version-list: '8.0, 8.1, 8.2, 8.3, 8.4, 8.5' + + windows-build: + needs: [windows-extension-matrix] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.windows-extension-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + + - name: Build the extension for Windows + uses: php/php-windows-builder/extension@v1 + with: + php-version: ${{ matrix.php-version }} + arch: ${{ matrix.arch }} + ts: ${{ matrix.ts }} + args: --enable-rar=shared + test-runner: run-tests-rar.php + + windows-release: + needs: [windows-build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Upload artifacts to the release + uses: php/php-windows-builder/release@v1 + with: + release: ${{ github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + draft: 'true' + + pecl-package: + needs: [create-draft-release] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + tools: pecl + + - name: Build PECL package + run: pecl package + + - name: Upload PECL package to release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${{ github.ref_name }}" rar-*.tgz diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..8636a9c1 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,156 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + check-dev-version: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' + steps: + - uses: actions/checkout@v4 + + - name: Check PHP_RAR_VERSION ends in -dev + run: | + HEADER_VERSION=$(grep -oP '(?<=#define PHP_RAR_VERSION ")[^"]+' php_rar.h) + echo "Header version: $HEADER_VERSION" + if [[ "$HEADER_VERSION" != *-dev ]]; then + echo "ERROR: PHP_RAR_VERSION ($HEADER_VERSION) does not end in -dev on master" + exit 1 + fi + + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + python3 <<'EOF' + import yaml, json, os + with open('.github/docker-image-shas.yml') as f: + data = yaml.safe_load(f) + image = 'ghcr.io/cataphract/php-minimal' + includes = [] + for tag, sha in data[image].items(): + # tag: "7.0-debug" or "7.0-release-zts"; skip glibc-only entries + ver, variant = tag.split('-', 1) + if variant not in ('debug', 'release-zts'): + continue + includes.append({'php': ver, 'variant': variant, 'image_sha': sha}) + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('matrix=' + json.dumps({'include': includes}) + '\n') + EOF + + linux: + name: PHP ${{ matrix.php }} (${{ matrix.variant }}) + needs: generate-matrix + runs-on: ubuntu-latest + container: + image: ghcr.io/cataphract/php-minimal@${{ matrix.image_sha }} + options: --user root + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build and test + run: bash .github/scripts/build-and-test.sh + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-php${{ matrix.php }}-${{ matrix.variant }} + path: report.xml + + glibc: + name: glibc (PHP 8.3 release, ${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runner: ubuntu-latest + - arch: aarch64 + runner: ubuntu-24.04-arm + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Read build image SHA + id: image-sha + run: | + python3 -c " + import yaml, os + with open('.github/docker-image-shas.yml') as f: + d = yaml.safe_load(f) + sha = d['ghcr.io/cataphract/php-minimal']['8.3-release'] + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('sha=' + sha + '\n') + " + + - name: Install PHP 8.3 and patchelf + run: | + sudo apt-get update -q + sudo apt-get install -y php8.3 php8.3-dev patchelf + + - name: Build rar.so in musl container + run: | + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -v "$GITHUB_WORKSPACE:/workspace" \ + -w /workspace \ + "ghcr.io/cataphract/php-minimal@${{ steps.image-sha.outputs.sha }}" \ + sh -c 'phpize && ./configure --with-php-config=$(which php-config) && make -j$(nproc)' + + - name: Remove musl DT_NEEDED from rar.so + run: patchelf --remove-needed "libc.musl-$(uname -m).so.1" modules/rar.so + + - name: Run tests + run: | + TEST_PHP_EXECUTABLE=$(which php8.3) \ + TEST_PHP_JUNIT=report.xml \ + REPORT_EXIT_STATUS=1 \ + NO_INTERACTION=1 \ + php8.3 run-tests-rar.php \ + -n -d extension_dir=modules/ -d extension=rar.so \ + --set-timeout 300 --show-diff \ + tests/ + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-glibc-php8.3-release-${{ matrix.arch }} + path: report.xml + + windows: + name: Windows PHP 8.1 TS + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build and test + uses: php/php-windows-builder/extension@v1 + with: + php-version: '8.1' + arch: x64 + ts: ts + args: --enable-rar=shared + test-runner: ${{ github.workspace }}/run-tests-rar.php + test-workers: 1 + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-windows-php8.1-ts + path: '*.xml' diff --git a/.gitignore b/.gitignore index 86886db5..196d9e00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o *.lo +/run-tests.php /tests/*.sh /tests/*.exp /tests/*.diff @@ -7,7 +8,6 @@ /tests/*.php /tests/*.out /tests/*.mem -/run-tests.php /modules /missing /.deps @@ -47,3 +47,8 @@ /php-rar.creator.user /compile_commands.json /.clangd +/report.xml +/.worktrees +*.dep +/configure~ +/.cache/ diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..84039c7c --- /dev/null +++ b/Justfile @@ -0,0 +1,113 @@ +# Build and test inside the matching CI Docker image. +# Image SHAs are read from .github/docker-image-shas.yml (multi-arch OCI index digests). +# To refresh SHAs: .github/scripts/update-docker-shas.sh + +# ── Images ──────────────────────────────────────────────────────────────────── + +_base := "ghcr.io/cataphract/php-minimal@" +_shas := ".github/docker-image-shas.yml" + +image_7_0_debug := _base + `grep '7.0-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_0_release_zts := _base + `grep '7.0-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_1_debug := _base + `grep '7.1-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_1_release_zts := _base + `grep '7.1-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_2_debug := _base + `grep '7.2-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_2_release_zts := _base + `grep '7.2-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_3_debug := _base + `grep '7.3-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_3_release_zts := _base + `grep '7.3-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_4_debug := _base + `grep '7.4-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_7_4_release_zts := _base + `grep '7.4-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_0_debug := _base + `grep '8.0-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_0_release_zts := _base + `grep '8.0-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_1_debug := _base + `grep '8.1-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_1_release_zts := _base + `grep '8.1-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_2_debug := _base + `grep '8.2-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_2_release_zts := _base + `grep '8.2-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_3_debug := _base + `grep '8.3-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_3_release_zts := _base + `grep '8.3-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_4_debug := _base + `grep '8.4-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_4_release_zts := _base + `grep '8.4-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_5_debug := _base + `grep '8.5-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_8_5_release_zts := _base + `grep '8.5-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` + +_run := "docker run --rm --entrypoint bash -v \"$PWD:/workspace\" -w /workspace --user root" + +# ── Default ─────────────────────────────────────────────────────────────────── + +default: + @just --list + +# ── Individual targets ──────────────────────────────────────────────────────── + +test-7_0-debug: + {{_run}} {{image_7_0_debug}} .github/scripts/build-and-test.sh +test-7_0-release-zts: + {{_run}} {{image_7_0_release_zts}} .github/scripts/build-and-test.sh + +test-7_1-debug: + {{_run}} {{image_7_1_debug}} .github/scripts/build-and-test.sh +test-7_1-release-zts: + {{_run}} {{image_7_1_release_zts}} .github/scripts/build-and-test.sh + +test-7_2-debug: + {{_run}} {{image_7_2_debug}} .github/scripts/build-and-test.sh +test-7_2-release-zts: + {{_run}} {{image_7_2_release_zts}} .github/scripts/build-and-test.sh + +test-7_3-debug: + {{_run}} {{image_7_3_debug}} .github/scripts/build-and-test.sh +test-7_3-release-zts: + {{_run}} {{image_7_3_release_zts}} .github/scripts/build-and-test.sh + +test-7_4-debug: + {{_run}} {{image_7_4_debug}} .github/scripts/build-and-test.sh +test-7_4-release-zts: + {{_run}} {{image_7_4_release_zts}} .github/scripts/build-and-test.sh + +test-8_0-debug: + {{_run}} {{image_8_0_debug}} .github/scripts/build-and-test.sh +test-8_0-release-zts: + {{_run}} {{image_8_0_release_zts}} .github/scripts/build-and-test.sh + +test-8_1-debug: + {{_run}} {{image_8_1_debug}} .github/scripts/build-and-test.sh +test-8_1-release-zts: + {{_run}} {{image_8_1_release_zts}} .github/scripts/build-and-test.sh + +test-8_2-debug: + {{_run}} {{image_8_2_debug}} .github/scripts/build-and-test.sh +test-8_2-release-zts: + {{_run}} {{image_8_2_release_zts}} .github/scripts/build-and-test.sh + +test-8_3-debug: + {{_run}} {{image_8_3_debug}} .github/scripts/build-and-test.sh +test-8_3-release-zts: + {{_run}} {{image_8_3_release_zts}} .github/scripts/build-and-test.sh + +test-8_4-debug: + {{_run}} {{image_8_4_debug}} .github/scripts/build-and-test.sh +test-8_4-release-zts: + {{_run}} {{image_8_4_release_zts}} .github/scripts/build-and-test.sh + +test-8_5-debug: + {{_run}} {{image_8_5_debug}} .github/scripts/build-and-test.sh +test-8_5-release-zts: + {{_run}} {{image_8_5_release_zts}} .github/scripts/build-and-test.sh + +# ── Per-version aggregates (sequential to avoid workspace conflicts) ─────────── + +test-7_0: test-7_0-debug test-7_0-release-zts +test-7_1: test-7_1-debug test-7_1-release-zts +test-7_2: test-7_2-debug test-7_2-release-zts +test-7_3: test-7_3-debug test-7_3-release-zts +test-7_4: test-7_4-debug test-7_4-release-zts +test-8_0: test-8_0-debug test-8_0-release-zts +test-8_1: test-8_1-debug test-8_1-release-zts +test-8_2: test-8_2-debug test-8_2-release-zts +test-8_3: test-8_3-debug test-8_3-release-zts +test-8_4: test-8_4-debug test-8_4-release-zts +test-8_5: test-8_5-debug test-8_5-release-zts + +# ── All Linux targets ───────────────────────────────────────────────────────── + +test-linux: test-7_0 test-7_1 test-7_2 test-7_3 test-7_4 test-8_0 test-8_1 test-8_2 test-8_3 test-8_4 test-8_5 diff --git a/Makefile.frag b/Makefile.frag index 6c96fd40..8e8bb437 100644 --- a/Makefile.frag +++ b/Makefile.frag @@ -1,6 +1,7 @@ +EXTRA_CFLAGS := $(EXTRA_CFLAGS) -Wall + +.PHONY: replace-run-tests replace-run-tests: - @if ! grep -q 'Minimum required PHP version: 5\.3\.0' run-tests.php; then \ - cp run-tests8.php run-tests.php; \ - fi + cp run-tests-rar.php run-tests.php test: replace-run-tests diff --git a/README.md b/README.md index d354d4fb..b85323d2 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,22 @@ unrar/LICENSE.txt for details. Some modifications have been applied to the UnRAR library, mainly to allow streaming extraction of files without using threads. -[![Build Status Appveyor](https://ci.appveyor.com/api/projects/status/cbgpepx6kyax2198/branch/master?svg=true)](https://ci.appveyor.com/project/cataphract/php-rar/branch/master) -[![Build Status Travis](https://travis-ci.org/cataphract/php-rar.svg?branch=master)](https://travis-ci.org/cataphract/php-rar) -[![codecov](https://codecov.io/gh/cataphract/php-rar/branch/master/graph/badge.svg)](https://codecov.io/gh/cataphract/php-rar) +## Installation + +### With PECL + +```sh +pecl install rar +``` + +Then add `extension=rar` to your `php.ini`. + +### With PIE + +[PIE](https://github.com/php/pie) is the modern replacement for PECL, available from PHP 8.1+. + +```sh +pie install rar +``` + +PIE automatically adds the extension to your `php.ini`. diff --git a/appveyor.bat b/appveyor.bat deleted file mode 100644 index 24dd8e94..00000000 --- a/appveyor.bat +++ /dev/null @@ -1,31 +0,0 @@ -echo Running appveyor.bat -echo on - -CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\vcvars32.bat" - -IF NOT EXIST "C:\projects\php-sdk" ( - wget -nv http://windows.php.net/downloads/php-sdk/php-sdk-binary-tools-20110915.zip - 7z x -y php-sdk-binary-tools-20110915.zip -oC:\projects\php-sdk -) -IF NOT EXIST "C:\projects\php-src\Release_TS\php7ts.lib" ( - git clone --depth=1 --branch=PHP-7.1 https://github.com/php/php-src C:\projects\php-src - wget -nv http://windows.php.net/downloads/php-sdk/deps-7.1-vc14-x86.7z - 7z x -y deps-7.1-vc14-x86.7z -oC:\projects\php-src - CALL C:\projects\php-sdk\bin\phpsdk_setvars.bat - cd C:\projects\php-src - CALL buildconf.bat - CALL configure.bat --disable-all --enable-cli --with-config-file-scan-dir=C:\projects\extension\bin\modules.d --with-prefix=%APPVEYOR_BUILD_FOLDER%\bin --with-php-build=deps - nmake -) ELSE ( - echo php7ts.lib already exists - cd C:\projects\php-src - CALL C:\projects\php-sdk\bin\phpsdk_setvars.bat -) - -CALL buildconf.bat --force --add-modules-dir=%APPVEYOR_BUILD_FOLDER%\.. -CALL configure.bat --disable-all --enable-cli --enable-rar=shared --with-config-file-scan-dir=C:\projects\extension\bin\modules.d --with-prefix=%APPVEYOR_BUILD_FOLDER%\bin --with-php-build=deps -nmake || exit /b -rmdir Release_TS\php-rar /S /Q -del /S /Q "Release_TS\*.sbr" - -copy %APPVEYOR_BUILD_FOLDER%\run-tests8.php C:\projects\php-src\run-tests.php diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6560b7f5..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Based on igbinary's appveyor config. - -version: '{branch}.{build}' -install: - - cmd: choco feature enable -n=allowGlobalConfirmation - - cmd: cinst wget - - cmd: mkdir %APPVEYOR_BUILD_FOLDER%\bin - -build_script: - - cmd: "%APPVEYOR_BUILD_FOLDER%\\appveyor.bat" - -test_script: - - cmd: cd C:\projects\php-src - - cmd: set NO_INTERACTION=1 - - cmd: set REPORT_EXIT_STATUS=1 - - cmd: set TZ=UTC - - cmd: 'nmake test TESTS="--show-diff --set-timeout 30 -d extension=C:\projects\php-src\Release_TS\php_rar.dll %APPVEYOR_BUILD_FOLDER%\tests"' - -after_test: -# - cmd: dir /S C:\projects\php-src\Release_TS - - cmd: 'del C:\projects\php-src\Release_TS\php_rar.*' - -cache: - - C:\ProgramData\chocolatey\lib -> appveyor.yml - - C:\projects\php-src - - C:\projects\php-sdk diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 0f5fb944..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,85 +0,0 @@ -jobs: - - template: azure-template.yml - parameters: - name: php_5_3_valgrind - displayName: PHP 5.3 ZTS (valgrind, clang) - phpVersion: '5.3' - clang: true - valgrind: true - zts: true - - - template: azure-template.yml - parameters: - name: php_5_4 - displayName: PHP 5.4 - phpVersion: '5.4' - - - template: azure-template.yml - parameters: - name: php_5_5 - displayName: PHP 5.5 - phpVersion: '5.5' - - - template: azure-template.yml - parameters: - name: php_5_6 - displayName: PHP 5.6 - phpVersion: '5.6' - - - template: azure-template.yml - parameters: - name: php_5_6_valgrind - displayName: PHP 5.6 ZTS (valgrind, clang) - phpVersion: '5.6' - clang: true - valgrind: true - zts: true - - - template: azure-template.yml - parameters: - name: php_7_0 - displayName: PHP 7.0 - phpVersion: '7.0' - - - template: azure-template.yml - parameters: - name: php_7_1 - displayName: PHP 7.1 - phpVersion: '7.1' - - - template: azure-template.yml - parameters: - name: php_7_2 - displayName: PHP 7.2 - phpVersion: '7.2' - - - template: azure-template.yml - parameters: - name: php_7_3 - displayName: PHP 7.3 - phpVersion: '7.3' - - - template: azure-template.yml - parameters: - name: php_7_4 - displayName: PHP 7.4 ZTS (valgrind, clang) - phpVersion: '7.4' - clang: true - valgrind: true - zts: true - - - template: azure-template.yml - parameters: - name: php_8_0 - displayName: PHP 8.0 ZTS - phpVersion: '8.0' - zts: true - publishCoverage: true - - - template: azure-template.yml - parameters: - name: php_8_0_valgrind - displayName: PHP 8.0 (valgrind, clang) - phpVersion: '8.0' - clang: true - valgrind: true diff --git a/azure-template.yml b/azure-template.yml deleted file mode 100644 index b23fda08..00000000 --- a/azure-template.yml +++ /dev/null @@ -1,65 +0,0 @@ -parameters: - name: ~ - displayName: ~ - phpVersion: bundled - imageName: ubuntu-20.04 - zts: false - clang: false - publishCoverage: false - valgrind: false - -jobs: - - job: ${{ parameters.name }} - displayName: ${{ parameters.displayName }} - pool: - vmImage: ${{ parameters.imageName }} - - variables: - ${{ if eq(parameters.clang, true) }}: - CC: clang - CXX: clang++ - - steps: - - checkout: self - displayName: Checkout - clean: true - submodules: recursive - - - bash: | - source test_funcs.sh - install_php ${{ parameters.phpVersion }} ${{ parameters.zts }} - displayName: Install PHP ${{ parameters.phpVersion }} - - - ${{ if eq(parameters.valgrind, true) }}: - - bash: > - sudo apt-get install valgrind - displayName: "Install Valgrind" - - - script: | - source test_funcs.sh - build_ext ${{ parameters.phpVersion }} ${{ parameters.zts }} ${{ parameters.publishCoverage }} - displayName: Build extension - - - script: | - source test_funcs.sh - run_tests ${{ parameters.phpVersion }} ${{ parameters.zts }} ${{ parameters.publishCoverage }} - ${{ if eq(parameters.valgrind, true) }}: - env: - RUN_TESTS_FLAGS: -m - displayName: Run tests - - - task: PublishTestResults@2 - displayName: Publish test results - condition: succeededOrFailed() - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: 'report.xml' - searchFolder: '$(System.DefaultWorkingDirectory)' - mergeTestResults: true - - - ${{ if eq(parameters.publishCoverage, true) }}: - - bash: > - bash <(curl -s https://codecov.io/bash) - displayName: "Publish coverage (Codecov)" - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) diff --git a/composer.json b/composer.json index 30795efb..6f683e62 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,31 @@ { - "name": "rar", - "type": "extension", - "license": [ - "PHP License" - ], + "name": "cataphract/rar", + "type": "php-ext", + "description": "PHP extension for reading RAR archives using bundled unRAR library", + "license": "PHP-3.01", "authors": [ { "name": "Gustavo Lopes", - "email": "cataphract@php.net" + "email": "cataphract@php.net", + "role": "lead" }, { "name": "Antony Dovgal", - "email": "tony@daylessday.org" + "email": "tony@daylessday.org", + "role": "developer" } ], - "description": "rar extension" -} \ No newline at end of file + "require": { + "php": "^7.0 || ^8.0" + }, + "replace": { + "ext-rar": "*" + }, + "support": { + "source": "https://github.com/cataphract/php-rar" + }, + "php-ext": { + "extension-name": "rar", + "download-url-method": ["pre-packaged-binary", "composer-default"] + } +} diff --git a/config.m4 b/config.m4 index e1ee121e..0225a8a9 100644 --- a/config.m4 +++ b/config.m4 @@ -29,7 +29,8 @@ unrar_sources="unrar/sha256.cpp unrar/qopen.cpp \ unrar/arcread.cpp unrar/filefn.cpp \ unrar/global.cpp unrar/list.cpp \ unrar/encname.cpp unrar/file.cpp \ - unrar/secpassword.cpp unrar/options.cpp" + unrar/secpassword.cpp unrar/options.cpp \ + unrar/largepage.cpp" AC_LANG_PUSH([C++]) @@ -51,23 +52,40 @@ CXXFLAGS="$ac_saved_cxxflags" CXXC_FLAG_CHECK([-Wparentheses], [-Wno-parentheses]) CXXC_FLAG_CHECK([-Wswitch], [-Wno-switch]) CXXC_FLAG_CHECK([-Wdangling-else], [-Wno-dangling-else]) +CXXC_FLAG_CHECK([-Wlogical-op-parentheses], [-Wno-logical-op-parentheses]) +CXXC_FLAG_CHECK([-Wmissing-braces], [-Wno-missing-braces]) CXXC_FLAG_CHECK([-Wunused-function], [-Wno-unused-function]) CXXC_FLAG_CHECK([-Wunused-variable], [-Wno-unused-variable]) CXXC_FLAG_CHECK([-Wsign-compare], [-Wno-sign-compare]) CXXC_FLAG_CHECK([-Wmisleading-indentation], [-Wno-misleading-indentation]) AC_LANG_POP([C++]) -extra_cxxflags="$cxxflags_null" +extra_cxxflags="-Wall $cxxflags_null" echo "EXTRA_CXXFLAGS := \$(EXTRA_CXXFLAGS) $extra_cxxflags" >> Makefile.fragments cat Makefile.frag >> Makefile.fragments INCLUDES=`echo "$INCLUDES" | sed 's/-I/-isystem /g'` +dnl Move -Wall into CFLAGS/CXXFLAGS so it precedes EXTRA_CXXFLAGS in compile +dnl commands; the -Wno-* suppression flags in EXTRA_CXXFLAGS must come last. +CFLAGS="$CFLAGS -Wall" +CXXFLAGS="$CXXFLAGS -Wall" if test "$PHP_RAR" != "no"; then AC_DEFINE(HAVE_RAR, 1, [Whether you have rar support]) PHP_SUBST(RAR_SHARED_LIBADD) PHP_REQUIRE_CXX() - PHP_ADD_LIBRARY_WITH_PATH(stdc++, "", RAR_SHARED_LIBADD) - PHP_NEW_EXTENSION(rar, rar.c rar_error.c rararch.c rarentry.c rar_stream.c rar_navigation.c rar_time.c $unrar_sources, $ext_shared,,-DRARDLL -DSILENT -Wno-write-strings -Wall -fvisibility=hidden -I@ext_srcdir@/unrar) + PHP_NEW_EXTENSION(rar, rar.c rar_error.c rararch.c rarentry.c rar_stream.c rar_navigation.c rar_time.c $unrar_sources, $ext_shared,,-DRARDLL -DSILENT -fPIC -fvisibility=hidden -I@ext_srcdir@/unrar, yes) PHP_ADD_BUILD_DIR($ext_builddir/unrar) + + AC_MSG_CHECKING([whether linker supports version scripts]) + rar_save_ldflags="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--version-script=$ext_srcdir/rar.map" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[void get_module(void) {}]], [])], + [AC_MSG_RESULT([yes]) + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,--version-script=$ext_srcdir/rar.map"], + [AC_MSG_RESULT([no])] + ) + LDFLAGS="$rar_save_ldflags" + PHP_SUBST(EXTRA_LDFLAGS) fi diff --git a/config.w32 b/config.w32 index b72a7c09..54e898c8 100644 --- a/config.w32 +++ b/config.w32 @@ -18,7 +18,7 @@ if (PHP_RAR != "no") { crc.cpp rijndael.cpp crypt.cpp \ rawread.cpp \ rs.cpp smallfn.cpp \ - isnt.cpp rar.cpp consio.cpp \ + isnt.cpp consio.cpp \ scantree.cpp archive.cpp strfn.cpp \ strlist.cpp \ getbits.cpp hash.cpp \ @@ -31,7 +31,8 @@ if (PHP_RAR != "no") { arcread.cpp filefn.cpp \ global.cpp list.cpp \ encname.cpp file.cpp \ - secpassword.cpp options.cpp", "rar"); + secpassword.cpp options.cpp \ + largepage.cpp motw.cpp", "rar"); AC_DEFINE("HAVE_RAR", 1, "Rar support"); } diff --git a/docker/musl-build-env/Dockerfile b/docker/musl-build-env/Dockerfile new file mode 100644 index 00000000..d1eaafa9 --- /dev/null +++ b/docker/musl-build-env/Dockerfile @@ -0,0 +1,113 @@ +# Build environment using Alpine 3.23 with LLVM runtimes built from source. +# Provides a musl-based toolchain for building portable Linux binaries +# that work on both musl and glibc systems (via glibc_compat.a). +# +# Build: +# docker build --build-arg ARCH=aarch64 -t musl-build-env:latest . +# docker build --build-arg ARCH=x86_64 -t musl-build-env:latest . + +FROM alpine:3.23 + +ARG ARCH + +RUN test -n "${ARCH}" || (echo "ARCH build arg is required (aarch64 or x86_64)" && false) + +# Install build tools and dependencies +RUN apk --no-cache add \ + alpine-sdk bash binutils clang cmake compiler-rt \ + coreutils git linux-headers lld llvm make \ + musl-dev patchelf python3 wget xz + +# Apply musl patches for glibc ABI compatibility +COPY locale.h.diff /locale.h.diff +RUN cd /usr/include && patch -p0 < /locale.h.diff + +COPY alltypes.h.diff /alltypes.h.diff +RUN cd /usr/include && patch -p0 < /alltypes.h.diff + +# Download and build LLVM runtimes (libunwind, libc++abi, libc++) matching +# the system clang version so headers, ABIs and compiler-rt all align. +# Detect the full version string (e.g. 21.1.2) from the installed clang. +# Only the runtimes are built from source — the system clang is the bootstrap. +RUN set -e && \ + LLVM_VER=$(clang --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) && \ + echo "Building LLVM ${LLVM_VER} runtimes to match system clang" && \ + wget -q "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/llvm-project-${LLVM_VER}.src.tar.xz" && \ + tar -xf "llvm-project-${LLVM_VER}.src.tar.xz" && \ + rm "llvm-project-${LLVM_VER}.src.tar.xz" && \ + cd "llvm-project-${LLVM_VER}.src" && mkdir build && cd build && \ + cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_C_FLAGS="-fno-omit-frame-pointer" \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_FLAGS="-fno-omit-frame-pointer" \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DLIBUNWIND_ENABLE_SHARED=OFF \ + -DLIBUNWIND_ENABLE_STATIC=ON \ + -DLIBUNWIND_USE_COMPILER_RT=ON \ + -DLIBCXXABI_ENABLE_SHARED=OFF \ + -DLIBCXXABI_USE_LLVM_UNWINDER=ON \ + -DLIBCXXABI_ENABLE_STATIC_UNWINDER=ON \ + -DLIBCXXABI_USE_COMPILER_RT=ON \ + -DLIBCXX_ENABLE_SHARED=OFF \ + -DLIBCXX_HAS_MUSL_LIBC=ON \ + -DLIBCXX_USE_COMPILER_RT=ON \ + -DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \ + ../runtimes && \ + make -j$(nproc) install-unwind install-cxxabi install-cxx && \ + cd / && rm -rf "/llvm-project-${LLVM_VER}.src" + +# Set up symlinks using the system LLVM and GCC versions detected at build time. +# Use grep -oE (not -oP) since busybox grep lacks Perl regex support. +RUN set -e && \ + SYS_LLVM_VER=$(clang --version | grep -oE '[0-9]+' | head -1) && \ + GCC_VER=$(ls /usr/lib/gcc/${ARCH}-alpine-linux-musl/) && \ + echo "System LLVM: ${SYS_LLVM_VER}, GCC: ${GCC_VER}" && \ + GCC_DIR=/usr/lib/gcc/${ARCH}-alpine-linux-musl/${GCC_VER} && \ + CLANG_DIR=/usr/lib/llvm${SYS_LLVM_VER}/lib/clang/${SYS_LLVM_VER} && \ + ln -s ${GCC_DIR} /usr/lib/resource_dir && \ + ln -s ${CLANG_DIR}/lib /usr/lib/resource_dir/lib && \ + ln -s ${CLANG_DIR}/lib/${ARCH}-alpine-linux-musl/libclang_rt.builtins-${ARCH}.a \ + /usr/lib/libclang_rt.builtins.a && \ + GCC_STDATOMIC=${GCC_DIR}/include/stdatomic.h && \ + if [ -f "${GCC_STDATOMIC}" ]; then \ + mv "${GCC_STDATOMIC}" "${GCC_STDATOMIC}_" && \ + cp ${CLANG_DIR}/include/stdatomic.h "${GCC_STDATOMIC}"; \ + fi + +# Set up sysroot directory structure. +# Programs compiled with --sysroot=/sysroot/ARCH-none-linux-musl will find +# headers and libraries via these symlinks. +RUN mkdir -p /sysroot/${ARCH}-none-linux-musl/usr && \ + ln -s /usr/lib /sysroot/${ARCH}-none-linux-musl/usr/lib && \ + ln -s /usr/include /sysroot/${ARCH}-none-linux-musl/usr/include && \ + ln -s /lib /sysroot/${ARCH}-none-linux-musl/lib + +# Build glibc_compat.c into a static archive. +# This library ensures binaries work on both musl and glibc: +# - On musl: musl exports __xstat and other glibc compat symbols, so the +# wrappers in glibc_compat resolve correctly. +# - On glibc: glibc_compat bridges the musl-compiled ABI to glibc internals. +# Always link against -lglibc_compat; the musl-clang wrapper does this by default. +COPY glibc_compat.c /sysroot/glibc_compat.c +RUN clang \ + --target=${ARCH}-none-linux-musl \ + --sysroot=/sysroot/${ARCH}-none-linux-musl \ + -rtlib=compiler-rt \ + -fpie -O2 -fno-omit-frame-pointer -ggdb3 \ + -c /sysroot/glibc_compat.c -o /tmp/glibc_compat.o && \ + ar rcs /sysroot/${ARCH}-none-linux-musl/usr/lib/libglibc_compat.a /tmp/glibc_compat.o && \ + rm /tmp/glibc_compat.o + +# Install CMake toolchain file for this architecture +COPY Toolchain.cmake.${ARCH} /sysroot/${ARCH}-none-linux-musl/Toolchain.cmake + +# Install musl-clang / musl-clang++ wrapper scripts. +# These encode the target triple, sysroot, required compiler flags, and always +# link -lglibc_compat so every binary works on both musl and glibc systems. +COPY musl-clang.sh /usr/local/bin/musl-clang +COPY musl-clang++.sh /usr/local/bin/musl-clang++ +RUN chmod +x /usr/local/bin/musl-clang /usr/local/bin/musl-clang++ diff --git a/docker/musl-build-env/Toolchain.cmake.aarch64 b/docker/musl-build-env/Toolchain.cmake.aarch64 new file mode 100644 index 00000000..42dca1bd --- /dev/null +++ b/docker/musl-build-env/Toolchain.cmake.aarch64 @@ -0,0 +1,28 @@ +# vim: set ft=cmake: +set(sysroot /sysroot/aarch64-none-linux-musl) +set(arch aarch64) +set(interpreter ld-musl-aarch64.so.1) +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR ${arch}) +set(CMAKE_SYSROOT ${sysroot}) +set(CMAKE_AR /usr/bin/llvm-ar) +set(triple ${arch}-none-linux-musl) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +set(CMAKE_C_COMPILER /usr/bin/clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) + +set(c_cxx_flags "-Qunused-arguments -rtlib=compiler-rt -unwindlib=libunwind -static-libgcc -fno-omit-frame-pointer") +set(CMAKE_C_FLAGS_INIT ${c_cxx_flags}) +set(CMAKE_CXX_COMPILER /usr/bin/clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++ ${c_cxx_flags}") + +set(linker_flags "-fuse-ld=lld -nodefaultlibs -Wl,-Bstatic -lc++ -lc++abi ${sysroot}/usr/lib/libclang_rt.builtins.a -lunwind -lglibc_compat -Wl,-Bdynamic ${sysroot}/usr/lib/libclang_rt.builtins.a -Wl,--dynamic-linker,${sysroot}/lib/${interpreter} -Wl,-rpath=${sysroot} -resource-dir ${sysroot}/usr/lib/resource_dir") +set(CMAKE_EXE_LINKER_FLAGS_INIT "${linker_flags} -Wl,--dynamic-linker,${sysroot}/lib/${interpreter}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT ${linker_flags}) +set(CMAKE_C_STANDARD_LIBRARIES "-Wl,-Bdynamic -lc") +set(CMAKE_CXX_STANDARD_LIBRARIES "-Wl,-Bdynamic -lc") + +set(CMAKE_NM /usr/bin/llvm-nm) +set(CMAKE_RANLIB /usr/bin/llvm-ranlib) +set(CMAKE_STRIP /usr/bin/strip) diff --git a/docker/musl-build-env/Toolchain.cmake.x86_64 b/docker/musl-build-env/Toolchain.cmake.x86_64 new file mode 100644 index 00000000..029be042 --- /dev/null +++ b/docker/musl-build-env/Toolchain.cmake.x86_64 @@ -0,0 +1,28 @@ +# vim: set ft=cmake: +set(sysroot /sysroot/x86_64-none-linux-musl) +set(arch x86_64) +set(interpreter ld-musl-x86_64.so.1) +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR ${arch}) +set(CMAKE_SYSROOT ${sysroot}) +set(CMAKE_AR /usr/bin/llvm-ar) +set(triple ${arch}-none-linux-musl) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +set(CMAKE_C_COMPILER /usr/bin/clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) + +set(c_cxx_flags "-Qunused-arguments -rtlib=compiler-rt -unwindlib=libunwind -static-libgcc -fno-omit-frame-pointer") +set(CMAKE_C_FLAGS_INIT ${c_cxx_flags}) +set(CMAKE_CXX_COMPILER /usr/bin/clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++ ${c_cxx_flags}") + +set(linker_flags "-fuse-ld=lld -nodefaultlibs -Wl,-Bstatic -lc++ -lc++abi ${sysroot}/usr/lib/libclang_rt.builtins.a -lunwind -lglibc_compat -Wl,-Bdynamic ${sysroot}/usr/lib/libclang_rt.builtins.a -Wl,--dynamic-linker,${sysroot}/lib/${interpreter} -Wl,-rpath=${sysroot} -resource-dir ${sysroot}/usr/lib/resource_dir") +set(CMAKE_EXE_LINKER_FLAGS_INIT "${linker_flags} -Wl,--dynamic-linker,${sysroot}/lib/${interpreter}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT ${linker_flags}) +set(CMAKE_C_STANDARD_LIBRARIES "-Wl,-Bdynamic -lc") +set(CMAKE_CXX_STANDARD_LIBRARIES "-Wl,-Bdynamic -lc") + +set(CMAKE_NM /usr/bin/llvm-nm) +set(CMAKE_RANLIB /usr/bin/llvm-ranlib) +set(CMAKE_STRIP /usr/bin/strip) diff --git a/docker/musl-build-env/alltypes.h.diff b/docker/musl-build-env/alltypes.h.diff new file mode 100644 index 00000000..967c1b29 --- /dev/null +++ b/docker/musl-build-env/alltypes.h.diff @@ -0,0 +1,60 @@ +--- bits/alltypes.h ++++ bits/alltypes.h +@@ -299,17 +299,32 @@ + #endif + + #if defined(__NEED_pthread_mutexattr_t) && !defined(__DEFINED_pthread_mutexattr_t) +-typedef struct { unsigned __attr; } pthread_mutexattr_t; ++typedef struct { union { unsigned __attr; ++#ifdef __aarch64__ ++ long __glibc_compat; ++#endif ++}; ++} pthread_mutexattr_t; + #define __DEFINED_pthread_mutexattr_t + #endif + + #if defined(__NEED_pthread_condattr_t) && !defined(__DEFINED_pthread_condattr_t) +-typedef struct { unsigned __attr; } pthread_condattr_t; ++typedef struct { union { unsigned __attr; ++#ifdef __aarch64__ ++ long __glibc_compat; ++#endif ++}; ++} pthread_condattr_t; + #define __DEFINED_pthread_condattr_t + #endif + + #if defined(__NEED_pthread_barrierattr_t) && !defined(__DEFINED_pthread_barrierattr_t) +-typedef struct { unsigned __attr; } pthread_barrierattr_t; ++typedef struct { union { unsigned __attr; ++#ifdef __aarch64__ ++ long __glibc_compat; ++#endif ++}; ++} pthread_barrierattr_t; + #define __DEFINED_pthread_barrierattr_t + #endif + +@@ -383,12 +398,20 @@ + + + #if defined(__NEED_pthread_attr_t) && !defined(__DEFINED_pthread_attr_t) +-typedef struct { union { int __i[sizeof(long)==8?14:9]; volatile int __vi[sizeof(long)==8?14:9]; unsigned long __s[sizeof(long)==8?7:9]; } __u; } pthread_attr_t; ++typedef struct { union { int __i[sizeof(long)==8?14:9]; volatile int __vi[sizeof(long)==8?14:9]; unsigned long __s[sizeof(long)==8?7:9]; ++#ifdef __aarch64__ ++ char __glibc_compat[64]; ++#endif ++} __u; } pthread_attr_t; + #define __DEFINED_pthread_attr_t + #endif + + #if defined(__NEED_pthread_mutex_t) && !defined(__DEFINED_pthread_mutex_t) +-typedef struct { union { int __i[sizeof(long)==8?10:6]; volatile int __vi[sizeof(long)==8?10:6]; volatile void *volatile __p[sizeof(long)==8?5:6]; } __u; } pthread_mutex_t; ++typedef struct { union { int __i[sizeof(long)==8?10:6]; volatile int __vi[sizeof(long)==8?10:6]; volatile void *volatile __p[sizeof(long)==8?5:6]; ++#ifdef __aarch64__ ++ char __glibc_compat[48]; ++#endif ++} __u; } pthread_mutex_t; + #define __DEFINED_pthread_mutex_t + #endif diff --git a/docker/musl-build-env/glibc_compat.c b/docker/musl-build-env/glibc_compat.c new file mode 100644 index 00000000..3ae15464 --- /dev/null +++ b/docker/musl-build-env/glibc_compat.c @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) && !defined(__GLIBC__) + +# ifdef __x86_64__ +float ceilf(float x) +{ + float result; + // NOLINTNEXTLINE(hicpp-no-assembler) + __asm__("roundss $0x0A, %[x], %[result]" + : [result] "=x"(result) + : [x] "x"(x)); + return result; +} +double ceil(double x) +{ + double result; + // NOLINTNEXTLINE(hicpp-no-assembler) + __asm__("roundsd $0x0A, %[x], %[result]" + : [result] "=x"(result) + : [x] "x"(x)); + return result; +} +# endif + +# ifdef __aarch64__ +float ceilf(float x) +{ + float result; + __asm__("frintp %s0, %s1\n" : "=w"(result) : "w"(x)); + return result; +} +double ceil(double x) +{ + double result; + __asm__("frintp %d0, %d1\n" : "=w"(result) : "w"(x)); + return result; +} +# endif + +# ifdef __aarch64__ +# define _STAT_VER 0 +# else +# define _STAT_VER 1 +# endif + +// glibc before 2.33 (2021) doesn't have these +int stat(const char *restrict path, void *restrict buf) +{ + int __xstat(int, const char *restrict, void *restrict); + return __xstat(_STAT_VER, path, buf); +} + +int fstat(int fd, void *buf) +{ + int __fxstat(int, int, void *); + return __fxstat(_STAT_VER, fd, buf); +} + +int lstat(const char *restrict path, void *restrict buf) +{ + int __lxstat(int, const char *restrict, void *restrict); + return __lxstat(_STAT_VER, path, buf); +} + +int fstatat(int dirfd, const char *restrict pathname, void *restrict statbuf, int flags) +{ + int __fxstatat(int, int, const char *restrict, void *restrict, int); + return __fxstatat(_STAT_VER, dirfd, pathname, statbuf, flags); +} + +// glibc doesn't define pthread_atfork on aarch64. We need to delegate to +// glibc's __register_atfork() instead. __register_atfork() takes an extra +// argument, __dso_handle, which is a pointer to the DSO that is registering the +// fork handlers. This is used to ensure that the handlers are not called after +// the DSO is unloaded. glibc on amd64 also implements pthread_atfork() in terms +// of __register_atfork(). (musl never unloads modules so that potential +// problem doesn't exist) + +// On amd64, even though pthread_atfork is exported by glibc, it should not be +// used. Code that uses pthread_atfork will compile to an import to +// __register_atfork(), but here we're compiling against musl, resulting in an +// an import to pthread_atfork. This will cause a runtime error when unloading +// a shared module. The reason is that when we call pthread_atfork in glibc, +// __register_atfork() is called with the __dso_handle of libc6.so, not the +// __dso_handle of our module. So the fork handler is not unregistered when our +// module is unloaded. + +extern void *__dso_handle __attribute__((weak)); +int __register_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void), void *__dso_handle) __attribute__((weak)); + +int pthread_atfork( + void (*prepare)(void), void (*parent)(void), void (*child)(void)) +{ + // glibc + if (__dso_handle && __register_atfork) { + return __register_atfork(prepare, parent, child, __dso_handle); + } + + static int (*real_atfork)(void (*)(void), void (*)(void), void (*)(void)); + + if (!real_atfork) { + // dlopen musl +# ifdef __aarch64__ + void *handle = dlopen("ld-musl-aarch64.so.1", RTLD_LAZY); + if (!handle) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlopen of ld-musl-aarch64.so.1 failed: %s\n", + dlerror()); + abort(); + } +# else + void *handle = dlopen("libc.musl-x86_64.so.1", RTLD_LAZY); + if (!handle) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlopen of libc.musl-x86_64.so.1 failed: %s\n", + dlerror()); + abort(); + } +# endif + real_atfork = dlsym(handle, "pthread_atfork"); + if (!real_atfork) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlsym of pthread_atfork failed: %s\n", dlerror()); + abort(); + } + } + + return real_atfork(prepare, parent, child); +} + +# ifdef __x86_64__ +struct pthread_cond; +struct pthread_condattr; +typedef struct pthread_cond pthread_cond_t; +typedef struct pthread_condattr pthread_condattr_t; + +int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr) +{ + static int (*real_pthread_cond_init)(pthread_cond_t *cond, const pthread_condattr_t *cond_attr); + + if (!real_pthread_cond_init) { + void *handle = dlopen("libc.so.6", RTLD_LAZY); + if (!handle) { + void *handle = dlopen("libc.musl-x86_64.so.1", RTLD_LAZY); + if (!handle) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlopen of libc.so.6 and libc.musl-x86_64.so.1 failed: %s\n", + dlerror()); + abort(); + } + } + + real_pthread_cond_init = dlsym(handle, "pthread_cond_init"); + if (!real_pthread_cond_init) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlsym of pthread_cond_init failed: %s\n", dlerror()); + abort(); + } + } + + return real_pthread_cond_init(cond, cond_attr); +} +# endif + +// the symbol strerror_r in glibc is not the POSIX version; it returns char * +// __xpg_sterror_r is exported by both glibc and musl +int strerror_r(int errnum, char *buf, size_t buflen) +{ + int __xpg_strerror_r(int, char *, size_t); + return __xpg_strerror_r(errnum, buf, buflen); +} + +// when compiling with --coverage, some references to atexit show up. +// glibc doesn't provide atexit for similar reasons as pthread_atfork presumably +int __cxa_atexit(void (*func)(void *), void *arg, void *dso_handle); +int atexit(void (*function)(void)) +{ + if (!__dso_handle) { + (void)fprintf(stderr, "Aborting because __dso_handle is NULL\n"); + abort(); + } + + // the cast is harmless on amd64 and aarch64. Passing an extra argument to a + // function that expects none causes no problems + return __cxa_atexit((void (*)(void *))function, 0, __dso_handle); +} + +// introduced in glibc 2.25 +ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) { + int fd; + size_t bytes_read = 0; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return -1; + } + + while (bytes_read < buflen) { + ssize_t result = read(fd, (char*)buf + bytes_read, buflen - bytes_read); + if (result < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + return -1; + } + bytes_read += result; + } + + close(fd); + return (ssize_t)bytes_read; +} + +#ifdef __x86_64__ +#define MEMFD_CREATE_SYSCALL 319 +#elif __aarch64__ +#define MEMFD_CREATE_SYSCALL 279 +#endif + +// introduced in glibc 2.27 +int memfd_create(const char *name, unsigned flags) { + return syscall(MEMFD_CREATE_SYSCALL, name, flags); +} + +// __flt_rounds is the backing function behind musl's FLT_ROUNDS macro: musl's +// defines FLT_ROUNDS as (__flt_rounds()), making it dynamic — it +// reflects the current rounding mode after fesetround(). glibc/GCC does not +// export __flt_rounds; GCC's defines FLT_ROUNDS as the compile-time +// constant 1 (round-to-nearest), so it never tracks fesetround() at all (GCC +// bug #59046 — GCC's own float.h has the comment "??? This is supposed to +// change with calls to fesetround in "). fegetround() provides the +// actual hardware rounding mode on both. +int __flt_rounds(void) +{ + switch (fegetround()) { + case FE_TONEAREST: return 1; + case FE_UPWARD: return 2; + case FE_DOWNWARD: return 3; + case FE_TOWARDZERO: return 0; + default: return -1; + } +} + +// glibc has never exported sigsetjmp as a dynamic symbol — it is defined in +// as a macro expanding to __sigsetjmp, so only __sigsetjmp appears +// in the DSO. Musl exports both sigsetjmp ajd __sigsetjmp as real symbols. +// sigsetjmp is referenced directly in compiled code, so we provide this +// bridge. +int sigsetjmp(sigjmp_buf env, int savemask) +{ + int __sigsetjmp(sigjmp_buf, int); + return __sigsetjmp(env, savemask); +} + +// glibc no longer exports res_init as a dynamic symbol (it never did for amd64 +// or aarch64, only for older archs). Only __res_init is exported. +// Musl-compiled code references res_init directly. __res_init is declared weak +// so linking succeeds on musl (where it doesn't exist). At runtime: on glibc +// it's non-NULL and called directly; on musl it's NULL and we fall through to +// dlopen musl's own res_init. +extern int __res_init(void) __attribute__((weak)); + +int res_init(void) +{ + if (__res_init) + return __res_init(); + + // musl path: find res_init in musl's libc via dlopen + static int (*musl_res_init)(void); + if (!musl_res_init) { +# ifdef __aarch64__ + void *handle = dlopen("ld-musl-aarch64.so.1", RTLD_LAZY); +# else + void *handle = dlopen("libc.musl-x86_64.so.1", RTLD_LAZY); +# endif + if (handle) { + musl_res_init = dlsym(handle, "res_init"); + } else { + (void)fprintf(stderr, "Aborting because dlopen() of musl failed\n"); + abort(); + } + } + if (musl_res_init) { + return musl_res_init(); + } else { + (void)fprintf(stderr, "Aborting because res_init/__res_init could not " + "be found"); + abort(); + } +} + +#endif diff --git a/docker/musl-build-env/locale.h.diff b/docker/musl-build-env/locale.h.diff new file mode 100644 index 00000000..408367ec --- /dev/null +++ b/docker/musl-build-env/locale.h.diff @@ -0,0 +1,11 @@ +-- locale.h ++++ locale.h +@@ -71,7 +71,7 @@ + #define LC_COLLATE_MASK (1< no - 2020-12-06 + 2026-03-15 - 4.2.0 - 4.2.0 + 4.3.1 + 4.0.0 @@ -36,9 +36,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> PHP License - - Support PHP 8. -- Merge unrar 6.0.2. -- RarArchive implements IteratorAggregate (PHP 8 only). + - Fix bug #75557: error opening archive with non-English characters in path on Windows. +- Do not build unconditionally against listdc++. @@ -111,8 +110,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - @@ -148,7 +145,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - @@ -159,6 +155,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -193,7 +191,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - @@ -248,6 +245,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -258,6 +257,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -307,7 +308,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - @@ -340,7 +340,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - @@ -356,7 +355,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + @@ -364,7 +363,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 5.3.0 + 7.0.0 1.4.0 @@ -376,6 +375,60 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 4.3.1 + 4.0.0 + + + stable + stable + + 2026-03-15 + Changes in this version: +- Fix bug #75557: error opening archive with non-English characters in path on Windows. +- Do not build unconditionally against listdc++. + + + + + + 4.3.0 + 4.0.0 + + + stable + stable + + 2026-03-08 + Changes in this version: +- Add PHP 8.1, 8.2, 8.3 support. +- Drop PHP 5 support; minimum PHP version is now 7.0. +- Update bundled unrar to 7.2.4. +- Fix segfault caused by uninitialized RARHeaderDataEx. +- Fix dll.cpp: don't propagate non-fatal ErrHandler errors as failures. +- Fix RAR5 chunk extraction spurious warning on Windows with multi-core CPUs. +- Migrate CI from Azure Pipelines/Appveyor to GitHub Actions. + + + + + + 4.2.0 + 4.0.0 + + + stable + stable + + 2020-12-06 + Changes in this version: +- Support PHP 8. +- Merge unrar 6.0.2. +- RarArchive implements IteratorAggregate (PHP 8 only). + + + 4.1.0 diff --git a/php_compat.h b/php_compat.h index 51d1a581..961f932a 100644 --- a/php_compat.h +++ b/php_compat.h @@ -15,7 +15,6 @@ typedef zend_object handler_this_t; typedef zval handler_this_t; #endif -#if PHP_MAJOR_VERSION >= 7 typedef zend_object* rar_obj_ref; #define rar_zval_add_ref(ppzv) zval_add_ref(*ppzv) @@ -53,20 +52,3 @@ typedef size_t zpp_s_size_t; #define INIT_ZVAL(zv) ZVAL_UNDEF(&zv) #define ZEND_ACC_FINAL_CLASS ZEND_ACC_FINAL - -#else /* PHP 5.x */ -typedef zend_object_handle rar_obj_ref; - -#define rar_zval_add_ref zval_add_ref -#define ZVAL_ALLOC_DUP(dst, src) \ - do { \ - zval *z_src = src; \ - dst = z_src; \ - zval_add_ref(&dst); \ - SEPARATE_ZVAL(&dst); \ - } while (0) -#define RAR_ZVAL_STRING ZVAL_STRING -#define RAR_RETURN_STRINGL(s, l, duplicate) RETURN_STRINGL(s, l, duplicate) -typedef int zpp_s_size_t; -#define zend_hash_str_del zend_hash_del -#endif diff --git a/php_rar.h b/php_rar.h index 9bdee067..98793739 100644 --- a/php_rar.h +++ b/php_rar.h @@ -51,7 +51,7 @@ extern zend_module_entry rar_module_entry; #define phpext_rar_ptr &rar_module_entry -#define PHP_RAR_VERSION "4.2.0" +#define PHP_RAR_VERSION "4.3.2-dev" #ifdef PHP_WIN32 #define PHP_RAR_API __declspec(dllexport) @@ -77,6 +77,7 @@ extern zend_module_entry rar_module_entry; #include "unrar/dll.hpp" #include "unrar/version.hpp" /* These are in unrar/headers.hpp, but that header depends on several other */ +/* clang-format off */ enum HOST_SYSTEM { HOST_MSDOS=0,HOST_OS2=1,HOST_WIN32=2,HOST_UNIX=3,HOST_MACOS=4, HOST_BEOS=5,HOST_MAX @@ -85,10 +86,12 @@ enum FILE_SYSTEM_REDIRECT { FSREDIR_NONE=0, FSREDIR_UNIXSYMLINK, FSREDIR_WINSYMLINK, FSREDIR_JUNCTION, FSREDIR_HARDLINK, FSREDIR_FILECOPY }; +/* clang-format on */ /* maximum comment size if 64KB */ #define RAR_MAX_COMMENT_SIZE 65536 +/* clang-format off */ typedef struct _rar_cb_user_data { char *password; /* can be NULL */ zval *callable; /* can be NULL */ @@ -105,13 +108,19 @@ typedef struct rar { rar_cb_user_data cb_userdata; int allow_broken; } rar_file_t; +/* clang-format on */ /* Misc */ -#if defined(ZTS) && PHP_MAJOR_VERSION < 7 -# define RAR_TSRMLS_TC , void *** -#else +# if defined(__GNUC__) || defined(__clang__) +# define ARR_SIZE(arr) \ + (sizeof(arr) / sizeof((arr)[0]) + \ + 0 * sizeof(char[1 - 2 * __builtin_types_compatible_p( \ + __typeof__(arr), __typeof__(&(arr)[0]))])) +# else +# define ARR_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +# endif + # define RAR_TSRMLS_TC -#endif #define RAR_RETNULL_ON_ARGS() \ if (zend_parse_parameters_none() == FAILURE) { \ @@ -158,33 +167,6 @@ ZEND_EXTERN_MODULE_GLOBALS(rar); # define RAR_G(v) (rar_globals.v) #endif -/* PHP 5.2 compatibility */ -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 -#define zend_parse_parameters_none() \ - zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") -#define Z_DELREF_P ZVAL_DELREF -# define STREAM_ASSUME_REALPATH 0 -# define ALLOC_PERMANENT_ZVAL(z) \ - (z) = (zval*) malloc(sizeof(zval)); -# define OPENBASEDIR_CHECKPATH(filename) \ - (PG(safe_mode) && \ - (!php_checkuid(filename, NULL, CHECKUID_CHECK_FILE_AND_DIR))) \ - || php_check_open_basedir(filename TSRMLS_CC) -# undef ZEND_BEGIN_ARG_INFO_EX -# define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \ - static const zend_arg_info name[] = { \ - { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args }, -#endif - -/* Other compatibility quirks */ -/* PHP 5.3 doesn't have ZVAL_COPY_VALUE */ -#if !defined(ZEND_COPY_VALUE) && PHP_MAJOR_VERSION == 5 -#define ZVAL_COPY_VALUE(z, v) \ - do { \ - (z)->value = (v)->value; \ - Z_TYPE_P(z) = Z_TYPE_P(v); \ - } while (0) -#endif #if !defined(HAVE_STRNLEN) || !HAVE_STRNLEN size_t _rar_strnlen(const char *s, size_t maxlen); @@ -248,7 +230,7 @@ typedef struct _rar_find_output { int found; size_t position; struct RARHeaderDataEx * header; - unsigned long packed_size; + zend_ulong packed_size; int eof; } rar_find_output; #define RAR_SEARCH_INDEX 0x01U @@ -297,7 +279,7 @@ extern zend_class_entry *rar_class_entry_ptr; void minit_rarentry(TSRMLS_D); void _rar_entry_to_zval(zval *parent, struct RARHeaderDataEx *entry, - unsigned long packed_size, + zend_ulong packed_size, size_t index, zval *entry_object TSRMLS_DC); diff --git a/php_upgrade.md b/php_upgrade.md new file mode 100644 index 00000000..fc024263 --- /dev/null +++ b/php_upgrade.md @@ -0,0 +1,189 @@ +# PHP 8.1–8.5 Upgrade Procedure for php-rar + +This document describes the step-by-step procedure to extend php-rar support +from PHP 8.0 to PHP 8.5. Each minor version is handled independently: CI is +wired up, the extension is compiled and tested inside the matching Docker image, +code changes are applied to fix any failures, and only then is the next version +tackled. + +--- + +## Overview of files touched per version + +| File | Change | +|---|---| +| `.github/docker-image-shas.yml` | Add new tag → SHA entries | +| `.github/scripts/update-docker-shas.sh` | Add new tags to the `TAGS` array | +| `Justfile` | Add image variables and `test-X_Y-*` targets | +| `*.c` / `*.h` | C source changes for API compatibility | +| `.github/workflows/tests.yml` | Windows job — update `php-version` (once per bump) | + +The Linux CI matrix is generated automatically from `docker-image-shas.yml`, so +no manual edit to `tests.yml` is needed for Linux jobs. + +--- + +## Repeatable procedure for each version + +Follow these numbered steps for **each** minor version in order. Example: +8.0 → 8.1 → 8.2 → 8.3 → 8.4 → 8.5. + +### Step 1 — Read the upgrade guides in php-src + +Clone or browse php-src on the target branch, e.g. `PHP-8.1`: + +``` +https://github.com/php/php-src/blob/PHP-8.X/UPGRADING +https://github.com/php/php-src/blob/PHP-8.X/UPGRADING.INTERNALS +``` + +Focus on sections relevant to C extensions: +- Removed or renamed macros / functions +- Changed return types (`int` → `zend_result`) +- Changed struct member types +- New mandatory includes +- Any other backwards-incompatible changes + +The per-version notes below summarise the items relevant to php-rar. + +### Step 2 — Add the Docker image SHA + +Fetch the OCI index digest from Docker Hub for the two new tags: + +```bash +# Quick one-liner — prints the index digest for a given tag +curl -fsSL "https://hub.docker.com/v2/repositories/datadog/dd-appsec-php-ci/tags/php-X.Y-debug" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['digest'])" +``` + +Or regenerate everything at once with the provided script after adding the new +tags to it (see Step 3): + +```bash +.github/scripts/update-docker-shas.sh +``` + +Append the two lines to `.github/docker-image-shas.yml`: + +```yaml + php-X.Y-debug: "sha256:" + php-X.Y-release-zts: "sha256:" +``` + +Also add both tags to the `TAGS` array in `.github/scripts/update-docker-shas.sh`: + +```bash +TAGS=( + ...existing tags... + php-X.Y-debug php-X.Y-release-zts +) +``` + +### Step 3 — Add Justfile targets + +Add image variables and `test-X_Y-*` targets following the existing pattern: + +```just +image_X_Y_debug := _base + `grep 'php-X.Y-debug:' .github/docker-image-shas.yml | cut -d'"' -f2` +image_X_Y_release_zts := _base + `grep 'php-X.Y-release-zts:' .github/docker-image-shas.yml | cut -d'"' -f2` + +test-X_Y-debug: + {{_run}} {{image_X_Y_debug}} .github/scripts/build-and-test.sh +test-X_Y-release-zts: + {{_run}} {{image_X_Y_release_zts}} .github/scripts/build-and-test.sh + +test-X_Y: test-X_Y-debug test-X_Y-release-zts +``` + +Add `test-X_Y` to the `test-linux` aggregate at the bottom. + +### Step 4 — Compile and test + +Run both variants locally before pushing: + +```bash +just test-X_Y-debug +just test-X_Y-release-zts +``` + +Or both together: + +```bash +just test-X_Y +``` + +Examine the output for compiler warnings, errors, and test failures. + +### Step 5 — Apply C source changes + +Based on the compilation output and the per-version notes below, make the +minimum necessary changes to `.c`/`.h` files. Guard every change with `#if +PHP_VERSION_ID >= XXYY00` so that older PHP versions continue to work. + +### Step 6 — Re-run tests until green + +Repeat Step 4 after each change. When both `debug` and `release-zts` pass, +commit. + +### Step 7 — Push and verify CI + +Push the branch. The `linux` CI job matrix is auto-built from +`docker-image-shas.yml` — the new versions appear automatically. Verify the +GitHub Actions run is green for all new jobs. + +### Step 8 — Update Windows CI (optional, once per bump) + +The Windows job in `.github/workflows/tests.yml` pins a specific PHP version. +Update it when the Linux jobs for the matching version are confirmed green: + +```yaml + - name: Build and test + uses: php/php-windows-builder/extension@v1 + with: + php-version: 'X.Y' # ← change here +``` + +Also update the `name:` and artifact `name:` strings in the same Windows job +block. + +--- + +## Current Docker image SHAs (as of 2026-03-01) + +These are the OCI index digests (multi-arch: amd64 + arm64) to use in +`docker-image-shas.yml`. + +```yaml + php-8.1-debug: "sha256:1a1e5b44cf043e59768c65fd7c94aaefdacde5fa96d83102d35db11ad86f24c6" + php-8.1-release-zts: "sha256:5b8a269b4228d9191420059daef820b660110be0aca6776557924172fd1ff0c8" + php-8.2-debug: "sha256:52ad14560672fc8c5130f5758bbee3fa401bc1d35b412f4a230c6258143291a5" + php-8.2-release-zts: "sha256:cb143d915b394f16a2d78018765705460f3d1b788fdd2a90ef50fad5f8f5918c" + php-8.3-debug: "sha256:bb6df08160126374d3d9247428928aa19a9c2b2429c98356650199b85ae20212" + php-8.3-release-zts: "sha256:e58e25a017f75df82691d408b8cb70453875ff36718e295ee8c6653a0f117331" + php-8.4-debug: "sha256:15045688f6986f4625b1507a7f4be6104e7bbb88caf877f1611463b929f2bca2" + php-8.4-release-zts: "sha256:8e0ac25a3306b4b9f692c593b8a509cc789c2e001ce52682928065a92c880136" + php-8.5-debug: "sha256:bd0170331b34fb469e29d00b19b20fb88b726160f76df274a1bdc3a27ac18d30" + php-8.5-release-zts: "sha256:e071b2095da55bd24686209422f43a01c65acfc6021f04156d9fb43fd3d4d426" +``` + +Refresh at any time with `.github/scripts/update-docker-shas.sh` after adding +the new tags. + +--- + +## Summary checklist + +For each version X.Y in order (8.1, 8.2, 8.3, 8.4, 8.5): + +- [ ] Read `PHP-X.Y/UPGRADING.INTERNALS` on GitHub +- [ ] Add two SHA entries to `.github/docker-image-shas.yml` +- [ ] Add both tags to `TAGS` array in `.github/scripts/update-docker-shas.sh` +- [ ] Add `image_X_Y_*` variables and `test-X_Y-*` targets to `Justfile` +- [ ] Add `test-X_Y` to `test-linux` aggregate in `Justfile` +- [ ] Run `just test-X_Y` and fix all compilation errors +- [ ] Run `just test-X_Y` again; confirm all tests pass +- [ ] Commit infrastructure + code changes together +- [ ] Push; confirm GitHub Actions CI is green for the new matrix entries +- [ ] (Optional) Update Windows `php-version` in `.github/workflows/tests.yml` to X.Y + + diff --git a/rar.c b/rar.c index 3eb15085..c0dd0bc8 100644 --- a/rar.c +++ b/rar.c @@ -28,14 +28,12 @@ /* $Id$ */ #ifdef HAVE_CONFIG_H -#include "config.h" +# include "config.h" #endif -#ifdef __cplusplus -extern "C" { +#ifndef _GNU_SOURCE +# define _GNU_SOURCE #endif - -#define _GNU_SOURCE #include #ifdef PHP_WIN32 @@ -50,7 +48,7 @@ extern "C" { #include #include -#if HAVE_RAR +#include "unrar/rardefs.hpp" #include "php_rar.h" @@ -161,12 +159,8 @@ void _rar_destroy_userdata(rar_cb_user_data *udata) /* {{{ */ } if (udata->callable != NULL) { -#if PHP_MAJOR_VERSION < 7 - zval_ptr_dtor(&udata->callable); -#else zval_ptr_dtor(udata->callable); efree(udata->callable); -#endif } udata->password = NULL; @@ -240,13 +234,16 @@ int _rar_find_file_w(struct RAROpenArchiveDataEx *open_data, /* IN */ while ((result = RARReadHeaderEx(*arc_handle, used_header_data)) == 0) { #if WCHAR_MAX > 0xffff - _rar_fix_wide(used_header_data->FileNameW, NM); + _rar_fix_wide(used_header_data->FileNameW, + ARR_SIZE(used_header_data->FileNameW)); #endif - if (wcsncmp(used_header_data->FileNameW, file_name, NM) == 0) { + if (wcsncmp(used_header_data->FileNameW, file_name, + ARR_SIZE(used_header_data->FileNameW)) == 0) { *found = TRUE; goto cleanup; - } else { + } + else { process_result = RARProcessFile(*arc_handle, RAR_SKIP, NULL, NULL); } if (process_result != 0) { @@ -383,6 +380,7 @@ int CALLBACK _rar_unrar_callback(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2 return ret; } } + // TODO: maybe support UCM_NEEDPASSWORDW and UCM_CHANGEVOLUMEW return 0; } @@ -425,7 +423,7 @@ PHP_FUNCTION(rar_wrapper_cache_stats) /* {{{ */ static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */ { wchar_t *write, - *read, + *read, *max_fin; max_fin = str + max_size; for (write = str, read = str; *read != L'\0' && read != max_fin; read++) { @@ -442,61 +440,37 @@ static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */ * because, in case we're using exceptions, we want to let an exception with * error code ERAR_EOPEN to be thrown. */ -static int _rar_unrar_volume_user_callback(char* dst_buffer, +static int _rar_unrar_volume_user_callback(char* dst_buffer, // MAXPATHSIZE zend_fcall_info *fci, zend_fcall_info_cache *cache TSRMLS_DC) /* {{{ */ { -#if PHP_MAJOR_VERSION < 7 - zval *failed_vol, - *retval_ptr = NULL, - **params; -#else zval failed_vol, retval, *params, *const retval_ptr = &retval; -#endif int ret = -1; -#if PHP_MAJOR_VERSION < 7 - MAKE_STD_ZVAL(failed_vol); - RAR_ZVAL_STRING(failed_vol, dst_buffer, 1); - params = &failed_vol; - fci->retval_ptr_ptr = &retval_ptr; - fci->params = ¶ms; -#else ZVAL_STRING(&failed_vol, dst_buffer); ZVAL_NULL(&retval); params = &failed_vol; fci->retval = &retval; fci->params = params; -#endif fci->param_count = 1; -#if PHP_MAJOR_VERSION < 7 - if (zend_call_function(fci, cache TSRMLS_CC) != SUCCESS || - fci->retval_ptr_ptr == NULL || - *fci->retval_ptr_ptr == NULL) { -#else if (zend_call_function(fci, cache TSRMLS_CC) != SUCCESS || EG(exception)) { -#endif php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failure to call volume find callback"); goto cleanup; } -#if PHP_MAJOR_VERSION < 7 - assert(*fci->retval_ptr_ptr == retval_ptr); -#else assert(fci->retval == &retval); -#endif if (Z_TYPE_P(retval_ptr) == IS_NULL) { /* let return -1 */ } else if (Z_TYPE_P(retval_ptr) == IS_STRING) { char *filename = Z_STRVAL_P(retval_ptr); - char resolved_path[MAXPATHLEN]; + char resolved_path[MAXPATHSIZE]; size_t resolved_len; if (OPENBASEDIR_CHECKPATH(filename)) { @@ -508,17 +482,15 @@ static int _rar_unrar_volume_user_callback(char* dst_buffer, goto cleanup; } - resolved_len = _rar_strnlen(resolved_path, MAXPATHLEN); - /* dst_buffer size is NM; first condition won't happen short of a bug - * in expand_filepath */ - if (resolved_len == MAXPATHLEN || resolved_len > NM - 1) { + resolved_len = _rar_strnlen(resolved_path, MAXPATHSIZE); + if (resolved_len > MAXPATHSIZE - 1) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Resolved path is too big for the unRAR library"); goto cleanup; } - strncpy(dst_buffer, resolved_path, NM); - dst_buffer[NM - 1] = '\0'; + strncpy(dst_buffer, resolved_path, MAXPATHSIZE); + dst_buffer[MAXPATHSIZE - 1] = '\0'; ret = 1; /* try this new filename */ } else { @@ -529,15 +501,8 @@ static int _rar_unrar_volume_user_callback(char* dst_buffer, } cleanup: -#if PHP_MAJOR_VERSION < 7 - zval_ptr_dtor(&failed_vol); - if (retval_ptr != NULL) { - zval_ptr_dtor(&retval_ptr); - } -#else zval_ptr_dtor(&failed_vol); zval_ptr_dtor(&retval); -#endif return ret; } /* }}} */ @@ -553,17 +518,6 @@ static int _rar_make_userdata_fcall(zval *callable, *cache = empty_fcall_info_cache; -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 2 - if (zend_fcall_info_init(callable, fci, cache TSRMLS_CC) != SUCCESS) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "The RAR file was not opened in rar_open/RarArchive::open with a " - "valid callback.", error); - return FAILURE; - } - else { - return SUCCESS; - } -#else if (zend_fcall_info_init(callable, IS_CALLABLE_STRICT, fci, cache, NULL, &error TSRMLS_CC) == SUCCESS) { if (error) { @@ -583,7 +537,6 @@ static int _rar_make_userdata_fcall(zval *callable, } return FAILURE; } -#endif } /* }}} */ @@ -634,6 +587,7 @@ ZEND_END_ARG_INFO() /* {{{ rar_functions[] * */ +/* clang-format off */ static zend_function_entry rar_functions[] = { PHP_FE(rar_open, arginfo_rar_open) PHP_FE(rar_list, arginfo_rar_void_archmeth) @@ -646,16 +600,13 @@ static zend_function_entry rar_functions[] = { PHP_FE(rar_wrapper_cache_stats, arginfo_rar_wrapper_cache_stats) {NULL, NULL, NULL} }; +/* clang-format on */ /* }}} */ /* {{{ Globals' related activities */ ZEND_DECLARE_MODULE_GLOBALS(rar); -#if PHP_MAJOR_VERSION < 7 -static int _rar_array_apply_remove_first(void *pDest TSRMLS_DC) -#else static int _rar_array_apply_remove_first(zval *pDest TSRMLS_DC) -#endif { return (ZEND_HASH_APPLY_STOP | ZEND_HASH_APPLY_REMOVE); } @@ -673,13 +624,7 @@ static void _rar_contents_cache_put(const char *key, assert(zend_hash_num_elements(cc->data) == cur_size - 1); } rar_zval_add_ref(&zv); -#if PHP_MAJOR_VERSION < 7 - assert(Z_REFCOUNT_P(zv) > 1); - SEPARATE_ZVAL(&zv); /* ensure we store a heap allocated copy */ - zend_hash_update(cc->data, key, key_len, &zv, sizeof(zv), NULL); -#else zend_hash_str_update(cc->data, key, key_len, zv); -#endif } static zval *_rar_contents_cache_get(const char *key, @@ -688,15 +633,7 @@ static zval *_rar_contents_cache_get(const char *key, { rar_contents_cache *cc = &RAR_G(contents_cache); zval *element = NULL; -#if PHP_MAJOR_VERSION < 7 - zval **element_p = NULL; - zend_hash_find(cc->data, key, key_len, (void **) &element_p); - if (element_p) { - element = *element_p; - } -#else element = zend_hash_str_find(cc->data, key, key_len); -#endif if (element != NULL) { cc->hits++; @@ -755,16 +692,14 @@ ZEND_MODULE_STARTUP_D(rar) php_register_url_stream_wrapper("rar", &php_stream_rar_wrapper TSRMLS_CC); + /* clang-format off */ REGISTER_LONG_CONSTANT("RAR_HOST_MSDOS", HOST_MSDOS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("RAR_HOST_OS2", HOST_OS2, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("RAR_HOST_WIN32", HOST_WIN32, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("RAR_HOST_UNIX", HOST_UNIX, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("RAR_HOST_MACOS", HOST_MACOS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("RAR_HOST_BEOS", HOST_BEOS, CONST_CS | CONST_PERSISTENT); - /* PHP < 5.3 doesn't have the PHP_MAXPATHLEN constant */ -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 - REGISTER_LONG_CONSTANT("RAR_MAXPATHLEN", MAXPATHLEN, CONST_CS | CONST_PERSISTENT); -#endif + /* clang-format on */ return SUCCESS; } /* }}} */ @@ -829,12 +764,6 @@ zend_module_entry rar_module_entry = { }; /* }}} */ -#endif /* HAVE_RAR */ - -#ifdef __cplusplus -} -#endif - /* * Local variables: * tab-width: 4 diff --git a/rar.map b/rar.map new file mode 100644 index 00000000..46119be5 --- /dev/null +++ b/rar.map @@ -0,0 +1 @@ +{ global: get_module; local: *; }; diff --git a/rar_error.c b/rar_error.c index 13a742bc..7eef868f 100644 --- a/rar_error.c +++ b/rar_error.c @@ -25,10 +25,6 @@ +----------------------------------------------------------------------+ */ -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include "php_rar.h" @@ -70,11 +66,7 @@ void _rar_handle_ext_error(const char *format TSRMLS_DC, ...) /* {{{ */ va_list arg; char *message; -#if defined(ZTS) && PHP_MAJOR_VERSION < 7 - va_start(arg, TSRMLS_C); -#else va_start(arg, format); -#endif vspprintf(&message, 0, format, arg); va_end(arg); @@ -91,13 +83,8 @@ int _rar_using_exceptions(TSRMLS_D) zval *pval; pval = zend_read_static_property(rarexception_ce_ptr, "usingExceptions", sizeof("usingExceptions") -1, (zend_bool) 1 TSRMLS_CC); -#if PHP_MAJOR_VERSION < 7 - assert(Z_TYPE_P(pval) == IS_BOOL); - return Z_BVAL_P(pval); -#else assert(Z_TYPE_P(pval) == IS_TRUE || Z_TYPE_P(pval) == IS_FALSE); return Z_TYPE_P(pval) == IS_TRUE; -#endif } /* returns a string or NULL if not an error */ @@ -186,39 +173,22 @@ PHP_METHOD(rarexception, setUsingExceptions) Return whether exceptions are being used */ PHP_METHOD(rarexception, isUsingExceptions) { -#if PHP_MAJOR_VERSION < 7 - zval **pval; -#else zval *pval; -#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE ) { return; } /* or zend_read_static_property, which calls zend_std_get... after chg scope */ -#if PHP_VERSION_ID < 50399 - pval = zend_std_get_static_property(rarexception_ce_ptr, "usingExceptions", - sizeof("usingExceptions") -1, (zend_bool) 0 TSRMLS_CC); -#elif PHP_MAJOR_VERSION < 7 - pval = zend_std_get_static_property(rarexception_ce_ptr, "usingExceptions", - sizeof("usingExceptions") -1, (zend_bool) 0, NULL TSRMLS_CC); -#else zend_string *prop_name = zend_string_init("usingExceptions", sizeof("usingExceptions") - 1, 0); pval = zend_std_get_static_property(rarexception_ce_ptr, prop_name, (zend_bool) 0); zend_string_release(prop_name); -#endif /* property always exists */ assert(pval != NULL); -#if PHP_MAJOR_VERSION < 7 - assert(Z_TYPE_PP(pval) == IS_BOOL); - RETURN_ZVAL(*pval, 0, 0); -#else assert(Z_TYPE_P(pval) == IS_TRUE || Z_TYPE_P(pval) == IS_FALSE); RETURN_ZVAL(pval, 0, 0); -#endif } /* }}} */ @@ -231,31 +201,24 @@ ZEND_BEGIN_ARG_INFO(arginfo_rarexception_void, 0) ZEND_END_ARG_INFO() /* }}} */ +/* clang-format off */ static zend_function_entry php_rarexception_class_functions[] = { PHP_ME(rarexception, setUsingExceptions, arginfo_rarexception_sue, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(rarexception, isUsingExceptions, arginfo_rarexception_void, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) {NULL, NULL, NULL} }; +/* clang-format on */ void minit_rarerror(TSRMLS_D) /* {{{ */ { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "RarException", php_rarexception_class_functions); -#if PHP_MAJOR_VERSION < 7 - rarexception_ce_ptr = zend_register_internal_class_ex(&ce, - zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); -#else - rarexception_ce_ptr = zend_register_internal_class_ex(&ce, - zend_exception_get_default(TSRMLS_C)); -#endif + /* zend_exception_get_default() was removed in PHP 8.5; use the global directly */ + rarexception_ce_ptr = zend_register_internal_class_ex(&ce, zend_ce_exception); rarexception_ce_ptr->ce_flags |= ZEND_ACC_FINAL; zend_declare_property_bool(rarexception_ce_ptr, "usingExceptions", sizeof("usingExceptions") -1, 0L /* FALSE */, ZEND_ACC_STATIC TSRMLS_CC); } /* }}} */ - -#ifdef __cplusplus -} -#endif diff --git a/rar_navigation.c b/rar_navigation.c index bf5136c4..557629cf 100644 --- a/rar_navigation.c +++ b/rar_navigation.c @@ -25,21 +25,16 @@ */ #ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#ifdef __cplusplus -extern "C" { +# include "config.h" #endif #include #include #include "php_rar.h" -#if HAVE_RAR - /* {{{ Structure definitions */ +/* clang-format off */ typedef struct _rar_find_state { rar_find_output out; rar_file_t *rar; @@ -49,7 +44,7 @@ typedef struct _rar_find_state { struct _rar_unique_entry { size_t id; /* position in the entries_array */ struct RARHeaderDataEx entry; /* last entry */ - unsigned long packed_size; + zend_ulong packed_size; int depth; /* number of directory separators */ size_t name_wlen; /* excluding L'\0' terminator */ }; @@ -65,6 +60,7 @@ struct _rar_entries { struct _rar_unique_entry *last_accessed; int list_result; /* tell whether the archive's broken */ }; +/* clang-format on */ /* }}} */ @@ -73,9 +69,7 @@ static void _rar_nav_get_depth_and_length(wchar_t *filenamew, const size_t file_ int *depth_out, size_t *wlen_out TSRMLS_DC); static int _rar_nav_get_depth(const wchar_t *filenamew, const size_t file_size); static int _rar_nav_compare_entries(const void *op1, const void *op2 TSRMLS_DC); -#if PHP_MAJOR_VERSION >= 7 static void _rar_nav_swap_entries(void *op1, void *op2); -#endif static int _rar_nav_compare_entries_std(const void *op1, const void *op2); static inline int _rar_nav_compare_values(const wchar_t *str1, const int depth1, const wchar_t *str2, const int depth2, @@ -116,15 +110,9 @@ void _rar_entry_search_start(rar_file_t *rar, sizeof rar->entries->entries_array_s[0]); memcpy(rar->entries->entries_array_s, rar->entries->entries_array, rar->entries->num_entries * sizeof rar->entries->entries_array[0]); -#if PHP_MAJOR_VERSION < 7 - zend_qsort(rar->entries->entries_array_s, rar->entries->num_entries, - sizeof *rar->entries->entries_array_s, _rar_nav_compare_entries - TSRMLS_CC); -#else zend_qsort(rar->entries->entries_array_s, rar->entries->num_entries, sizeof *rar->entries->entries_array_s, _rar_nav_compare_entries, _rar_nav_swap_entries); -#endif } } /* }}} */ @@ -336,7 +324,7 @@ int _rar_list_files(rar_file_t *rar TSRMLS_DC) /* {{{ */ int result = 0; size_t capacity = 0; int first_file_check = TRUE; - unsigned long packed_size = 0UL; + zend_ulong packed_size = 0; struct _rar_entries *ents; if (rar->entries != NULL) { @@ -378,22 +366,16 @@ int _rar_list_files(rar_file_t *rar TSRMLS_DC) /* {{{ */ /* reset packed size if not split before */ if ((entry.Flags & RHDF_SPLITBEFORE) == 0) - packed_size = 0UL; - - /* we would exceed size of ulong. cap at ulong_max - * equivalent to packed_size + entry.PackSize > ULONG_MAX, - * but without overflowing */ - if (ULONG_MAX - packed_size < entry.PackSize) - packed_size = ULONG_MAX; - else { - packed_size += entry.PackSize; - if (entry.PackSizeHigh != 0) { -#if ULONG_MAX > 0xffffffffUL - packed_size += ((unsigned long) entry.PackSizeHigh) << 32; -#else - packed_size = ULONG_MAX; /* cap */ -#endif - } + packed_size = 0; + + /* accumulate packed size; cap at ZEND_LONG_MAX (the PHP int ceiling) */ + { + zend_ulong entry_packed = ((zend_ulong)entry.PackSizeHigh << 32) | entry.PackSize; + if (entry_packed > (zend_ulong)ZEND_LONG_MAX || + packed_size > (zend_ulong)ZEND_LONG_MAX - entry_packed) + packed_size = (zend_ulong)ZEND_LONG_MAX; + else + packed_size += entry_packed; } if (entry.Flags & RHDF_SPLITAFTER) /* do not commit */ @@ -502,7 +484,6 @@ static int _rar_nav_compare_entries(const void *op1, const void *op2 TSRMLS_DC) } /* }}} */ -#if PHP_MAJOR_VERSION >= 7 static void _rar_nav_swap_entries(void *op1, void *op2) /* {{{ */ { /* just swaps two pointer values */ @@ -515,7 +496,6 @@ static void _rar_nav_swap_entries(void *op1, void *op2) /* {{{ */ } /* }}} */ -#endif static int _rar_nav_compare_entries_std(const void *op1, const void *op2) /* {{{ */ { @@ -620,12 +600,6 @@ static size_t _rar_nav_position_on_dir_start(const wchar_t *dir_name, /* end functions with internal linkage */ -#endif /* HAVE_RAR */ - -#ifdef __cplusplus -} -#endif - /* * Local variables: * tab-width: 4 @@ -634,5 +608,3 @@ static size_t _rar_nav_position_on_dir_start(const wchar_t *dir_name, * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ - - diff --git a/rar_stream.c b/rar_stream.c index c6546a66..02dd327d 100644 --- a/rar_stream.c +++ b/rar_stream.c @@ -27,17 +27,11 @@ /* $Id$ */ #ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#ifdef __cplusplus -extern "C" { +# include "config.h" #endif #include -#if HAVE_RAR - #include #include "php_rar.h" @@ -47,6 +41,7 @@ extern "C" { #include #include +/* clang-format off */ typedef struct php_rar_stream_data_t { struct RAROpenArchiveDataEx open_data; struct RARHeaderDataEx header_data; @@ -73,12 +68,15 @@ typedef struct php_rar_dir_stream_data_t { int no_encode; /* do not urlencode entry names */ php_stream *stream; } php_rar_dir_stream_data, *php_rar_dir_stream_data_P; +/* clang-format on */ +/* clang-format off */ #define STREAM_DATA_FROM_STREAM \ php_rar_stream_data_P self = (php_rar_stream_data_P) stream->abstract; #define STREAM_DIR_DATA_FROM_STREAM \ php_rar_dir_stream_data_P self = (php_rar_dir_stream_data_P) stream->abstract; +/* clang-format on */ /* len can be -1 (calculate) */ static char *_rar_wide_to_utf_with_alloc(const wchar_t *wide, int len) @@ -166,7 +164,7 @@ static ssize_t php_rar_ops_read(php_stream *stream, char *buf, size_t count) if (self->cursor > self->file_size) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "The file size is supposed to be %lu bytes, but " - "we read more: %lu bytes (corruption/wrong pwd)", + "we read more: %" PRIu64 " bytes (corruption/wrong pwd)", self->file_size, self->cursor); } } @@ -179,11 +177,7 @@ static ssize_t php_rar_ops_read(php_stream *stream, char *buf, size_t count) stream->eof = 1; } -#if PHP_VERSION_ID < 50400 - return n; -#else return (ssize_t) n; -#endif } /* }}} */ @@ -217,6 +211,12 @@ static int php_rar_ops_close(php_stream *stream, int close_handle TSRMLS_DC) efree(self->open_data.ArcName); self->open_data.ArcName = NULL; } +#ifdef PHP_WIN32 + if (self->open_data.ArcNameW != NULL) { + efree(self->open_data.ArcNameW); + self->open_data.ArcNameW = NULL; + } +#endif _rar_destroy_userdata(&self->cb_userdata); if (self->buffer != NULL) { efree(self->buffer); @@ -424,19 +424,10 @@ static ssize_t php_rar_dir_ops_read(php_stream *stream, char *buf, size_t count entry.d_name, sizeof entry.d_name); if (!self->no_encode) { /* urlencode entry */ -#if PHP_MAJOR_VERSION < 7 - int new_len; - char *encoded_name; - encoded_name = php_url_encode(entry.d_name, strlen(entry.d_name), - &new_len); - strlcpy(entry.d_name, encoded_name, sizeof entry.d_name); - efree(encoded_name); -#else zend_string *encoded_name = php_url_encode(entry.d_name, strlen(entry.d_name)); strlcpy(entry.d_name, encoded_name->val, sizeof entry.d_name); zend_string_release(encoded_name); -#endif } @@ -452,11 +443,7 @@ static int php_rar_dir_ops_close(php_stream *stream, int close_handle TSRMLS_DC) { STREAM_DIR_DATA_FROM_STREAM -#if PHP_MAJOR_VERSION < 7 - zval_dtor(&self->rar_obj); -#else zval_ptr_dtor(&self->rar_obj); -#endif efree(self->directory); efree(self->state); efree(self); @@ -530,6 +517,13 @@ php_stream *php_stream_rar_open(char *arc_name, self = ecalloc(1, sizeof *self); self->open_data.ArcName = estrdup(arc_name); self->open_data.OpenMode = RAR_OM_EXTRACT; +#ifdef PHP_WIN32 + { + size_t arcnamew_len = strlen(arc_name); + self->open_data.ArcNameW = safe_emalloc(arcnamew_len, sizeof(wchar_t), sizeof(wchar_t)); + _rar_utf_to_wide(arc_name, self->open_data.ArcNameW, arcnamew_len + 1); + } +#endif /* deep copy the callback userdata */ if (cb_udata_ptr->password != NULL) self->cb_userdata.password = estrdup(cb_udata_ptr->password); @@ -572,6 +566,10 @@ php_stream *php_stream_rar_open(char *arc_name, if (self != NULL) { if (self->open_data.ArcName != NULL) efree(self->open_data.ArcName); +#ifdef PHP_WIN32 + if (self->open_data.ArcNameW != NULL) + efree(self->open_data.ArcNameW); +#endif _rar_destroy_userdata(&self->cb_userdata); if (self->buffer != NULL) efree(self->buffer); @@ -587,67 +585,6 @@ php_stream *php_stream_rar_open(char *arc_name, /* {{{ Wrapper stuff */ -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 -/* PHP 5.2 has no zend_resolve_path. Adapted from 5.3's php_resolve_path */ -static char *zend_resolve_path(const char *filename, - int filename_length TSRMLS_DC) /* {{{ */ -{ - const char *path = PG(include_path); - char resolved_path[MAXPATHLEN]; - char trypath[MAXPATHLEN]; - const char *ptr, *end; - char *actual_path; - - if (filename == NULL || filename[0] == '\0') { - return NULL; - } - - /* do not use the include path in these circumstances */ - if ((*filename == '.' && (IS_SLASH(filename[1]) || - ((filename[1] == '.') && IS_SLASH(filename[2])))) || - IS_ABSOLUTE_PATH(filename, filename_length) || - path == NULL || path[0] == '\0') { - if (tsrm_realpath(filename, resolved_path TSRMLS_CC)) { - return estrdup(resolved_path); - } else { - return NULL; - } - } - - ptr = path; - while (ptr && *ptr) { - end = strchr(ptr, DEFAULT_DIR_SEPARATOR); - if (end) { - if ((end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) { - ptr = end + 1; - continue; - } - memcpy(trypath, ptr, end-ptr); - trypath[end-ptr] = '/'; - memcpy(trypath+(end-ptr)+1, filename, filename_length+1); - ptr = end+1; - } else { - int len = strlen(ptr); - - if (len + 1 + filename_length + 1 >= MAXPATHLEN) { - break; - } - memcpy(trypath, ptr, len); - trypath[len] = '/'; - memcpy(trypath+len+1, filename, filename_length+1); - ptr = NULL; - } - actual_path = trypath; - if (tsrm_realpath(actual_path, resolved_path TSRMLS_CC)) { - return estrdup(resolved_path); - } - } /* end provided path */ - - return NULL; -} -/* }}} */ -#endif - /* {{{ php_rar_process_context */ /* memory is to be managed externally */ static void php_rar_process_context(php_stream_context *context, @@ -658,9 +595,6 @@ static void php_rar_process_context(php_stream_context *context, zval **volume_cb TSRMLS_DC) { zval *ctx_opt; -#if PHP_MAJOR_VERSION < 7 - zval **ctx_opt_p = NULL; -#endif assert(context != NULL); assert(open_password != NULL); @@ -670,14 +604,8 @@ static void php_rar_process_context(php_stream_context *context, /* TODO: don't know if I can log errors and not fail. check that */ -#if PHP_MAJOR_VERSION < 7 - if (php_stream_context_get_option( - context, "rar", "open_password", &ctx_opt_p) == SUCCESS) { - ctx_opt = *ctx_opt_p; -#else if ((ctx_opt = php_stream_context_get_option( context, "rar", "open_password"))) { -#endif if (Z_TYPE_P(ctx_opt) != IS_STRING) php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "RAR open password was provided, but not a string."); @@ -685,14 +613,8 @@ static void php_rar_process_context(php_stream_context *context, *open_password = Z_STRVAL_P(ctx_opt); } -#if PHP_MAJOR_VERSION < 7 - if (file_password != NULL && php_stream_context_get_option(context, "rar", - "file_password", &ctx_opt_p) == SUCCESS) { - ctx_opt = *ctx_opt_p; -#else if (file_password != NULL && (ctx_opt = php_stream_context_get_option( context, "rar", "file_password"))) { -#endif if (Z_TYPE_P(ctx_opt) != IS_STRING) php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "RAR file password was provided, but not a string."); @@ -700,19 +622,9 @@ static void php_rar_process_context(php_stream_context *context, *file_password = Z_STRVAL_P(ctx_opt); } -#if PHP_MAJOR_VERSION < 7 - if (php_stream_context_get_option(context, "rar", "volume_callback", - &ctx_opt_p) == SUCCESS) { - ctx_opt = *ctx_opt_p; -#else if ((ctx_opt = php_stream_context_get_option( context, "rar", "volume_callback"))) { -#endif -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 2 - if (zend_is_callable(ctx_opt, IS_CALLABLE_STRICT, NULL)) { -#else if (zend_is_callable(ctx_opt, IS_CALLABLE_STRICT, NULL TSRMLS_CC)) { -#endif *volume_cb = ctx_opt; } else @@ -781,23 +693,19 @@ static int _rar_get_archive_and_fragment(php_stream_wrapper *wrapper, if (!(options & STREAM_ASSUME_REALPATH)) { if (options & USE_PATH) { -#if PHP_MAJOR_VERSION < 7 - *archive = zend_resolve_path(tmp_archive, tmp_arch_len TSRMLS_CC); -#else -# if PHP_VERSION_ID < 80100 +#if PHP_VERSION_ID < 80100 zend_string *arc_str = zend_resolve_path(tmp_archive, tmp_arch_len); -# else +#else zend_string *tmp_archive_str = zend_string_init_fast(tmp_archive, tmp_arch_len); zend_string *arc_str = zend_resolve_path(tmp_archive_str); zend_string_free(tmp_archive_str); -# endif +#endif if (arc_str != NULL) { *archive = estrndup(arc_str->val, arc_str->len); } else { *archive = NULL; } zend_string_release(arc_str); -#endif } if (*archive == NULL) { if ((*archive = expand_filepath(tmp_archive, NULL TSRMLS_CC)) @@ -865,17 +773,10 @@ static int _rar_get_archive_and_fragment(php_stream_wrapper *wrapper, /* {{{ php_stream_rar_opener */ static php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper, -#if PHP_MAJOR_VERSION < 7 - char *filename, - char *mode, - int options, - char **opened_path, -#else const char *filename, const char *mode, int options, zend_string **opened_path, -#endif php_stream_context *context STREAMS_DC TSRMLS_DC) { @@ -919,6 +820,13 @@ static php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper, self = ecalloc(1, sizeof *self); self->open_data.ArcName = estrdup(tmp_open_path); self->open_data.OpenMode = RAR_OM_EXTRACT; +#ifdef PHP_WIN32 + { + size_t arcnamew_len = strlen(tmp_open_path); + self->open_data.ArcNameW = safe_emalloc(arcnamew_len, sizeof(wchar_t), sizeof(wchar_t)); + _rar_utf_to_wide(tmp_open_path, self->open_data.ArcNameW, arcnamew_len + 1); + } +#endif if (open_passwd != NULL) self->cb_userdata.password = estrdup(open_passwd); if (volume_cb != NULL) { @@ -987,12 +895,8 @@ static php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper, if (tmp_open_path != NULL) { if (opened_path != NULL) { -#if PHP_MAJOR_VERSION < 7 - *opened_path = tmp_open_path; -#else *opened_path = zend_string_init(tmp_open_path, strlen(tmp_open_path), 0); -#endif } else { efree(tmp_open_path); } @@ -1004,6 +908,10 @@ static php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper, if (self != NULL) { if (self->open_data.ArcName != NULL) efree(self->open_data.ArcName); +#ifdef PHP_WIN32 + if (self->open_data.ArcNameW != NULL) + efree(self->open_data.ArcNameW); +#endif _rar_destroy_userdata(&self->cb_userdata); if (self->buffer != NULL) efree(self->buffer); @@ -1048,11 +956,7 @@ static int _rar_get_cachable_rararch(php_stream_wrapper *wrapper, zval *cache_zv; assert(rar_obj != NULL); -#if PHP_MAJOR_VERSION < 7 - INIT_ZVAL(*rar_obj); -#else ZVAL_UNDEF(rar_obj); -#endif _rar_arch_cache_get_key(arch_path, open_passwd, volume_cb, &cache_key, &cache_key_len); @@ -1115,56 +1019,26 @@ static int _rar_get_cachable_rararch(php_stream_wrapper *wrapper, efree(cache_key); if (ret != SUCCESS && Z_TYPE_P(rar_obj) == IS_OBJECT) { -#if PHP_MAJOR_VERSION < 7 - zval_dtor(rar_obj); - Z_TYPE_P(rar_obj) = IS_NULL; -#else zval_ptr_dtor(rar_obj); ZVAL_UNDEF(rar_obj); -#endif } return ret; } /* }}} */ -/* {{{ _rar_stream_tidy_wrapper_error_log - * These two different versions are because of PHP commit 7166298 */ -#if PHP_VERSION_ID <= 50310 || PHP_VERSION_ID == 50400 -/* copied from main/streams/streams.c because it's an internal function */ -static void _rar_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC) -{ - if (wrapper) { - /* tidy up the error stack */ - int i; - - for (i = 0; i < wrapper->err_count; i++) { - efree(wrapper->err_stack[i]); - } - if (wrapper->err_stack) { - efree(wrapper->err_stack); - } - wrapper->err_stack = NULL; - wrapper->err_count = 0; - } -} -#else +/* {{{ _rar_stream_tidy_wrapper_error_log */ static void _rar_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC) { if (wrapper && FG(wrapper_errors)) { zend_hash_str_del(FG(wrapper_errors), (const char*)&wrapper, sizeof wrapper); } } -#endif /* }}} */ /* {{{ php_stream_rar_stater */ static int php_stream_rar_stater(php_stream_wrapper *wrapper, -#if PHP_MAJOR_VERSION < 7 - char *url, -#else const char *url, -#endif int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) @@ -1183,11 +1057,7 @@ static int php_stream_rar_stater(php_stream_wrapper *wrapper, int ret = FAILURE; /* {{{ preliminaries */ -#if PHP_MAJOR_VERSION < 7 - Z_TYPE(rararch) = IS_NULL; -#else ZVAL_UNDEF(&rararch); -#endif if (_rar_get_archive_and_fragment(wrapper, url, options, 1, &open_path, &fragment, NULL TSRMLS_CC) == FAILURE) { @@ -1238,11 +1108,7 @@ static int php_stream_rar_stater(php_stream_wrapper *wrapper, } if (Z_TYPE(rararch) == IS_OBJECT) { -#if PHP_MAJOR_VERSION < 7 - zval_dtor(&rararch); -#else zval_ptr_dtor(&rararch); -#endif } if (state != NULL) { _rar_entry_search_end(state); @@ -1265,17 +1131,10 @@ static int php_stream_rar_stater(php_stream_wrapper *wrapper, /* {{{ php_stream_rar_dir_opener */ static php_stream *php_stream_rar_dir_opener(php_stream_wrapper *wrapper, -#if PHP_MAJOR_VERSION < 7 - char *filename, - char *mode, - int options, - char **opened_path, -#else const char *filename, const char *mode, int options, zend_string **opened_path, -#endif php_stream_context *context STREAMS_DC TSRMLS_DC) { @@ -1374,12 +1233,8 @@ static php_stream *php_stream_rar_dir_opener(php_stream_wrapper *wrapper, if (tmp_open_path != NULL) { if (opened_path != NULL) { -#if PHP_MAJOR_VERSION < 7 - *opened_path = tmp_open_path; -#else *opened_path = zend_string_init(tmp_open_path, strlen(tmp_open_path), 0); -#endif } else { efree(tmp_open_path); } @@ -1390,11 +1245,7 @@ static php_stream *php_stream_rar_dir_opener(php_stream_wrapper *wrapper, if (stream == NULL) { /* failed */ if (self != NULL) { if (Z_TYPE(self->rar_obj) == IS_OBJECT) { -#if PHP_MAJOR_VERSION < 7 - zval_dtor(&self->rar_obj); -#else zval_ptr_dtor(&self->rar_obj); -#endif } if (self->directory != NULL) { efree(self->directory); @@ -1431,12 +1282,6 @@ php_stream_wrapper php_stream_rar_wrapper = { /* end wrapper stuff }}} */ -#endif /* HAVE_RAR */ - -#ifdef __cplusplus -} -#endif - /* * Local variables: * tab-width: 4 diff --git a/rar_time.c b/rar_time.c index c47a32a1..92ad58c0 100644 --- a/rar_time.c +++ b/rar_time.c @@ -1,7 +1,3 @@ -#ifdef __cplusplus -extern "C" { -#endif - #include #include "php_rar.h" @@ -57,7 +53,3 @@ int rar_dos_time_convert(unsigned dos_time, time_t *to) /* {{{ */ return SUCCESS; } /* }}} */ - -#ifdef __cplusplus -} -#endif diff --git a/rararch.c b/rararch.c index 7cbfa26e..3d222784 100644 --- a/rararch.c +++ b/rararch.c @@ -29,9 +29,6 @@ #include "zend_types.h" #include -#ifdef __cplusplus -extern "C" { -#endif #ifndef _GNU_SOURCE # define _GNU_SOURCE @@ -45,26 +42,19 @@ extern "C" { #include "php_compat.h" /* {{{ Type definitions reserved for this translation unit */ +/* clang-format off */ typedef struct _ze_rararch_object { -#if PHP_MAJOR_VERSION < 7 - zend_object parent; - rar_file_t *rar_file; -#else rar_file_t *rar_file; zend_object parent; -#endif } ze_rararch_object; typedef struct _rararch_iterator { zend_object_iterator parent; rar_find_output *state; -#if PHP_MAJOR_VERSION < 7 - zval *value; -#else zval value; -#endif int empty_iterator; /* iterator should give nothing */ } rararch_iterator; +/* clang-format on */ /* }}} */ /* {{{ Globals with internal linkage */ @@ -88,30 +78,19 @@ static zend_object_handlers rararch_object_handlers; /* {{{ Function prototypes for functions with internal linkage */ static inline rar_obj_ref rar_obj_ref_fetch(zval *zv); static inline void rar_obj_ref_make_zv(rar_obj_ref zo, zval *zv TSRMLS_DC); -#if PHP_MAJOR_VERSION >= 7 static inline ze_rararch_object *rararch_object_fetch(zend_object *zobj); static ze_rararch_object *rararch_object_from_zv(const zval *zv); static ze_rararch_object *rararch_object_from_ref(const rar_obj_ref ref); static zend_object *rararch_ce_create_object(zend_class_entry *ce); static void rararch_ce_free_object_storage(zend_object *zobj); -#else -#define rararch_object_from_zv zend_object_store_get_object -#define rararch_object_from_ref(ref) zend_object_store_get_object_by_handle((ref) TSRMLS_CC) -static zend_object_value rararch_ce_create_object(zend_class_entry *class_type TSRMLS_DC); -static void rararch_ce_free_object_storage(ze_rararch_object *object TSRMLS_DC); -#endif /* }}} */ /* {{{ RarArchive handlers */ static int rararch_handlers_preamble(handler_this_t *object, rar_file_t **rar TSRMLS_DC); -static int rararch_dimensions_preamble(rar_file_t *rar, zval *offset, long *index, int quiet TSRMLS_DC); -static int rararch_count_elements(handler_this_t *object, long *count TSRMLS_DC); -#if PHP_MAJOR_VERSION < 7 -static zval *rararch_read_dimension(zval *object, zval *offset, int type TSRMLS_DC); -#else +static int rararch_dimensions_preamble(rar_file_t *rar, zval *offset, zend_long *index, int quiet TSRMLS_DC); +static int rararch_count_elements(handler_this_t *object, zend_long *count TSRMLS_DC); static zval *rararch_read_dimension(handler_this_t *object, zval *offset, int type, zval *rv); -#endif static void rararch_write_dimension(handler_this_t *object, zval *offset, zval *value TSRMLS_DC); static int rararch_has_dimension(handler_this_t *object, zval *offset, int check_empty TSRMLS_DC); /* }}} */ @@ -156,6 +135,15 @@ int _rar_create_rararch_obj(const char* resolved_path, rar->list_open_data->CmtBufSize = RAR_MAX_COMMENT_SIZE; rar->extract_open_data = ecalloc(1, sizeof *rar->extract_open_data); rar->extract_open_data->ArcName = estrdup(resolved_path); +#ifdef PHP_WIN32 + { + size_t arcnamew_len = strlen(resolved_path); + rar->list_open_data->ArcNameW = safe_emalloc(arcnamew_len, sizeof(wchar_t), sizeof(wchar)); + _rar_utf_to_wide(resolved_path, rar->list_open_data->ArcNameW, arcnamew_len + 1); + rar->extract_open_data->ArcNameW = safe_emalloc(arcnamew_len, sizeof(wchar_t), sizeof(wchar)); + _rar_utf_to_wide(resolved_path, rar->extract_open_data->ArcNameW, arcnamew_len + 1); + } +#endif rar->extract_open_data->OpenMode = RAR_OM_EXTRACT; rar->extract_open_data->CmtBuf = NULL; /* not interested in it again */ rar->cb_userdata.password = NULL; @@ -188,8 +176,14 @@ int _rar_create_rararch_obj(const char* resolved_path, efree(rar->list_open_data->ArcName); efree(rar->list_open_data->CmtBuf); +#ifdef PHP_WIN32 + efree(rar->list_open_data->ArcNameW); +#endif efree(rar->list_open_data); efree(rar->extract_open_data->ArcName); +#ifdef PHP_WIN32 + efree(rar->extract_open_data->ArcNameW); +#endif efree(rar->extract_open_data); efree(rar); return FAILURE; @@ -258,25 +252,17 @@ static void _rar_raw_entries_to_array(rar_file_t *rar, zval *target TSRMLS_DC) / state->position, entry_obj TSRMLS_CC); add_next_index_zval(target, entry_obj); -#if PHP_MAJOR_VERSION >= 7 /* PHP 7 copies the zval (but without increasing the refcount of the - * obj), while 5.x simply copies the pointer. Only for PHP 5.x do we - * keep the allocation) */ + * obj). Free the allocation. */ efree(entry_obj); -#endif } } while (state->eof == 0); _rar_entry_search_end(state); -#if PHP_MAJOR_VERSION < 7 - zval_dtor(&rararch_obj); -#else zval_ptr_dtor(&rararch_obj); -#endif } /* }}} */ -#if PHP_MAJOR_VERSION >=7 static inline rar_obj_ref rar_obj_ref_fetch(zval *zv) { return Z_OBJ(*zv); @@ -286,24 +272,7 @@ static inline void rar_obj_ref_make_zv(rar_obj_ref zo, zval *zv TSRMLS_DC) ZVAL_OBJ(zv, zo); zval_addref_p(zv); } -#else -inline rar_obj_ref rar_obj_ref_fetch(zval *zv) -{ - return Z_OBJ_HANDLE_P(zv); -} -inline void rar_obj_ref_make_zv(rar_obj_ref zoh, zval *zv TSRMLS_DC) -{ - INIT_ZVAL(*zv); - Z_TYPE_P(zv) = IS_OBJECT; - Z_OBJ_HANDLE_P(zv) = zoh; - Z_OBJ_HT_P(zv) = &rararch_object_handlers; - /* object has a new reference; if not incremented, the object would be - * be destroyed when this new zval we created was destroyed */ - zend_objects_store_add_ref_by_handle(zoh TSRMLS_CC); -} -#endif -#if PHP_MAJOR_VERSION >=7 static inline ze_rararch_object *rararch_object_fetch(zend_object *zobj) { return (ze_rararch_object *) @@ -317,35 +286,8 @@ static ze_rararch_object *rararch_object_from_ref(const rar_obj_ref ref) { return rararch_object_fetch(ref); } -#endif /* {{{ */ -#if PHP_MAJOR_VERSION < 7 -static zend_object_value rararch_ce_create_object(zend_class_entry *class_type TSRMLS_DC) -{ - zend_object_value zov; - ze_rararch_object *zobj; - - zobj = emalloc(sizeof *zobj); - /* rararch_ce_free_object_storage will attempt to access it otherwise */ - zobj->rar_file = NULL; - zend_object_std_init((zend_object*) zobj, class_type TSRMLS_CC); - -#if PHP_VERSION_ID < 50399 - zend_hash_copy(((zend_object*)zobj)->properties, - &(class_type->default_properties), - (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); -#else - object_properties_init((zend_object*)zobj, class_type); -#endif - zov.handle = zend_objects_store_put(zobj, - (zend_objects_store_dtor_t) zend_objects_destroy_object, - (zend_objects_free_object_storage_t) rararch_ce_free_object_storage, - NULL TSRMLS_CC); - zov.handlers = &rararch_object_handlers; - return zov; -} -#else static zend_object *rararch_ce_create_object(zend_class_entry *ce) { ze_rararch_object *zobj = @@ -357,18 +299,12 @@ static zend_object *rararch_ce_create_object(zend_class_entry *ce) return &zobj->parent; } -#endif /* }}} */ /* {{{ */ -#if PHP_MAJOR_VERSION < 7 -static void rararch_ce_free_object_storage(ze_rararch_object *object TSRMLS_DC) -{ -#else static void rararch_ce_free_object_storage(zend_object *zobj) { ze_rararch_object *object = rararch_object_fetch(zobj); -#endif rar_file_t *rar = object->rar_file; /* may be NULL if the user did new RarArchive() */ @@ -383,8 +319,14 @@ static void rararch_ce_free_object_storage(zend_object *zobj) efree(rar->list_open_data->ArcName); efree(rar->list_open_data->CmtBuf); +#ifdef PHP_WIN32 + efree(rar->list_open_data->ArcNameW); +#endif efree(rar->list_open_data); efree(rar->extract_open_data->ArcName); +#ifdef PHP_WIN32 + efree(rar->extract_open_data->ArcNameW); +#endif efree(rar->extract_open_data); efree(rar); } @@ -392,9 +334,6 @@ static void rararch_ce_free_object_storage(zend_object *zobj) /* could call zend_objects_free_object_storage here (not before!), but * instead I'll mimic its behaviour */ zend_object_std_dtor(&object->parent TSRMLS_CC); -#if PHP_MAJOR_VERSION < 7 - efree(object); -#endif } /* }}} */ @@ -420,7 +359,7 @@ static int rararch_handlers_preamble(handler_this_t *object, /* {{{ rararch_dimensions_preamble - semi-strict parsing of int argument */ static int rararch_dimensions_preamble(rar_file_t *rar, zval *offset, - long *index, + zend_long *index, int quiet TSRMLS_DC) { if (offset == NULL) { @@ -444,36 +383,32 @@ static int rararch_dimensions_preamble(rar_file_t *rar, return FAILURE; } else if (type == IS_DOUBLE) { - if (d > (double) LONG_MAX || d < (double) LONG_MIN) { + if (d > (double) ZEND_LONG_MAX || d < (double) ZEND_LONG_MIN) { RAR_DOCREF_IF_UNQUIET(NULL TSRMLS_CC, E_WARNING, "Dimension index is out of integer bounds"); return FAILURE; } - *index = (long) d; + *index = (zend_long) d; } } else if (Z_TYPE_P(offset) == IS_DOUBLE) { - if (Z_DVAL_P(offset) > (double) LONG_MAX || - Z_DVAL_P(offset) < (double) LONG_MIN) { + if (Z_DVAL_P(offset) > (double) ZEND_LONG_MAX || + Z_DVAL_P(offset) < (double) ZEND_LONG_MIN) { RAR_DOCREF_IF_UNQUIET(NULL TSRMLS_CC, E_WARNING, "Dimension index is out of integer bounds"); return FAILURE; } - *index = (long) Z_DVAL_P(offset); + *index = (zend_long) Z_DVAL_P(offset); } else if (Z_TYPE_P(offset) == IS_OBJECT) { #if PHP_MAJOR_VERSION < 8 if (Z_OBJ_HT_P(offset)->get) { zval *newoffset = NULL; int ret; -# if PHP_MAJOR_VERSION < 7 - newoffset = Z_OBJ_HT_P(offset)->get(offset TSRMLS_CC); -# else zval zv_holder; ZVAL_NULL(&zv_holder); newoffset = Z_OBJ_HT_P(offset)->get(offset, &zv_holder); -# endif /* get handler cannot return NULL */ assert(newoffset != NULL); @@ -486,11 +421,7 @@ static int rararch_dimensions_preamble(rar_file_t *rar, ret = rararch_dimensions_preamble(rar, newoffset, index, quiet TSRMLS_CC); -# if PHP_MAJOR_VERSION < 7 - zval_ptr_dtor(&newoffset); -# else zval_ptr_dtor(newoffset); -# endif return ret; } else #endif // PHP < 8 @@ -527,9 +458,9 @@ static int rararch_dimensions_preamble(rar_file_t *rar, return FAILURE; } - if (*index < 0L) { + if (*index < 0) { RAR_DOCREF_IF_UNQUIET(NULL TSRMLS_CC, E_WARNING, - "Dimension index must be non-negative, given %ld", *index); + "Dimension index must be non-negative, given " ZEND_LONG_FMT, *index); return FAILURE; } if ((size_t) *index >= _rar_entry_count(rar)) { @@ -544,34 +475,30 @@ static int rararch_dimensions_preamble(rar_file_t *rar, /* }}} */ /* {{{ RarArchive count_elements handler */ -static int rararch_count_elements(handler_this_t *object, long *count TSRMLS_DC) +static int rararch_count_elements(handler_this_t *object, zend_long *count TSRMLS_DC) { rar_file_t *rar = NULL; size_t entry_count; if (rararch_handlers_preamble(object, &rar TSRMLS_CC) == FAILURE) { - *count = 0L; + *count = 0; return SUCCESS; /* intentional */ } entry_count = _rar_entry_count(rar); - if (entry_count > LONG_MAX) - entry_count = (size_t) LONG_MAX; + if (entry_count > ZEND_LONG_MAX) + entry_count = (size_t) ZEND_LONG_MAX; - *count = (long) entry_count; + *count = (zend_long) entry_count; return SUCCESS; } /* }}} */ /* {{{ RarArchive read_dimension handler */ -#if PHP_MAJOR_VERSION < 7 -static zval *rararch_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) -#else static zval *rararch_read_dimension(handler_this_t *object, zval *offset, int type, zval *rv) -#endif { - long index; + zend_long index; rar_file_t *rar = NULL; struct _rar_find_output *out; zval *ret = NULL; @@ -592,11 +519,7 @@ static zval *rararch_read_dimension(handler_this_t *object, zval *offset, int ty _rar_entry_search_seek(out, (size_t) index); _rar_entry_search_advance(out, NULL, 0, 0); assert(out->found); -#if PHP_MAJOR_VERSION < 7 - ALLOC_INIT_ZVAL(ret); -#else ret = rv; -#endif #if PHP_MAJOR_VERSION >= 8 zval object_zv; ZVAL_OBJ(&object_zv, object); @@ -608,9 +531,6 @@ static zval *rararch_read_dimension(handler_this_t *object, zval *offset, int ty ret TSRMLS_CC); #endif _rar_entry_search_end(out); -#if PHP_MAJOR_VERSION < 7 - Z_DELREF_P(ret); /* set refcount to 0 */ -#endif return ret; } /* }}} */ @@ -626,7 +546,7 @@ static void rararch_write_dimension(handler_this_t *object, zval *offset, zval * /* {{{ RarArchive has_dimension handler */ static int rararch_has_dimension(handler_this_t *object, zval *offset, int check_empty TSRMLS_DC) { - long index; + zend_long index; rar_file_t *rar = NULL; (void) check_empty; /* don't care */ @@ -681,11 +601,7 @@ PHP_FUNCTION(rar_open) assert(strnlen(resolved_path, MAXPATHLEN) < MAXPATHLEN); if (callable != NULL) { /* given volume resolver callback */ -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 2 - if (!zend_is_callable(callable, IS_CALLABLE_STRICT, NULL)) { -#else if (!zend_is_callable(callable, IS_CALLABLE_STRICT, NULL TSRMLS_CC)) { -#endif _rar_handle_ext_error("%s" TSRMLS_CC, "Expected the third " "argument, if provided, to be a valid callback"); RETURN_FALSE; @@ -970,8 +886,16 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_rararchive_void, 0) ZEND_END_ARG_INFO() + +#if PHP_VERSION_ID >= 80200 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rararchive_tostring, 0, 0, IS_STRING, 0) +ZEND_END_ARG_INFO() +#else +#define arginfo_rararchive_tostring arginfo_rararchive_void +#endif /* }}} */ + /* clang-format off */ static zend_function_entry php_rararch_class_functions[] = { PHP_ME_MAPPING(open, rar_open, arginfo_rararchive_open, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC) PHP_ME_MAPPING(getEntries, rar_list, arginfo_rararchive_void, ZEND_ACC_PUBLIC) @@ -984,13 +908,14 @@ static zend_function_entry php_rararch_class_functions[] = { PHP_ME_MAPPING(isBroken, rar_broken_is, arginfo_rararchive_void, ZEND_ACC_PUBLIC) PHP_ME_MAPPING(setAllowBroken, rar_allow_broken_set, arginfo_rararchive_setallowbroken, ZEND_ACC_PUBLIC) PHP_ME_MAPPING(close, rar_close, arginfo_rararchive_void, ZEND_ACC_PUBLIC) - PHP_ME(rararch, __toString, arginfo_rararchive_void, ZEND_ACC_PUBLIC) + PHP_ME(rararch, __toString, arginfo_rararchive_tostring, ZEND_ACC_PUBLIC) PHP_ME_MAPPING(__construct, rar_bogus_ctor, arginfo_rararchive_void, ZEND_ACC_PRIVATE | ZEND_ACC_CTOR) #if PHP_MAJOR_VERSION >= 8 PHP_ME(rararch, getIterator, arginfo_rararchive_getiterator, ZEND_ACC_PUBLIC) #endif {NULL, NULL, NULL} }; +/* clang-format on */ /* {{{ Iteration. Very boring stuff indeed. */ @@ -1002,12 +927,7 @@ static zend_object_iterator *rararch_it_get_iterator(zend_class_entry *ce, static void rararch_it_dtor(zend_object_iterator *iter TSRMLS_DC); static void rararch_it_fetch(rararch_iterator *it TSRMLS_DC); static int rararch_it_valid(zend_object_iterator *iter TSRMLS_DC); -#if PHP_MAJOR_VERSION < 7 -static void rararch_it_current_data(zend_object_iterator *iter, - zval ***data TSRMLS_DC); -#else static zval *rararch_it_current_data(zend_object_iterator *iter); -#endif static void rararch_it_move_forward(zend_object_iterator *iter TSRMLS_DC); static void rararch_it_rewind(zend_object_iterator *iter TSRMLS_DC); /* }}} */ @@ -1016,15 +936,8 @@ static void rararch_it_rewind(zend_object_iterator *iter TSRMLS_DC); static void rararch_it_invalidate_current(zend_object_iterator *iter TSRMLS_DC) { rararch_iterator *it = (rararch_iterator *) iter; -#if PHP_MAJOR_VERSION < 7 - if (it->value != NULL) { - zval_ptr_dtor(&it->value); - it->value = NULL; - } -#else zval_ptr_dtor(&it->value); ZVAL_UNDEF(&it->value); -#endif } /* }}} */ @@ -1035,16 +948,9 @@ static void rararch_it_dtor(zend_object_iterator *iter TSRMLS_DC) rararch_it_invalidate_current((zend_object_iterator *) it TSRMLS_CC); -#if PHP_MAJOR_VERSION < 7 - zval_ptr_dtor((zval**) &it->parent.data); /* decrease refcount on zval object */ -#else zval_ptr_dtor(&it->parent.data); -#endif _rar_entry_search_end(it->state); -#if PHP_MAJOR_VERSION < 7 - efree(it); -#endif } /* }}} */ @@ -1055,27 +961,14 @@ static void rararch_it_fetch(rararch_iterator *it TSRMLS_DC) int res; zval *robj; -#if PHP_MAJOR_VERSION < 7 - assert(it->value == NULL); -#else assert(Z_TYPE(it->value) == IS_UNDEF); -#endif if (it->empty_iterator) { -#if PHP_MAJOR_VERSION < 7 - MAKE_STD_ZVAL(it->value); - ZVAL_FALSE(it->value); -#else ZVAL_FALSE(&it->value); -#endif return; } -#if PHP_MAJOR_VERSION < 7 - robj = it->parent.data; -#else robj = &it->parent.data; -#endif res = _rar_get_file_resource_zv_ex(robj, &rar_file, 1 TSRMLS_CC); if (res == FAILURE) @@ -1083,50 +976,25 @@ static void rararch_it_fetch(rararch_iterator *it TSRMLS_DC) "Cannot fetch RarArchive object"); _rar_entry_search_advance(it->state, NULL, 0, 0); -#if PHP_MAJOR_VERSION < 7 - MAKE_STD_ZVAL(it->value); - if (it->state->found) - _rar_entry_to_zval(robj, it->state->header, it->state->packed_size, - it->state->position, it->value TSRMLS_CC); - else { - ZVAL_FALSE(it->value); - } -#else if (it->state->found) _rar_entry_to_zval(&it->parent.data, it->state->header, it->state->packed_size, it->state->position, &it->value TSRMLS_CC); else { ZVAL_FALSE(&it->value); } -#endif } /* }}} */ /* {{{ rararch_it_valid */ static int rararch_it_valid(zend_object_iterator *iter TSRMLS_DC) { -#if PHP_MAJOR_VERSION < 7 - zval *value = ((rararch_iterator *) iter)->value; - assert(value != NULL); - return (Z_TYPE_P(value) != IS_BOOL)?SUCCESS:FAILURE; -#else zval *value = &((rararch_iterator *) iter)->value; assert(Z_TYPE_P(value) != IS_UNDEF); return Z_TYPE_P(value) != IS_FALSE ? SUCCESS : FAILURE; -#endif } /* }}} */ /* {{{ rararch_it_current_data */ -#if PHP_MAJOR_VERSION < 7 -static void rararch_it_current_data(zend_object_iterator *iter, - zval ***data TSRMLS_DC) -{ - zval **value = &(((rararch_iterator *) iter)->value); - assert(*value != NULL); - *data = value; -} -#else static zval *rararch_it_current_data(zend_object_iterator *iter) { zval *ret; @@ -1134,7 +1002,6 @@ static zval *rararch_it_current_data(zend_object_iterator *iter) assert(Z_TYPE_P(ret) != IS_UNDEF); return ret; } -#endif /* }}} */ /* {{{ rararch_it_move_forward */ @@ -1142,11 +1009,7 @@ static void rararch_it_move_forward(zend_object_iterator *iter TSRMLS_DC) { rararch_iterator *it = (rararch_iterator *) iter; rararch_it_invalidate_current((zend_object_iterator *) it TSRMLS_CC); -#if PHP_MAJOR_VERSION < 7 - it->value = NULL; -#else ZVAL_UNDEF(&it->value); -#endif rararch_it_fetch(it TSRMLS_CC); } /* }}} */ @@ -1198,15 +1061,9 @@ static zend_object_iterator *rararch_it_get_iterator(zend_class_entry *ce, rararch_iterator *it = emalloc(sizeof *it); -#if PHP_MAJOR_VERSION < 7 - zval_add_ref(&object); - it->parent.data = object; - it->value = NULL; -#else zend_iterator_init((zend_object_iterator *) it); ZVAL_COPY(&it->parent.data, object); ZVAL_UNDEF(&it->value); -#endif #if PHP_VERSION_ID < 70300 it->parent.funcs = ce->iterator_funcs.funcs; @@ -1240,10 +1097,8 @@ void minit_rararch(TSRMLS_D) rararch_object_handlers.has_dimension = rararch_has_dimension; rararch_object_handlers.unset_dimension = rararch_unset_dimension; rararch_object_handlers.clone_obj = NULL; -#if PHP_MAJOR_VERSION >= 7 rararch_object_handlers.free_obj = rararch_ce_free_object_storage; rararch_object_handlers.offset = XtOffsetOf(ze_rararch_object, parent); -#endif INIT_CLASS_ENTRY(ce, "RarArchive", php_rararch_class_functions); rararch_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC); @@ -1260,7 +1115,3 @@ void minit_rararch(TSRMLS_D) zend_class_implements(rararch_ce_ptr TSRMLS_CC, 1, zend_ce_traversable); #endif } - -#ifdef __cplusplus -} -#endif diff --git a/rarentry.c b/rarentry.c index 5e680f61..ec4cb286 100644 --- a/rarentry.c +++ b/rarentry.c @@ -51,11 +51,11 @@ static void _rar_dos_date_to_text(unsigned dos_time, char *date_string); /* {{{ Functions with external linkage */ /* should be passed the last entry that corresponds to a given file * only that one has the correct CRC. Still, it may have a wrong packedSize */ -/* parent is zval to RarArchive object. The object (not the zval, in PHP 5.x) +/* parent is zval to RarArchive object. The object * will have its refcount increased */ void _rar_entry_to_zval(zval *parent, struct RARHeaderDataEx *entry, - unsigned long packed_size, + zend_ulong packed_size, size_t position, zval *object TSRMLS_DC) /* {{{ */ @@ -65,15 +65,8 @@ void _rar_entry_to_zval(zval *parent, char *filename; int filename_size, filename_len; - long unp_size; /* zval stores PHP ints as long, so use that here */ + zend_long unp_size; zval *parent_copy = parent; -#if PHP_MAJOR_VERSION < 7 - /* allocate zval on the heap */ - zval_addref_p(parent_copy); - SEPARATE_ZVAL(&parent_copy); - /* set refcount to 0; zend_update_property will increase it */ - Z_DELREF_P(parent_copy); -#endif object_init_ex(object, rar_class_entry_ptr); #if PHP_MAJOR_VERSION >= 8 @@ -85,22 +78,17 @@ void _rar_entry_to_zval(zval *parent, zend_update_property(rar_class_entry_ptr, obj, "rarfile", sizeof("rararch") - 1, parent_copy TSRMLS_CC); -#if ULONG_MAX > 0xffffffffUL - unp_size = ((long) entry->UnpSize) + (((long) entry->UnpSizeHigh) << 32); -#else - /* for 32-bit long, at least don't give negative values */ - if ((unsigned long) entry->UnpSize > (unsigned long) LONG_MAX - || entry->UnpSizeHigh != 0) - unp_size = LONG_MAX; - else - unp_size = (long) entry->UnpSize; -#endif + { + uint64_t raw_size = (uint64_t)entry->UnpSizeHigh << 32 | entry->UnpSize; + unp_size = raw_size > (uint64_t)ZEND_LONG_MAX + ? ZEND_LONG_MAX : (zend_long)raw_size; + } filename_size = sizeof(entry->FileNameW) * 4; filename = (char*) emalloc(filename_size); - if (packed_size > (unsigned long) LONG_MAX) - packed_size = LONG_MAX; + if (packed_size > (zend_ulong) ZEND_LONG_MAX) + packed_size = (zend_ulong) ZEND_LONG_MAX; _rar_wide_to_utf(entry->FileNameW, filename, filename_size); /* OK; safe usage below: */ filename_len = _rar_strnlen(filename, filename_size); @@ -109,7 +97,7 @@ void _rar_entry_to_zval(zval *parent, * direct call to rarentry_object_handlers.write_property * zend_update_property_x updates the scope accordingly */ zend_update_property_long(rar_class_entry_ptr, obj, "position", - sizeof("position") - 1, (long) position TSRMLS_CC); + sizeof("position") - 1, (zend_long) position TSRMLS_CC); zend_update_property_stringl(rar_class_entry_ptr, obj, "name", sizeof("name") - 1, filename, filename_len TSRMLS_CC); zend_update_property_long(rar_class_entry_ptr, obj, "unpacked_size", @@ -176,7 +164,7 @@ void _rar_entry_to_zval(zval *parent, #define REG_RAR_CLASS_CONST_LONG(const_name, value) \ zend_declare_class_constant_long(rar_class_entry_ptr, const_name, \ - sizeof(const_name) - 1, (long) value TSRMLS_CC) + sizeof(const_name) - 1, (zend_long) value TSRMLS_CC) #define REG_RAR_PROPERTY(name, comment) \ _rar_decl_priv_prop_null(rar_class_entry_ptr, name, sizeof(name) -1, \ @@ -186,13 +174,6 @@ static int _rar_decl_priv_prop_null(zend_class_entry *ce, const char *name, int name_length, char *doc_comment, int doc_comment_len TSRMLS_DC) /* {{{ */ { -#if PHP_MAJOR_VERSION < 7 - zval *property; - ALLOC_PERMANENT_ZVAL(property); - INIT_ZVAL(*property); - return zend_declare_property_ex(ce, name, name_length, property, - ZEND_ACC_PRIVATE, doc_comment, doc_comment_len TSRMLS_CC); -#else zval property; zend_string *name_str, *doc_str; @@ -212,16 +193,13 @@ static int _rar_decl_priv_prop_null(zend_class_entry *ce, const char *name, zend_string_release(name_str); zend_string_release(doc_str); return ret; -#endif } /* }}} */ static zval *_rar_entry_get_property(zval *entry_obj, char *name, int namelen TSRMLS_DC) /* {{{ */ { zval *tmp; -#if PHP_MAJOR_VERSION >= 7 zval zv; -#endif #if PHP_VERSION_ID < 70100 zend_class_entry *orig_scope = EG(scope); @@ -230,10 +208,8 @@ static zval *_rar_entry_get_property(zval *entry_obj, char *name, int namelen TS #if PHP_MAJOR_VERSION >= 8 tmp = zend_read_property(Z_OBJCE_P(entry_obj), Z_OBJ_P(entry_obj), name, namelen, 1, &zv); -#elif PHP_MAJOR_VERSION >= 7 - tmp = zend_read_property(Z_OBJCE_P(entry_obj), entry_obj, name, namelen, 1, &zv); #else - tmp = zend_read_property(Z_OBJCE_P(entry_obj), entry_obj, name, namelen, 1 TSRMLS_CC); + tmp = zend_read_property(Z_OBJCE_P(entry_obj), entry_obj, name, namelen, 1, &zv); #endif if (tmp == NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, @@ -289,7 +265,7 @@ PHP_METHOD(rarentry, extract) *tmp_position; rar_file_t *rar = NULL; zval *entry_obj = getThis(); - struct RARHeaderDataEx entry; + struct RARHeaderDataEx entry = {0}; HANDLE extract_handle = NULL; int result; int found; @@ -365,12 +341,25 @@ PHP_METHOD(rarentry, extract) cb_udata.password = password; /* Do extraction */ +#ifdef PHP_WIN32 + { + size_t path_w_len = strlen(considered_path_res); + wchar_t *path_w = safe_emalloc(path_w_len, sizeof(wchar_t), sizeof(wchar)); + _rar_utf_to_wide(considered_path_res, path_w, path_w_len + 1); + if (!with_second_arg) + result = RARProcessFileW(extract_handle, RAR_EXTRACT, path_w, NULL); + else + result = RARProcessFileW(extract_handle, RAR_EXTRACT, NULL, path_w); + efree(path_w); + } +#else if (!with_second_arg) result = RARProcessFile(extract_handle, RAR_EXTRACT, considered_path_res, NULL); else result = RARProcessFile(extract_handle, RAR_EXTRACT, NULL, considered_path_res); +#endif if (_rar_handle_error(result TSRMLS_CC) == FAILURE) { RETVAL_FALSE; @@ -585,7 +574,7 @@ PHP_METHOD(rarentry, isDirectory) { zval *tmp; zval *entry_obj = getThis(); - long flags; + zend_long flags; int is_dir; RAR_RETNULL_ON_ARGS(); @@ -604,7 +593,7 @@ PHP_METHOD(rarentry, isEncrypted) { zval *tmp; zval *entry_obj = getThis(); - long flags; + zend_long flags; int is_encrypted; RAR_RETNULL_ON_ARGS(); @@ -735,8 +724,16 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_rar_void, 0) ZEND_END_ARG_INFO() + +#if PHP_VERSION_ID >= 80200 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rar_tostring, 0, 0, IS_STRING, 0) +ZEND_END_ARG_INFO() +#else +#define arginfo_rar_tostring arginfo_rar_void +#endif /* }}} */ + /* clang-format off */ static zend_function_entry php_rar_class_functions[] = { PHP_ME(rarentry, extract, arginfo_rarentry_extract, ZEND_ACC_PUBLIC) PHP_ME(rarentry, getPosition, arginfo_rar_void, ZEND_ACC_PUBLIC) @@ -755,10 +752,11 @@ static zend_function_entry php_rar_class_functions[] = { PHP_ME(rarentry, getRedirType, arginfo_rar_void, ZEND_ACC_PUBLIC) PHP_ME(rarentry, isRedirectToDirectory, arginfo_rar_void, ZEND_ACC_PUBLIC) PHP_ME(rarentry, getRedirTarget, arginfo_rar_void, ZEND_ACC_PUBLIC) - PHP_ME(rarentry, __toString, arginfo_rar_void, ZEND_ACC_PUBLIC) + PHP_ME(rarentry, __toString, arginfo_rar_tostring, ZEND_ACC_PUBLIC) PHP_ME_MAPPING(__construct, rar_bogus_ctor, arginfo_rar_void, ZEND_ACC_PRIVATE | ZEND_ACC_CTOR) {NULL, NULL, NULL} }; +/* clang-format on */ void minit_rarentry(TSRMLS_D) { @@ -785,6 +783,7 @@ void minit_rarentry(TSRMLS_D) REG_RAR_PROPERTY("redir_to_directory", "Whether the redirection target is a directory"); REG_RAR_PROPERTY("redir_target", "Target of the redirectory"); + /* clang-format off */ REG_RAR_CLASS_CONST_LONG("HOST_MSDOS", HOST_MSDOS); REG_RAR_CLASS_CONST_LONG("HOST_OS2", HOST_OS2); REG_RAR_CLASS_CONST_LONG("HOST_WIN32", HOST_WIN32); @@ -836,4 +835,5 @@ void minit_rarentry(TSRMLS_D) REG_RAR_CLASS_CONST_LONG("ATTRIBUTE_UNIX_REGULAR_FILE", 0x08000L); REG_RAR_CLASS_CONST_LONG("ATTRIBUTE_UNIX_SYM_LINK", 0x0A000L); REG_RAR_CLASS_CONST_LONG("ATTRIBUTE_UNIX_SOCKET", 0x0C000L); + /* clang-format on */ } diff --git a/report.xml b/report.xml deleted file mode 100644 index e69de29b..00000000 diff --git a/run-tests8.php b/run-tests-rar.php similarity index 89% rename from run-tests8.php rename to run-tests-rar.php index 062dabb4..5a174947 100644 --- a/run-tests8.php +++ b/run-tests-rar.php @@ -23,12 +23,12 @@ +----------------------------------------------------------------------+ */ -/* $Id$ */ +/* $Id: 2c93f53ab453fd06f92da4eb75b5ff00d0a9ae7c $ */ /* Let there be no top-level code beyond this point: * Only functions and classes, thanks! * - * Minimum required PHP version: 5.3.0 + * Minimum required PHP version: 7.1.0 */ function show_usage() @@ -97,12 +97,12 @@ function show_usage() Do not delete 'all' files, 'php' test file, 'skip' or 'clean' file. - --set-timeout [n] - Set timeout for individual tests, where [n] is the number of + --set-timeout + Set timeout for individual tests, where is the number of seconds. The default value is 60 seconds, or 300 seconds when testing for memory leaks. - --context [n] + --context Sets the number of lines of surrounding context to print for diffs. The default value is 3. @@ -113,18 +113,14 @@ function show_usage() 'mem'. The result types get written independent of the log format, however 'diff' only exists when a test fails. - --show-slow [n] - Show all tests that took longer than [n] milliseconds to run. + --show-slow + Show all tests that took longer than milliseconds to run. --no-clean Do not execute clean section if any. --color --no-color Do/Don't colorize the result type in the test result. - --repeat [n] - Run the tests multiple times in the same process and check the - output of the last execution (CLI SAPI only). - HELP; } @@ -151,7 +147,7 @@ function main() $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch, $temp_source, $temp_target, $test_cnt, $test_dirs, $test_files, $test_idx, $test_list, $test_results, $testfile, - $user_tests, $valgrind, $sum_results, $shuffle, $file_cache, $num_repeats; + $user_tests, $valgrind, $sum_results, $shuffle, $file_cache; // Parallel testing global $workers, $workerID; global $context_line_count; @@ -195,7 +191,7 @@ function main() error_reporting(E_ALL); - $environment = empty($_ENV) ? array() : $_ENV; + $environment = $_ENV ?? []; // Some configurations like php.ini-development set variables_order="GPCS" // not "EGPCS", in which case $_ENV is NOT populated. Detect if the $_ENV @@ -234,6 +230,65 @@ function main() $php_cgi = null; $phpdbg = null; + if (getenv('TEST_PHP_EXECUTABLE')) { + $php = getenv('TEST_PHP_EXECUTABLE'); + + if ($php == 'auto') { + $php = TEST_PHP_SRCDIR . '/sapi/cli/php'; + putenv("TEST_PHP_EXECUTABLE=$php"); + + if (!getenv('TEST_PHP_CGI_EXECUTABLE')) { + $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; + + if (file_exists($php_cgi)) { + putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); + } else { + $php_cgi = null; + } + } + } + $environment['TEST_PHP_EXECUTABLE'] = $php; + } + + if (getenv('TEST_PHP_CGI_EXECUTABLE')) { + $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE'); + + if ($php_cgi == 'auto') { + $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; + putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); + } + + $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi; + } + + if (!getenv('TEST_PHPDBG_EXECUTABLE')) { + if (IS_WINDOWS && file_exists(dirname($php) . "/phpdbg.exe")) { + $phpdbg = realpath(dirname($php) . "/phpdbg.exe"); + } elseif (file_exists(dirname($php) . "/../../sapi/phpdbg/phpdbg")) { + $phpdbg = realpath(dirname($php) . "/../../sapi/phpdbg/phpdbg"); + } elseif (file_exists("./sapi/phpdbg/phpdbg")) { + $phpdbg = realpath("./sapi/phpdbg/phpdbg"); + } elseif (file_exists(dirname($php) . "/phpdbg")) { + $phpdbg = realpath(dirname($php) . "/phpdbg"); + } else { + $phpdbg = null; + } + if ($phpdbg) { + putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); + } + } + + if (getenv('TEST_PHPDBG_EXECUTABLE')) { + $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE'); + + if ($phpdbg == 'auto') { + $phpdbg = TEST_PHP_SRCDIR . '/sapi/phpdbg/phpdbg'; + putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); + } + + $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg; + } + if (getenv('TEST_PHP_LOG_FORMAT')) { $log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT')); } else { @@ -252,18 +307,18 @@ function main() if (getenv('SHOW_ONLY_GROUPS')) { $SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS')); } else { - $SHOW_ONLY_GROUPS = array(); + $SHOW_ONLY_GROUPS = []; } // Check whether user test dirs are requested. if (getenv('TEST_PHP_USER')) { $user_tests = explode(',', getenv('TEST_PHP_USER')); } else { - $user_tests = array(); + $user_tests = []; } - $exts_to_test = array(); - $ini_overwrites = array( + $exts_to_test = []; + $ini_overwrites = [ 'output_handler=', 'open_basedir=', 'disable_functions=', @@ -298,7 +353,7 @@ function main() 'zend.exception_ignore_args=0', 'zend.exception_string_param_max_len=15', 'short_open_tag=0', - ); + ]; $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; @@ -309,18 +364,18 @@ function main() // Determine the tests to be run. - $test_files = array(); - $redir_tests = array(); - $test_results = array(); - $PHP_FAILED_TESTS = array( - 'BORKED' => array(), - 'FAILED' => array(), - 'WARNED' => array(), - 'LEAKED' => array(), - 'XFAILED' => array(), - 'XLEAKED' => array(), - 'SLOW' => array() - ); + $test_files = []; + $redir_tests = []; + $test_results = []; + $PHP_FAILED_TESTS = [ + 'BORKED' => [], + 'FAILED' => [], + 'WARNED' => [], + 'LEAKED' => [], + 'XFAILED' => [], + 'XLEAKED' => [], + 'SLOW' => [] + ]; // If parameters given assume they represent selected tests to run. $result_tests_file = false; @@ -350,14 +405,13 @@ function main() $shuffle = false; $workers = null; $context_line_count = 3; - $num_repeats = 1; - $cfgtypes = array('show', 'keep'); - $cfgfiles = array('skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem'); - $cfg = array(); + $cfgtypes = ['show', 'keep']; + $cfgfiles = ['skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem']; + $cfg = []; foreach ($cfgtypes as $type) { - $cfg[$type] = array(); + $cfg[$type] = []; foreach ($cfgfiles as $file) { $cfg[$type][$file] = false; @@ -365,7 +419,7 @@ function main() } if (!isset($argc, $argv) || !$argc) { - $argv = array(__FILE__); + $argv = [__FILE__]; $argc = 1; } @@ -424,9 +478,9 @@ function main() $test_list = file($argv[++$i]); if ($test_list) { foreach ($test_list as $test) { - $matches = array(); + $matches = []; if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) { - $redir_tests[] = array($matches[1], $matches[2]); + $redir_tests[] = [$matches[1], $matches[2]]; } else { if (strlen($test)) { $test_files[] = trim($test); @@ -517,10 +571,14 @@ function main() $just_save_results = true; break; case '--set-timeout': - $environment['TEST_TIMEOUT'] = $argv[++$i]; + $timeout = $argv[++$i] ?? ''; + if (!preg_match('/^\d+$/', $timeout)) { + error("'$timeout' is not a valid number of seconds, try e.g. --set-timeout 60 for 1 minute"); + } + $environment['TEST_TIMEOUT'] = intval($timeout, 10); break; case '--context': - $context_line_count = empty($argv[++$i]) ? '' : $argv[$i]; + $context_line_count = $argv[++$i] ?? ''; if (!preg_match('/^\d+$/', $context_line_count)) { error("'$context_line_count' is not a valid number of lines of context, try e.g. --context 3 for 3 lines"); } @@ -532,7 +590,11 @@ function main() } break; case '--show-slow': - $slow_min_ms = $argv[++$i]; + $slow_min_ms = $argv[++$i] ?? ''; + if (!preg_match('/^\d+$/', $slow_min_ms)) { + error("'$slow_min_ms' is not a valid number of milliseconds, try e.g. --show-slow 1000 for 1 second"); + } + $slow_min_ms = intval($slow_min_ms, 10); break; case '--temp-source': $temp_source = $argv[++$i]; @@ -569,10 +631,6 @@ function main() . ':print_suppressions=0'; } break; - case '--repeat': - $num_repeats = (int) $argv[++$i]; - $environment['SKIP_REPEAT'] = 1; - break; //case 'w' case '-': // repeat check with full switch @@ -582,7 +640,7 @@ function main() } break; case '--version': - echo '$Id$' . "\n"; + echo '$Id: 2c93f53ab453fd06f92da4eb75b5ff00d0a9ae7c $' . "\n"; exit(1); default: @@ -633,39 +691,14 @@ function main() return; } - if (!$php) { - $php = getenv('TEST_PHP_EXECUTABLE'); - } - if (!$php) { - $php = PHP_BINDIR . '/php'; - if (IS_WINDOWS) { - $php .= '.exe'; - } - var_dump($php); - } - - if (!$php_cgi) { - $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE'); - } - if (!$php_cgi) { - $php_cgi = get_binary($php, 'php-cgi', 'sapi/cgi/php-cgi'); + // Default to PHP_BINARY as executable + if (!isset($environment['TEST_PHP_EXECUTABLE'])) { + $php = PHP_BINARY; + putenv("TEST_PHP_EXECUTABLE=$php"); + $environment['TEST_PHP_EXECUTABLE'] = $php; } - if (!$phpdbg) { - $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE'); - } - if (!$phpdbg) { - $phpdbg = get_binary($php, 'phpdbg', 'sapi/phpdbg/phpdbg'); - } - - putenv("TEST_PHP_EXECUTABLE=$php"); - $environment['TEST_PHP_EXECUTABLE'] = $php; - putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); - $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi; - putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); - $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg; - - if ($conf_passed !== null) { + if (!empty($conf_passed)) { if (IS_WINDOWS) { $pass_options .= " -c " . escapeshellarg($conf_passed); } else { @@ -715,13 +748,13 @@ function main() } } else { // Compile a list of all test files (*.phpt). - $test_files = array(); + $test_files = []; $exts_tested = count($exts_to_test); $exts_skipped = 0; $ignored_by_ext = 0; sort($exts_to_test); - $test_dirs = array(); - $optionals = array('Zend', 'tests', 'ext', 'sapi'); + $test_dirs = []; + $optionals = ['Zend', 'tests', 'ext', 'sapi']; foreach ($optionals as $dir) { if (is_dir($dir)) { @@ -778,7 +811,7 @@ function main() junit_save_xml(); if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' && - ($sum_results['FAILED'] || $sum_results['LEAKED'])) { + ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) { exit(1); } } @@ -787,7 +820,7 @@ function main() /** * @return array|float|int */ - function hrtime($as_num = false) + function hrtime(bool $as_num = false) { $t = microtime(true); @@ -796,7 +829,7 @@ function hrtime($as_num = false) } $s = floor($t); - return array(0 => $s, 1 => ($t - $s) * 1000000000); + return [0 => $s, 1 => ($t - $s) * 1000000000]; } } @@ -828,14 +861,14 @@ function write_information() INI actual : " , realpath(get_cfg_var("cfg_file_path")) , " More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n","", php_ini_scanned_files()) : "** not determined **"); ?>'; save_text($info_file, $php_info); - $info_params = array(); + $info_params = []; settings2array($ini_overwrites, $info_params); $info_params = settings2params($info_params); - $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`; - define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`); + $php_info = shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\""); + define('TESTED_PHP_VERSION', shell_exec("$php -n -r \"echo PHP_VERSION;\"")); if ($php_cgi && $php != $php_cgi) { - $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`; + $php_info_cgi = shell_exec("$php_cgi $pass_options $info_params $no_file_cache -q \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; } else { @@ -843,7 +876,7 @@ function write_information() } if ($phpdbg) { - $phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`; + $phpdbg_info = shell_exec("$phpdbg $pass_options $info_params $no_file_cache -qrr \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep"; } else { @@ -858,15 +891,15 @@ function write_information() // load list of enabled extensions save_text($info_file, ''); - $exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`); + $exts_to_test = explode(',', shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\"")); // check for extensions that need special handling and regenerate - $info_params_ex = array( - 'session' => array('session.auto_start=0'), - 'tidy' => array('tidy.clean_output=0'), - 'zlib' => array('zlib.output_compression=Off'), - 'xdebug' => array('xdebug.mode=off'), - 'mbstring' => array('mbstring.func_overload=0'), - ); + $info_params_ex = [ + 'session' => ['session.auto_start=0'], + 'tidy' => ['tidy.clean_output=0'], + 'zlib' => ['zlib.output_compression=Off'], + 'xdebug' => ['xdebug.mode=off'], + 'mbstring' => ['mbstring.func_overload=0'], + ]; foreach ($info_params_ex as $ext => $ini_overwrites_ex) { if (in_array($ext, $exts_to_test)) { @@ -979,7 +1012,7 @@ function save_or_mail_results() } /* Try the most common flags for 'version' */ - $flags = array('-v', '-V', '--version'); + $flags = ['-v', '-V', '--version']; $cc_status = 0; foreach ($flags as $flag) { @@ -1024,29 +1057,14 @@ function save_or_mail_results() } } -function get_binary($php, $sapi, $sapi_path) -{ - $dir = dirname($php); - if (IS_WINDOWS && file_exists("$dir/$sapi.exe")) { - return realpath("$dir/$sapi.exe"); - } - if (file_exists("$dir/../../$sapi_path")) { - return realpath("$dir/../../$sapi_path"); - } - if (file_exists("$dir/$sapi")) { - return realpath("$dir/$sapi"); - } - return null; -} - -function find_files($dir, $is_ext_dir = false, $ignore = false) +function find_files(string $dir, bool $is_ext_dir = false, bool $ignore = false) { global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped; $o = opendir($dir) or error("cannot open directory: $dir"); while (($name = readdir($o)) !== false) { - if (is_dir("{$dir}/{$name}") && !in_array($name, array('.', '..', '.svn'))) { + if (is_dir("{$dir}/{$name}") && !in_array($name, ['.', '..', '.svn'])) { $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test)); if ($skip_ext) { $exts_skipped++; @@ -1077,7 +1095,7 @@ function find_files($dir, $is_ext_dir = false, $ignore = false) /** * @param array|string $name */ -function test_name($name) +function test_name($name): string { if (is_array($name)) { return $name[0] . ':' . $name[1]; @@ -1089,7 +1107,7 @@ function test_name($name) * @param array|string $a * @param array|string $b */ -function test_sort($a, $b) +function test_sort($a, $b): int { $a = test_name($a); $b = test_name($b); @@ -1110,7 +1128,7 @@ function test_sort($a, $b) // Send Email to QA Team // -function mail_qa_team($data, $status = false) +function mail_qa_team(string $data, bool $status = false): bool { $url_bits = parse_url(QA_SUBMISSION_PAGE); @@ -1156,7 +1174,7 @@ function mail_qa_team($data, $status = false) // Write the given text to a temporary file, and return the filename. // -function save_text($filename, $text, $filename_copy = null) +function save_text(string $filename, string $text, $filename_copy = null) { global $DETAILED; @@ -1183,7 +1201,7 @@ function save_text($filename, $text, $filename_copy = null) // Write an error in a format recognizable to Emacs or MSVC. // -function error_report($testname, $logname, $tested) +function error_report(string $testname, string $logname, string $tested) { $testname = realpath($testname); $logname = realpath($logname); @@ -1204,33 +1222,33 @@ function error_report($testname, $logname, $tested) * @return false|string */ function system_with_timeout( - $commandline, + string $commandline, $env = null, $stdin = null, - $captureStdIn = true, - $captureStdOut = true, - $captureStdErr = true + bool $captureStdIn = true, + bool $captureStdOut = true, + bool $captureStdErr = true ) { global $valgrind; $data = ''; - $bin_env = array(); + $bin_env = []; foreach ((array) $env as $key => $value) { $bin_env[$key] = $value; } - $descriptorspec = array(); + $descriptorspec = []; if ($captureStdIn) { - $descriptorspec[0] = array('pipe', 'r'); + $descriptorspec[0] = ['pipe', 'r']; } if ($captureStdOut) { - $descriptorspec[1] = array('pipe', 'w'); + $descriptorspec[1] = ['pipe', 'w']; } if ($captureStdErr) { - $descriptorspec[2] = array('pipe', 'w'); + $descriptorspec[2] = ['pipe', 'w']; } - $proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, array('suppress_errors' => true)); + $proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, ['suppress_errors' => true]); if (!$proc) { return false; @@ -1244,7 +1262,7 @@ function system_with_timeout( unset($pipes[0]); } - $timeout = $valgrind ? 300 : (empty($env['TEST_TIMEOUT']) ? 60 : $env['TEST_TIMEOUT']); + $timeout = $valgrind ? 300 : ($env['TEST_TIMEOUT'] ?? 60); while (true) { /* hide errors from interrupted syscalls */ @@ -1284,6 +1302,9 @@ function system_with_timeout( } if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) { $data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n"; + } else if (defined('PHP_WINDOWS_VERSION_MAJOR') && (($stat["exitcode"] >> 28) & 0b1111) === 0b1100) { + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781 + $data .= "\nTermsig=" . $stat["exitcode"] . "\n"; } proc_close($proc); @@ -1328,7 +1349,7 @@ function run_all_tests(array $test_files, array $env, $redir_tested = null) $test_idx++; if ($workerID) { - $PHP_FAILED_TESTS = array('BORKED' => array(), 'FAILED' => array(), 'WARNED' => array(), 'LEAKED' => array(), 'XFAILED' => array(), 'XLEAKED' => array(), 'SLOW' => array()); + $PHP_FAILED_TESTS = ['BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => []]; ob_start(); } @@ -1339,14 +1360,14 @@ function run_all_tests(array $test_files, array $env, $redir_tested = null) if (!is_array($name) && $result != 'REDIR') { if ($workerID) { - send_message($workerSock, array( + send_message($workerSock, [ "type" => "test_result", "name" => $name, "index" => $index, "result" => $result, "text" => $resultText, "PHP_FAILED_TESTS" => $PHP_FAILED_TESTS - )); + ]); continue; } @@ -1373,8 +1394,8 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) $thisPHP = PHP_BINARY; $thisScript = __FILE__; - $workerProcs = array(); - $workerSocks = array(); + $workerProcs = []; + $workerSocks = []; echo "=====================================================================\n"; echo "========= WELCOME TO THE FUTURE: run-tests PARALLEL EDITION =========\n"; @@ -1383,9 +1404,9 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) // Each test may specify a list of conflict keys. While a test that conflicts with // key K is running, no other test that conflicts with K may run. Conflict keys are // specified either in the --CONFLICTS-- section, or CONFLICTS file inside a directory. - $dirConflictsWith = array(); - $fileConflictsWith = array(); - $sequentialTests = array(); + $dirConflictsWith = []; + $fileConflictsWith = []; + $sequentialTests = []; foreach ($test_files as $i => $file) { $contents = file_get_contents($file); if (preg_match('/^--CONFLICTS--(.+?)^--/ms', $contents, $matches)) { @@ -1394,7 +1415,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) // Cache per-directory conflicts in a separate map, so we compute these only once. $dir = dirname($file); if (!isset($dirConflictsWith[$dir])) { - $dirConflicts = array(); + $dirConflicts = []; if (file_exists($dir . '/CONFLICTS')) { $contents = file_get_contents($dir . '/CONFLICTS'); $dirConflicts = parse_conflicts($contents); @@ -1447,17 +1468,17 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) for ($i = 1; $i <= $workers; $i++) { $proc = proc_open( $thisPHP . ' ' . escapeshellarg($thisScript), - array(), // Inherit our stdin, stdout and stderr + [], // Inherit our stdin, stdout and stderr $pipes, null, - $_ENV + array( + $_ENV + [ "TEST_PHP_WORKER" => $i, "TEST_PHP_URI" => $sockUri, - ), - array( + ], + [ "suppress_errors" => true, 'create_new_console' => true, - ) + ] ); if ($proc === false) { kill_children($workerProcs); @@ -1473,18 +1494,18 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) error("Failed to accept connection from worker."); } - $greeting = base64_encode(serialize(array( + $greeting = base64_encode(serialize([ "type" => "hello", "GLOBALS" => $GLOBALS, - "constants" => array( + "constants" => [ "INIT_DIR" => INIT_DIR, "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR, "PHP_QA_EMAIL" => PHP_QA_EMAIL, "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE, "QA_REPORTS_PAGE" => QA_REPORTS_PAGE, "TRAVIS_CI" => TRAVIS_CI - ) - ))) . "\n"; + ] + ])) . "\n"; stream_set_timeout($workerSock, 5); if (fwrite($workerSock, $greeting) === false) { @@ -1514,13 +1535,13 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) echo "=====================================================================\n"; echo "\n"; - $rawMessageBuffers = array(); + $rawMessageBuffers = []; $testsInProgress = 0; // Map from conflict key to worker ID. - $activeConflicts = array(); + $activeConflicts = []; // Tests waiting due to conflicts. Map from conflict key to array. - $waitingTests = array(); + $waitingTests = []; escape: while ($test_files || $sequentialTests || $testsInProgress > 0) { @@ -1536,7 +1557,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) } while (false !== ($rawMessage = fgets($workerSock))) { // work around fgets truncating things - if ((empty($rawMessageBuffers[$i]) ? '' : $rawMessageBuffers[$i]) !== '') { + if (($rawMessageBuffers[$i] ?? '') !== '') { $rawMessage = $rawMessageBuffers[$i] . $rawMessage; $rawMessageBuffers[$i] = ''; } @@ -1574,14 +1595,14 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) // Schedule sequential tests only once we are down to one worker. if (count($workerProcs) === 1 && $sequentialTests) { $test_files = array_merge($test_files, $sequentialTests); - $sequentialTests = array(); + $sequentialTests = []; } // Batch multiple tests to reduce communication overhead. // - When valgrind is used, communication overhead is relatively small, // so just use a batch size of 1. // - If this is running a small enough number of tests, // reduce the batch size to give batches to more workers. - $files = array(); + $files = []; $maxBatchSize = $valgrind ? 1 : ($shuffle ? 4 : 32); $averageFilesPerWorker = max(1, (int) ceil($totalFileCount / count($workerProcs))); $batchSize = min($maxBatchSize, $averageFilesPerWorker); @@ -1601,12 +1622,12 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) } } $testsInProgress++; - send_message($workerSocks[$i], array( + send_message($workerSocks[$i], [ "type" => "run_tests", "test_files" => $files, "env" => $env, "redir_tested" => $redir_tested - )); + ]); } else { proc_terminate($workerProcs[$i]); unset($workerProcs[$i]); @@ -1615,7 +1636,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) } break; case "test_result": - list($name, $index, $result, $resultText) = array($message["name"], $message["index"], $message["result"], $message["text"]); + list($name, $index, $result, $resultText) = [$message["name"], $message["index"], $message["result"], $message["text"]]; foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) { $PHP_FAILED_TESTS[$category] = array_merge($PHP_FAILED_TESTS[$category], $tests); } @@ -1648,7 +1669,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) break; case "php_error": kill_children($workerProcs); - $error_consts = array( + $error_consts = [ 'E_ERROR', 'E_WARNING', 'E_PARSE', @@ -1662,8 +1683,9 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) 'E_USER_NOTICE', 'E_STRICT', // TODO Cleanup when removed from Zend Engine. 'E_RECOVERABLE_ERROR', + 'E_DEPRECATED', 'E_USER_DEPRECATED' - ); + ]; $error_consts = array_combine(array_map('constant', $error_consts), $error_consts); error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]"); // no break @@ -1689,8 +1711,7 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested) function send_message($stream, array $message) { - $blocking = stream_get_meta_data($stream); - $blocking = $blocking["blocked"]; + $blocking = stream_get_meta_data($stream)["blocked"]; stream_set_blocking($stream, true); fwrite($stream, base64_encode(serialize($message)) . "\n"); stream_set_blocking($stream, $blocking); @@ -1719,11 +1740,11 @@ function run_worker() error("Unexpected greeting of type $greeting[type]"); } - set_error_handler(function ($errno, $errstr, $errfile, $errline) use ($workerSock) { + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use ($workerSock): bool { if (error_reporting() & $errno) { - send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + array( + send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + [ 'type' => 'php_error' - )); + ]); } return true; @@ -1738,14 +1759,14 @@ function run_worker() define($const, $value); } - send_message($workerSock, array( + send_message($workerSock, [ "type" => "hello_reply", "workerID" => $workerID - )); + ]); - send_message($workerSock, array( + send_message($workerSock, [ "type" => "ready" - )); + ]); while (($command = fgets($workerSock))) { $command = unserialize(base64_decode($command)); @@ -1753,17 +1774,17 @@ function run_worker() switch ($command["type"]) { case "run_tests": run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]); - send_message($workerSock, array( + send_message($workerSock, [ "type" => "tests_finished", "junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null, - )); + ]); junit_init(); break; default: - send_message($workerSock, array( + send_message($workerSock, [ "type" => "error", "msg" => "Unrecognised message type: $command[type]" - )); + ]); break 2; } } @@ -1772,7 +1793,7 @@ function run_worker() // // Show file or result block // -function show_file_block($file, $block, $section = null) +function show_file_block(string $file, string $block, $section = null) { global $cfg; global $colorize; @@ -1783,8 +1804,8 @@ function show_file_block($file, $block, $section = null) } if ($section === 'DIFF' && $colorize) { // '-' is Light Red for removal, '+' is Light Green for addition - $block = preg_replace('/^[0-9]+\-\s.*$/m', "\x1B[1;31m\\0\x1B[0m", $block); - $block = preg_replace('/^[0-9]+\+\s.*$/m', "\x1B[1;32m\\0\x1B[0m", $block); + $block = preg_replace('/^[0-9]+\-\s.*$/m', "\e[1;31m\\0\e[0m", $block); + $block = preg_replace('/^[0-9]+\+\s.*$/m', "\e[1;32m\\0\e[0m", $block); } echo "\n========" . $section . "========\n"; @@ -1793,20 +1814,13 @@ function show_file_block($file, $block, $section = null) } } -function skip_test($tested, $tested_file, $shortname, $reason) { - show_result('SKIP', $tested, $tested_file, "reason: $reason"); - junit_init_suite(junit_get_suitename_for($shortname)); - junit_mark_test_as('SKIP', $shortname, $tested, 0, $reason); - return 'SKIPPED'; -} - // // Run an individual test case. // /** * @param string|array $file */ -function run_test($php, $file, array $env) +function run_test(string $php, $file, array $env): string { global $log_format, $ini_overwrites, $PHP_FAILED_TESTS; global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx; @@ -1816,7 +1830,6 @@ function run_test($php, $file, array $env) global $no_file_cache; global $slow_min_ms; global $preload, $file_cache; - global $num_repeats; // Parallel testing global $workerID; $temp_filenames = null; @@ -1842,7 +1855,7 @@ function run_test($php, $file, array $env) } // Load the sections of the test file. - $section_text = array('TEST' => ''); + $section_text = ['TEST' => '']; $fp = fopen($file, "rb") or error("Cannot open test file: $file"); @@ -1881,16 +1894,16 @@ function run_test($php, $file, array $env) } // check for unknown sections - if (!in_array($section, array( + if (!in_array($section, [ 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', - 'EXPECTREGEX_DYNAMIC', 'EXPECT_DYNAMIC', 'EXPECTF_DYNAMIC', + 'EXPECT_DYNAMIC', 'EXPECTF_DYNAMIC', 'EXPECTREGEX_DYNAMIC', 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', 'INI', 'ENV', 'EXTENSIONS', 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', - ))) { + ])) { $bork_info = 'Unknown section "' . $section . '"'; } @@ -1911,10 +1924,6 @@ function run_test($php, $file, array $env) } } - $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); - $tested_file = $shortname; - $tested = trim($section_text['TEST']); - // the redirect section allows a set of tests to be reused outside of // a given test dir if ($bork_info === null) { @@ -1932,11 +1941,7 @@ function run_test($php, $file, array $env) unset($section_text['FILEEOF']); } - if ($num_repeats > 1 && isset($section_text['FILE_EXTERNAL'])) { - return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); - } - - foreach (array('FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) { + foreach (['FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX'] as $prefix) { $key = $prefix . '_EXTERNAL'; if (isset($section_text[$key])) { @@ -1951,21 +1956,22 @@ function run_test($php, $file, array $env) } } } + foreach (array('EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) { - $key = $prefix . '_DYNAMIC'; - if (isset($section_text[$key])) { - global $php; - $temp_file = tmpfile(); - fwrite($temp_file, $section_text[$key]); - fflush($temp_file); - $temp_file_name = stream_get_meta_data($temp_file); - $temp_file_name = $temp_file_name['uri']; - $output = system_with_timeout("$php -n -d display_errors=1 -d display_startup_errors=0 \"$temp_file_name\"", array()); - $output = trim($output); - $section_text[$prefix] = $output; - unset($section_text[$key]); - } - } + $key = $prefix . '_DYNAMIC'; + if (isset($section_text[$key])) { + global $php; + $temp_file = tmpfile(); + fwrite($temp_file, $section_text[$key]); + fflush($temp_file); + $temp_file_name = stream_get_meta_data($temp_file); + $temp_file_name = $temp_file_name['uri']; + $output = system_with_timeout("$php -n -d display_errors=1 -d display_startup_errors=0 \"$temp_file_name\"", array()); + $output = trim($output); + $section_text[$prefix] = $output; + unset($section_text[$key]); + } + } if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) { $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; @@ -1974,15 +1980,18 @@ function run_test($php, $file, array $env) } fclose($fp); + $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); + $tested_file = $shortname; + if ($bork_info !== null) { show_result("BORK", $bork_info, $tested_file); - $PHP_FAILED_TESTS['BORKED'][] = array( + $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$bork_info [$file]", - ); + ]; junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info); return 'BORKED'; @@ -2003,16 +2012,30 @@ function run_test($php, $file, array $env) $cmdRedirect = ''; } + $tested = trim($section_text['TEST']); + /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ if (array_key_exists('CGI', $section_text) || !empty($section_text['GET']) || !empty($section_text['POST']) || !empty($section_text['GZIP_POST']) || !empty($section_text['DEFLATE_POST']) || !empty($section_text['POST_RAW']) || !empty($section_text['PUT']) || !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { - if (!$php_cgi) { - return skip_test($tested, $tested_file, $shortname, 'CGI not available'); + if (isset($php_cgi)) { + $php = $php_cgi . ' -C '; + } elseif (IS_WINDOWS && file_exists(dirname($php) . "/php-cgi.exe")) { + $php = realpath(dirname($php) . "/php-cgi.exe") . ' -C '; + } else { + if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) { + $php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C '; + } elseif (file_exists("./sapi/cgi/php-cgi")) { + $php = realpath("./sapi/cgi/php-cgi") . ' -C '; + } elseif (file_exists(dirname($php) . "/php-cgi")) { + $php = realpath(dirname($php) . "/php-cgi") . ' -C '; + } else { + show_result('SKIP', $tested, $tested_file, "reason: CGI not available"); + + junit_init_suite(junit_get_suitename_for($shortname)); + junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available'); + return 'SKIPPED'; + } } - $php = $php_cgi . ' -C '; $uses_cgi = true; - if ($num_repeats > 1) { - return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat'); - } } /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ @@ -2029,22 +2052,11 @@ function run_test($php, $file, array $env) // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN. $extra_options = '-rr'; } else { - return skip_test($tested, $tested_file, $shortname, 'phpdbg not available'); - } - if ($num_repeats > 1) { - return skip_test($tested, $tested_file, $shortname, 'phpdbg does not support --repeat'); - } - } + show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available"); - if ($num_repeats > 1) { - if (array_key_exists('CLEAN', $section_text)) { - return skip_test($tested, $tested_file, $shortname, 'Test with CLEAN might not be repeatable'); - } - if (array_key_exists('STDIN', $section_text)) { - return skip_test($tested, $tested_file, $shortname, 'Test with STDIN might not be repeatable'); - } - if (array_key_exists('CAPTURE_STDIO', $section_text)) { - return skip_test($tested, $tested_file, $shortname, 'Test with CAPTURE_STDIO might not be repeatable'); + junit_init_suite(junit_get_suitename_for($shortname)); + junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available'); + return 'SKIPPED'; } } @@ -2094,7 +2106,7 @@ function run_test($php, $file, array $env) save_text($copy_file, $section_text['FILE']); } - $temp_filenames = array( + $temp_filenames = [ 'file' => $copy_file, 'diff' => $diff_filename, 'log' => $log_filename, @@ -2105,7 +2117,7 @@ function run_test($php, $file, array $env) 'php' => $temp_file, 'skip' => $temp_skipif, 'clean' => $temp_clean - ); + ]; } if (is_array($IN_REDIRECT)) { @@ -2151,16 +2163,16 @@ function run_test($php, $file, array $env) } // Default ini settings - $ini_settings = $workerID ? array('opcache.cache_id' => "worker$workerID") : array(); + $ini_settings = $workerID ? ['opcache.cache_id' => "worker$workerID"] : []; // Additional required extensions if (array_key_exists('EXTENSIONS', $section_text)) { - $ext_params = array(); + $ext_params = []; settings2array($ini_overwrites, $ext_params); $ext_params = settings2params($ext_params); - $ext_dir = `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo ini_get('extension_dir');"`; + $ext_dir = shell_exec("$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r \"echo ini_get('extension_dir');\""); $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); - $loaded = explode(",", `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); + $loaded = explode(",", shell_exec("$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r \"echo implode(',', get_loaded_extensions());\"")); $ext_prefix = IS_WINDOWS ? "php_" : ""; foreach ($extensions as $req_ext) { if (!in_array($req_ext, $loaded)) { @@ -2190,9 +2202,6 @@ function run_test($php, $file, array $env) // even though all the files are re-created. $ini_settings['opcache.validate_timestamps'] = '0'; } - } else if ($num_repeats > 1) { - // Make sure warnings still show up on the second run. - $ini_settings['opcache.record_warnings'] = '1'; } // Any special ini settings @@ -2203,10 +2212,6 @@ function run_test($php, $file, array $env) $replacement = IS_WINDOWS ? '"' . PHP_BINARY . ' -r \"while ($in = fgets(STDIN)) echo $in;\" > $1"' : 'tee $1 >/dev/null'; $section_text['INI'] = preg_replace('/{MAIL:(\S+)}/', $replacement, $section_text['INI']); settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings); - - if ($num_repeats > 1 && isset($ini_settings['opcache.opt_debug_level'])) { - return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); - } } $ini_settings = settings2params($ini_settings); @@ -2266,13 +2271,13 @@ function run_test($php, $file, array $env) $section_text['XFAIL'] = ltrim(substr($output, 5)); } elseif ($output !== '') { show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames); - $PHP_FAILED_TESTS['BORKED'][] = array( + $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$output [$file]", - ); + ]; junit_mark_test_as('BORK', $shortname, $tested, null, $output); return 'BORKED'; @@ -2290,7 +2295,7 @@ function run_test($php, $file, array $env) } if (isset($section_text['REDIRECTTEST'])) { - $test_files = array(); + $test_files = []; $IN_REDIRECT = eval($section_text['REDIRECTTEST']); $IN_REDIRECT['via'] = "via [$shortname]\n\t"; @@ -2305,7 +2310,7 @@ function run_test($php, $file, array $env) find_files($IN_REDIRECT['TESTS']); foreach ($GLOBALS['test_files'] as $f) { - $test_files[] = array($f, $file); + $test_files[] = [$f, $file]; } } $test_cnt += count($test_files) - 1; @@ -2330,13 +2335,13 @@ function run_test($php, $file, array $env) } else { $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; show_result("BORK", $bork_info, '', '', $temp_filenames); - $PHP_FAILED_TESTS['BORKED'][] = array( + $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$bork_info [$file]", - ); + ]; } } @@ -2347,13 +2352,13 @@ function run_test($php, $file, array $env) $bork_info = "Redirected test did not contain redirection info"; show_result("BORK", $bork_info, '', '', $temp_filenames); - $PHP_FAILED_TESTS['BORKED'][] = array( + $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$bork_info [$file]", - ); + ]; junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info); @@ -2508,8 +2513,7 @@ function run_test($php, $file, array $env) $env['CONTENT_TYPE'] = ''; $env['CONTENT_LENGTH'] = ''; - $repeat_option = $num_repeats > 1 ? "--repeat $num_repeats" : ""; - $cmd = "$php $pass_options $repeat_option $ini_settings -f \"$test_file\" $args$cmdRedirect"; + $cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect"; } if ($valgrind) { @@ -2537,19 +2541,19 @@ function run_test($php, $file, array $env) $hrtime = hrtime(); $startTime = $hrtime[0] * 1000000000 + $hrtime[1]; - $out = system_with_timeout($cmd, $env, empty($section_text['STDIN']) ? null : $section_text['STDIN'], $captureStdIn, $captureStdOut, $captureStdErr); + $out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr); junit_finish_timer($shortname); $hrtime = hrtime(); $time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime; if ($time >= $slow_min_ms * 1000000) { - $PHP_FAILED_TESTS['SLOW'][] = array( + $PHP_FAILED_TESTS['SLOW'][] = [ 'name' => $file, 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 'output' => '', 'diff' => '', 'info' => $time / 1000000000, - ); + ]; } if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { @@ -2582,30 +2586,11 @@ function run_test($php, $file, array $env) } } - if ($num_repeats > 1) { - // In repeat mode, retain the output before the first execution, - // and of the last execution. Do this early, because the trimming below - // makes the newline handling complicated. - $separator1 = "Executing for the first time...\n"; - $separator1_pos = strpos($out, $separator1); - if ($separator1_pos !== false) { - $separator2 = "Finished execution, repeating...\n"; - $separator2_pos = strrpos($out, $separator2); - if ($separator2_pos !== false) { - $out = substr($out, 0, $separator1_pos) - . substr($out, $separator2_pos + strlen($separator2)); - } else { - $out = substr($out, 0, $separator1_pos) - . substr($out, $separator1_pos + strlen($separator1)); - } - } - } - // Does the output match what is expected? $output = preg_replace("/\r\n/", "\n", trim($out)); /* when using CGI, strip the headers from the output */ - $headers = array(); + $headers = []; if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { $output = trim($match[2]); @@ -2622,8 +2607,8 @@ function run_test($php, $file, array $env) $failed_headers = false; if (isset($section_text['EXPECTHEADERS'])) { - $want = array(); - $wanted_headers = array(); + $want = []; + $wanted_headers = []; $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); foreach ($lines as $line) { @@ -2634,7 +2619,7 @@ function run_test($php, $file, array $env) } } - $output_headers = array(); + $output_headers = []; foreach ($want as $k => $v) { if (isset($headers[$k])) { @@ -2821,18 +2806,23 @@ function run_test($php, $file, array $env) // write .sh if (strpos($log_format, 'S') !== false) { + $env_lines = []; + foreach ($env as $env_var => $env_val) { + $env_lines[] = "export $env_var=" . escapeshellarg($env_val ?? ""); + } + $exported_environment = $env_lines ? "\n" . implode("\n", $env_lines) . "\n" : ""; $sh_script = << $file, 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 'output' => $output_filename, 'diff' => $diff_filename, 'info' => $info, - ); + ]; } $diff = empty($diff) ? '' : preg_replace('/\e/', '', $diff); @@ -2884,7 +2874,7 @@ function run_test($php, $file, array $env) /** * @return bool|int */ -function comp_line($l1, $l2, $is_reg) +function comp_line(string $l1, string $l2, bool $is_reg) { if ($is_reg) { return preg_match('/^' . $l1 . '$/s', $l2); @@ -2896,14 +2886,14 @@ function comp_line($l1, $l2, $is_reg) function count_array_diff( array $ar1, array $ar2, - $is_reg, + bool $is_reg, array $w, - $idx1, - $idx2, - $cnt1, - $cnt2, - $steps -) { + int $idx1, + int $idx2, + int $cnt1, + int $cnt2, + int $steps +): int { $equal = 0; while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { @@ -2944,21 +2934,21 @@ function count_array_diff( return $equal; } -function generate_array_diff($ar1, $ar2, $is_reg, array $w) +function generate_array_diff(array $ar1, array $ar2, bool $is_reg, array $w): array { global $context_line_count; $idx1 = 0; $cnt1 = @count($ar1); $idx2 = 0; $cnt2 = @count($ar2); - $diff = array(); - $old1 = array(); - $old2 = array(); + $diff = []; + $old1 = []; + $old2 = []; $number_len = max(3, strlen((string)max($cnt1 + 1, $cnt2 + 1))); $line_number_spec = '%0' . $number_len . 'd'; /** Mapping from $idx2 to $idx1, including indexes of idx2 that are identical to idx1 as well as entries that don't have matches */ - $mapping = array(); + $mapping = []; while ($idx1 < $cnt1 && $idx2 < $cnt2) { $mapping[$idx2] = $idx1; @@ -2990,7 +2980,7 @@ function generate_array_diff($ar1, $ar2, $is_reg, array $w) $k2 = key($old2); $l2 = -2; $old_k1 = -1; - $add_context_lines = function ($new_k1) use (&$old_k1, &$diff, $w, $context_line_count, $number_len) { + $add_context_lines = function (int $new_k1) use (&$old_k1, &$diff, $w, $context_line_count, $number_len) { if ($old_k1 >= $new_k1 || !$context_line_count) { return; } @@ -3060,7 +3050,7 @@ function generate_array_diff($ar1, $ar2, $is_reg, array $w) return $diff; } -function generate_diff($wanted, $wanted_re, $output) +function generate_diff(string $wanted, $wanted_re, string $output): string { $w = explode("\n", $wanted); $o = explode("\n", $output); @@ -3070,7 +3060,7 @@ function generate_diff($wanted, $wanted_re, $output) return implode(PHP_EOL, $diff); } -function error($message) +function error(string $message) { echo "ERROR: {$message}\n"; exit(1); @@ -3086,7 +3076,7 @@ function settings2array(array $settings, &$ini_settings) if ($name == 'extension' || $name == 'zend_extension') { if (!isset($ini_settings[$name])) { - $ini_settings[$name] = array(); + $ini_settings[$name] = []; } $ini_settings[$name][] = $value; @@ -3097,7 +3087,7 @@ function settings2array(array $settings, &$ini_settings) } } -function settings2params(array $ini_settings) +function settings2params(array $ini_settings): string { $settings = ''; @@ -3132,7 +3122,7 @@ function compute_summary() $n_total = count($test_results); $n_total += $ignored_by_ext; - $sum_results = array( + $sum_results = [ 'PASSED' => 0, 'WARNED' => 0, 'SKIPPED' => 0, @@ -3141,21 +3131,21 @@ function compute_summary() 'LEAKED' => 0, 'XFAILED' => 0, 'XLEAKED' => 0 - ); + ]; foreach ($test_results as $v) { $sum_results[$v]++; } $sum_results['SKIPPED'] += $ignored_by_ext; - $percent_results = array(); + $percent_results = []; foreach ($sum_results as $v => $n) { $percent_results[$v] = (100.0 * $n) / $n_total; } } -function get_summary($show_ext_summary) +function get_summary(bool $show_ext_summary): string { global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind; @@ -3221,7 +3211,7 @@ function get_summary($show_ext_summary) $failed_test_summary = ''; if (count($PHP_FAILED_TESTS['SLOW'])) { - usort($PHP_FAILED_TESTS['SLOW'], function (array $a, array $b) { + usort($PHP_FAILED_TESTS['SLOW'], function (array $a, array $b): int { return $a['info'] < $b['info'] ? 1 : -1; }); @@ -3333,7 +3323,7 @@ function show_summary() echo get_summary(true); } -function show_redirect_start($tests, $tested, $tested_file) +function show_redirect_start(string $tests, string $tested, string $tested_file) { global $SHOW_ONLY_GROUPS; @@ -3344,7 +3334,7 @@ function show_redirect_start($tests, $tested, $tested_file) } } -function show_redirect_ends($tests, $tested, $tested_file) +function show_redirect_ends(string $tests, string $tested, string $tested_file) { global $SHOW_ONLY_GROUPS; @@ -3355,7 +3345,7 @@ function show_redirect_ends($tests, $tested, $tested_file) } } -function show_test($test_idx, $shortname) +function show_test(int $test_idx, string $shortname) { global $test_cnt; global $line_length; @@ -3378,7 +3368,7 @@ function clear_show_test() } } -function parse_conflicts($text) +function parse_conflicts(string $text): array { // Strip comments $text = preg_replace('/#.*/', '', $text); @@ -3386,11 +3376,11 @@ function parse_conflicts($text) } function show_result( - $result, - $tested, - $tested_file, - $extra = '', - array $temp_filenames = null + string $result, + string $tested, + string $tested_file, + string $extra = '', + $temp_filenames = null ) { global $SHOW_ONLY_GROUPS, $colorize; @@ -3399,14 +3389,15 @@ function show_result( /* Use ANSI escape codes for coloring test result */ switch ( $result ) { case 'PASS': // Light Green - $color = "\x1B[1;32m{$result}\x1B[0m"; break; + $color = "\e[1;32m{$result}\e[0m"; break; case 'FAIL': case 'BORK': case 'LEAK': + case 'LEAK&FAIL': // Light Red - $color = "\x1B[1;31m{$result}\x1B[0m"; break; + $color = "\e[1;31m{$result}\e[0m"; break; default: // Yellow - $color = "\x1B[1;33m{$result}\x1B[0m"; break; + $color = "\e[1;33m{$result}\e[0m"; break; } echo "$color $tested [$tested_file] $extra\n"; @@ -3433,7 +3424,7 @@ function junit_init() } elseif (!$fp = fopen($JUNIT, 'w')) { error("Failed to open $JUNIT for writing."); } - $GLOBALS['JUNIT'] = array( + $GLOBALS['JUNIT'] = [ 'fp' => $fp, 'name' => 'PHP', 'test_total' => 0, @@ -3443,9 +3434,9 @@ function junit_init() 'test_skip' => 0, 'test_warn' => 0, 'execution_time' => 0, - 'suites' => array(), - 'files' => array() - ); + 'suites' => [], + 'files' => [] + ]; } function junit_save_xml() @@ -3470,7 +3461,7 @@ function junit_save_xml() fwrite($JUNIT['fp'], $xml); } -function junit_get_suite_xml($suite_name = '') +function junit_get_suite_xml(string $suite_name = ''): string { global $JUNIT; @@ -3499,7 +3490,7 @@ function junit_get_suite_xml($suite_name = '') return $result; } -function junit_enabled() +function junit_enabled(): bool { global $JUNIT; return !empty($JUNIT); @@ -3510,11 +3501,11 @@ function junit_enabled() */ function junit_mark_test_as( $type, - $file_name, - $test_name, + string $file_name, + string $test_name, $time = null, - $message = '', - $details = '' + string $message = '', + string $details = '' ) { global $JUNIT; if (!junit_enabled()) { @@ -3525,11 +3516,11 @@ function junit_mark_test_as( junit_suite_record($suite, 'test_total'); - $time = empty($time) ? junit_get_timer($file_name) : $time; + $time = $time ?? junit_get_timer($file_name); junit_suite_record($suite, 'execution_time', $time); $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); - $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function (array $c) { + $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function (array $c): string { return sprintf('[[0x%02x]]', ord($c[0])); }, $escaped_details); $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); @@ -3539,7 +3530,7 @@ function junit_mark_test_as( if (is_array($type)) { $output_type = $type[0] . 'ED'; - $temp = array_intersect(array('XFAIL', 'XLEAK', 'FAIL', 'WARN'), $type); + $temp = array_intersect(['XFAIL', 'XLEAK', 'FAIL', 'WARN'], $type); $type = reset($temp); } else { $output_type = $type . 'ED'; @@ -3567,7 +3558,7 @@ function junit_mark_test_as( $JUNIT['files'][$file_name]['xml'] .= "\n"; } -function junit_suite_record($suite, $param, $value = 1) +function junit_suite_record(string $suite, string $param, float $value = 1) { global $JUNIT; @@ -3575,7 +3566,7 @@ function junit_suite_record($suite, $param, $value = 1) $JUNIT['suites'][$suite][$param] += $value; } -function junit_get_timer($file_name) +function junit_get_timer(string $file_name): float { global $JUNIT; if (!junit_enabled()) { @@ -3589,7 +3580,7 @@ function junit_get_timer($file_name) return 0; } -function junit_start_timer($file_name) +function junit_start_timer(string $file_name) { global $JUNIT; if (!junit_enabled()) { @@ -3605,12 +3596,12 @@ function junit_start_timer($file_name) } } -function junit_get_suitename_for($file_name) +function junit_get_suitename_for(string $file_name): string { return junit_path_to_classname(dirname($file_name)); } -function junit_path_to_classname($file_name) +function junit_path_to_classname(string $file_name): string { global $JUNIT; @@ -3619,7 +3610,7 @@ function junit_path_to_classname($file_name) } $ret = $JUNIT['name']; - $_tmp = array(); + $_tmp = []; // lookup whether we're in the PHP source checkout $max = 5; @@ -3643,10 +3634,10 @@ function junit_path_to_classname($file_name) return $ret; } - return $JUNIT['name'] . '.' . str_replace(array(DIRECTORY_SEPARATOR, '-'), '.', $file_name); + return $JUNIT['name'] . '.' . str_replace([DIRECTORY_SEPARATOR, '-'], '.', $file_name); } -function junit_init_suite($suite_name) +function junit_init_suite(string $suite_name) { global $JUNIT; if (!junit_enabled()) { @@ -3657,7 +3648,7 @@ function junit_init_suite($suite_name) return; } - $JUNIT['suites'][$suite_name] = array( + $JUNIT['suites'][$suite_name] = [ 'name' => $suite_name, 'test_total' => 0, 'test_pass' => 0, @@ -3665,12 +3656,12 @@ function junit_init_suite($suite_name) 'test_error' => 0, 'test_skip' => 0, 'test_warn' => 0, - 'files' => array(), + 'files' => [], 'execution_time' => 0, - ); + ]; } -function junit_finish_timer($file_name) +function junit_finish_timer(string $file_name) { global $JUNIT; if (!junit_enabled()) { @@ -3727,17 +3718,17 @@ class RuntestsValgrind protected $version_3_8_0 = false; protected $tool = null; - public function getVersion() + public function getVersion(): string { return $this->version; } - public function getHeader() + public function getHeader(): string { return $this->header; } - public function __construct(array $environment, $tool = 'memcheck') + public function __construct(array $environment, string $tool = 'memcheck') { $this->tool = $tool; $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment); @@ -3757,14 +3748,9 @@ public function __construct(array $environment, $tool = 'memcheck') $this->version_3_8_0 = version_compare($version, '3.8.0', '>='); } - public function wrapCommand($cmd, $memcheck_filename, $check_all) - { - $supp_file = INIT_DIR . "/valgrind.supp"; - $vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes --leak-check=full " . - "--gen-suppressions=all --num-callers=16 --run-libc-freeres=no"; - if (file_exists($supp_file)) { - $vcmd .= " --suppressions='$supp_file'"; - } + public function wrapCommand(string $cmd, string $memcheck_filename, bool $check_all): string + { + $vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes"; if ($check_all) { $vcmd .= ' --smc-check=all'; } diff --git a/test_funcs.sh b/test_funcs.sh deleted file mode 100644 index 2d135920..00000000 --- a/test_funcs.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -e - -JOBS=3 - -function package_name { - local readonly version=$1 zts=$2 - local zts_suffix='' - if [[ $zts = 'true' ]]; then - zts_suffix='-zts' - fi - - echo "php-${version}${zts_suffix}-bare-dbg" -} - -function prefix { - local readonly version=$1 zts=$2 - - echo "/opt/$(package_name $version $zts)" -} - -function build_ext { - local readonly version=$1 zts=$2 coverage=$3 - local readonly prefix=$(prefix $1 $2) - local cflags= cxxflags= ldflags= - "$prefix"/bin/phpize - if [[ $coverage == true ]]; then - cflags=--coverage - cxxflags=--coverage - ldflags=--coverage - fi - CFLAGS="$cflags" CXXFLAGS="$cxxflags" LDFLAGS="$ldflags" \ - ./configure --with-php-config="$prefix/bin/php-config" - make -j $JOBS -} - -function do_tests { - local readonly prefix=$1 - local found_leaks= dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local ret=0 - sed -i s/-@if/@if/ Makefile - TEST_PHP_EXECUTABLE="$prefix/bin/php" \ - TEST_PHP_JUNIT=report.xml \ - REPORT_EXIT_STATUS=1 \ - NO_INTERACTION=1 \ - TESTS="--set-timeout 300 --show-diff $RUN_TESTS_FLAGS" make test \ - || ret=$? - found_leaks=$(find tests -name '*.mem' | wc -l) - if [[ $found_leaks -gt 0 ]]; then - echo "Found $found_leaks leaks. Failing." - find tests -name "*.mem" -print -exec cat {} \; - fi - return $ret -} - -function install_php { - local readonly version=$1 zts=$2 - local readonly url="$MIRROR/php-$version.tar.gz" - - sudo apt-get install -y gnupg - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 5D98E7264E3F3D89463B314B12229434A9F003C9 - echo deb [arch=amd64] http://artefacto-test.s3.amazonaws.com/php-bare-dbg bionic main | sudo tee -a /etc/apt/sources.list - sudo apt-get update - sudo apt-get install -y $(package_name $version $zts) -} - -function run_tests { - set -e - set -o pipefail - do_tests "$(prefix $1 $2)" -} diff --git a/tests/001.phpt b/tests/001.phpt index 5ca9acd7..a0ab4720 100644 --- a/tests/001.phpt +++ b/tests/001.phpt @@ -1,7 +1,5 @@ --TEST-- rar_open() function ---SKIPIF-- - --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- + --FILE-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- should fail (no password):\n"; diff --git a/tests/025.phpt b/tests/025.phpt index cf789671..ee70c062 100644 --- a/tests/025.phpt +++ b/tests/025.phpt @@ -1,7 +1,5 @@ --TEST-- rar_open()/RarEntry::extract() (headers level password) ---SKIPIF-- - --FILE-- --FILE-- should fail (no password):\n"; diff --git a/tests/027.phpt b/tests/027.phpt index 8f104c64..ab7133bb 100644 --- a/tests/027.phpt +++ b/tests/027.phpt @@ -1,7 +1,5 @@ --TEST-- RarEntry::getStream() with Linux directories and links ---SKIPIF-- - --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- $rarE) { } echo "Done.\n"; --EXPECTF-- -Fatal error: main(): The archive is already closed, cannot give an iterator in %s on line %d +Fatal error: main(): The archive is already closed, cannot give an iterator in %s on line %d%A diff --git a/tests/039.phpt b/tests/039.phpt index 2fa6b011..1e1f3c87 100644 --- a/tests/039.phpt +++ b/tests/039.phpt @@ -1,7 +1,5 @@ --TEST-- Access RAR archive with missing volumes ---SKIPIF-- - --FILE-- + --FILE-- + --FILE-- NM) +RarArchive::open() volume callback long return (case MAXPATHLEN > MAXPATHSIZE) --SKIPIF-- 2048)) +if (!(PHP_MAXPATHLEN > MAXPATHSIZE)) die("skip test is for systems where MAXPATHLEN > 2048"); -$rp = dirname(__FILE__) . "/" . str_repeat("a", 2048); +$rp = dirname(__FILE__) . "/" . str_repeat("a", MAXPATHSIZE); if (strlen(dirname(__FILE__) > PHP_MAXPATHLEN - 1)) die("skip current directory is too deep."); --FILE-- @@ -18,7 +18,7 @@ if (!defined("PHP_MAXPATHLEN")) chdir(dirname(__FILE__)); $fn = dirname(__FILE__) . '/multi_broken.part1.rar'; -function testA($vol) { if ($vol[0] != 'a') return str_repeat("a", 2048); } +function testA($vol) { if ($vol[0] != 'a') return str_repeat("a", MAXPATHSIZE); } $rar = RarArchive::open($fn, null, 'testA'); $rar->getEntries(); diff --git a/tests/050.phpt b/tests/050.phpt index 8b0c4e7a..49ae467d 100644 --- a/tests/050.phpt +++ b/tests/050.phpt @@ -1,8 +1,5 @@ --TEST-- Stream wrapper basic test ---SKIPIF-- - --CLEAN-- @unlink(dirname(__FILE__).'/extract_temp'); --FILE-- diff --git a/tests/067.phpt b/tests/067.phpt index cc28a583..b535b4f8 100644 --- a/tests/067.phpt +++ b/tests/067.phpt @@ -1,7 +1,7 @@ --TEST-- RarEntry::extract() process extended (Windows) --SKIPIF-- -= 70000) die("skip for PHP 5.x"); -if (key_exists('USE_ZEND_ALLOC', $_ENV) && PHP_VERSION_ID < 70000) die('skip do not use with valgrind in PHP <7'); ---FILE-- -= 70000) die("skip for PHP 5.x"); -if (key_exists('USE_ZEND_ALLOC', $_ENV) && PHP_VERSION_ID < 70000) die('skip do not use with valgrind in PHP <7'); ---FILE-- - --FILE-- --FILE-- --FILE-- --FILE-- --CLEAN-- --FILE-- --CLEAN-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- @@ -16,4 +15,4 @@ $a[0] = "jjj"; echo "\n"; echo "Done.\n"; --EXPECTF-- -Fatal error: main(): A RarArchive object is not writable in %s on line %d +Fatal error: main(): A RarArchive object is not writable in %s on line %d%A diff --git a/tests/087.phpt b/tests/087.phpt index 3b2c48f8..27f555bd 100644 --- a/tests/087.phpt +++ b/tests/087.phpt @@ -1,7 +1,5 @@ --TEST-- RarArchive read_property gives a fatal error on a write context ---SKIPIF-- - --FILE-- --FILE-- @@ -16,4 +15,4 @@ $a[0] = "hhh"; echo "\n"; echo "Done.\n"; --EXPECTF-- -Fatal error: main(): A RarArchive object is not writable in %s on line %d +Fatal error: main(): A RarArchive object is not writable in %s on line %d%A diff --git a/tests/089.phpt b/tests/089.phpt index 3f576153..0a8ea981 100644 --- a/tests/089.phpt +++ b/tests/089.phpt @@ -1,7 +1,5 @@ --TEST-- RarArchive unset_property gives a fatal error ---SKIPIF-- - --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --CLEAN-- --CLEAN-- --FILE-- --FILE-- = 7"); --FILE-- = 7"); --FILE-- = 7"); --FILE-- = 70000) die("skip for PHP 5.x"); ---FILE-- -getEntries(); -echo "Never reached.\n"; -?> ---EXPECTF-- -Fatal error: Trying to clone an uncloneable object of class RarArchive in %s on line %d diff --git a/tests/106.phpt b/tests/106.phpt index 2fe7a1f6..11b32cdb 100644 --- a/tests/106.phpt +++ b/tests/106.phpt @@ -1,8 +1,5 @@ --TEST-- Stat times don't depend on timezone (cf. 056.phpt) ---SKIPIF-- - --FILE-- diff --git "a/tests/75557\321\202\320\265\321\201\321\202.rar" "b/tests/75557\321\202\320\265\321\201\321\202.rar" new file mode 100644 index 00000000..4359daba Binary files /dev/null and "b/tests/75557\321\202\320\265\321\201\321\202.rar" differ diff --git a/tests/bug75557.phpt b/tests/bug75557.phpt new file mode 100644 index 00000000..596a1d81 --- /dev/null +++ b/tests/bug75557.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug #75557 (Error opening archive w/non-english chars in path) +--SKIPIF-- + +--FILE-- +getEntries())); +?> +--EXPECT-- +int(1) diff --git a/unrar/.clang-format b/unrar/.clang-format new file mode 100644 index 00000000..58544b34 --- /dev/null +++ b/unrar/.clang-format @@ -0,0 +1,3 @@ +--- +# Disable clang-format for vendored unrar sources. +DisableFormat: true diff --git a/unrar/UnRAR.vcxproj b/unrar/UnRAR.vcxproj index 512bcf15..ac0878f2 100644 --- a/unrar/UnRAR.vcxproj +++ b/unrar/UnRAR.vcxproj @@ -238,8 +238,10 @@ + + diff --git a/unrar/UnRARDll.vcxproj b/unrar/UnRARDll.vcxproj index ec5c17b0..17ea1b4f 100644 --- a/unrar/UnRARDll.vcxproj +++ b/unrar/UnRARDll.vcxproj @@ -138,7 +138,7 @@ Sync EnableFastChecks MultiThreadedDebug - 4Bytes + Default false Use rar.hpp @@ -168,7 +168,7 @@ Sync EnableFastChecks MultiThreadedDebug - 4Bytes + Default false Use rar.hpp @@ -198,7 +198,7 @@ false Sync MultiThreaded - 4Bytes + Default true true NoExtensions @@ -239,7 +239,7 @@ false Sync MultiThreaded - 4Bytes + Default true true false @@ -274,7 +274,7 @@ false Sync MultiThreaded - 4Bytes + Default true true NoExtensions @@ -315,7 +315,7 @@ false Sync MultiThreaded - 4Bytes + Default true true false @@ -374,7 +374,9 @@ + + diff --git a/unrar/acknow.txt b/unrar/acknow.txt index a68b6727..ec2c2c7c 100644 --- a/unrar/acknow.txt +++ b/unrar/acknow.txt @@ -7,51 +7,18 @@ for samples and ideas allowed to make Reed-Solomon coding more efficient. -* RAR text compression algorithm is based on Dmitry Shkarin PPMII +* RAR4 text compression algorithm is based on Dmitry Shkarin PPMII and Dmitry Subbotin carryless rangecoder public domain source code. - You may find it in ftp.elf.stuba.sk/pub/pc/pack. + You can find it in ftp.elf.stuba.sk/pub/pc/pack. -* RAR encryption includes parts of code from Szymon Stefanek - and Brian Gladman AES implementations also as Steve Reid SHA-1 source. +* RAR encryption includes parts of public domain code + from Szymon Stefanek AES and Steve Reid SHA-1 implementations. - --------------------------------------------------------------------------- - Copyright (c) 2002, Dr Brian Gladman < >, Worcester, UK. - All rights reserved. +* With exception of SFX modules, RAR uses CRC32 function based + on Intel Slicing-by-8 algorithm. Original Intel Slicing-by-8 code + is available here: - LICENSE TERMS - - The free distribution and use of this software in both source and binary - form is allowed (with or without changes) provided that: - - 1. distributions of this source code include the above copyright - notice, this list of conditions and the following disclaimer; - - 2. distributions in binary form include the above copyright - notice, this list of conditions and the following disclaimer - in the documentation and/or other associated materials; - - 3. the copyright holder's name is not used to endorse products - built using this software without specific written permission. - - ALTERNATIVELY, provided that this notice is retained in full, this product - may be distributed under the terms of the GNU General Public License (GPL), - in which case the provisions of the GPL apply INSTEAD OF those given above. - - DISCLAIMER - - This software is provided 'as is' with no explicit or implied warranties - in respect of its properties, including, but not limited to, correctness - and/or fitness for purpose. - --------------------------------------------------------------------------- - - Source code of this package also as other cryptographic technology - and computing project related links are available on Brian Gladman's - web site: http://www.gladman.me.uk - -* RAR uses CRC32 function based on Intel Slicing-by-8 algorithm. - Original Intel Slicing-by-8 code is available here: - - http://sourceforge.net/projects/slicing-by-8/ + https://sourceforge.net/projects/slicing-by-8/ Original Intel Slicing-by-8 code is licensed under BSD License available at http://www.opensource.org/licenses/bsd-license.html diff --git a/unrar/arccmt.cpp b/unrar/arccmt.cpp index 3e6135cd..40981b93 100644 --- a/unrar/arccmt.cpp +++ b/unrar/arccmt.cpp @@ -1,6 +1,6 @@ static bool IsAnsiEscComment(const wchar *Data,size_t Size); -bool Archive::GetComment(Array *CmtData) +bool Archive::GetComment(std::wstring &CmtData) { if (!MainComment) return false; @@ -11,7 +11,7 @@ bool Archive::GetComment(Array *CmtData) } -bool Archive::DoGetComment(Array *CmtData) +bool Archive::DoGetComment(std::wstring &CmtData) { #ifndef SFX_MODULE uint CmtLength; @@ -36,7 +36,12 @@ bool Archive::DoGetComment(Array *CmtData) { // Current (RAR 3.0+) version of archive comment. Seek(GetStartPos(),SEEK_SET); - return SearchSubBlock(SUBHEAD_TYPE_CMT)!=0 && ReadCommentData(CmtData); + if (SearchSubBlock(SUBHEAD_TYPE_CMT)!=0) + if (ReadCommentData(CmtData)) + return true; + else + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; } #ifndef SFX_MODULE // Old style (RAR 2.9) comment header embedded into the main @@ -101,23 +106,21 @@ bool Archive::DoGetComment(Array *CmtData) // 4x memory for OEM to UTF-8 output here. OemToCharBuffA((char *)UnpData,(char *)UnpData,(DWORD)UnpDataSize); #endif - CmtData->Alloc(UnpDataSize+1); - memset(CmtData->Addr(0),0,CmtData->Size()*sizeof(wchar)); - CharToWide((char *)UnpData,CmtData->Addr(0),CmtData->Size()); - CmtData->Alloc(wcslen(CmtData->Addr(0))); + std::string UnpStr((char*)UnpData,UnpDataSize); + CharToWide(UnpStr,CmtData); + } } } - } else { if (CmtLength==0) return false; - Array CmtRaw(CmtLength); - int ReadSize=Read(&CmtRaw[0],CmtLength); + std::vector CmtRaw(CmtLength); + int ReadSize=Read(CmtRaw.data(),CmtLength); if (ReadSize>=0 && (uint)ReadSize *CmtData) uiMsg(UIERROR_CMTBROKEN,FileName); return false; } - CmtData->Alloc(CmtLength+1); - CmtRaw.Push(0); +// CmtData.resize(CmtLength+1); + CmtRaw.push_back(0); #ifdef _WIN_ALL // If we ever decide to extend it to Android, we'll need to alloc // 4x memory for OEM to UTF-8 output here. - OemToCharA((char *)&CmtRaw[0],(char *)&CmtRaw[0]); + OemToCharA((char *)CmtRaw.data(),(char *)CmtRaw.data()); #endif - CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); - CmtData->Alloc(wcslen(CmtData->Addr(0))); + CharToWide((const char *)CmtRaw.data(),CmtData); +// CmtData->resize(wcslen(CmtData->data())); } #endif - return CmtData->Size() > 0; + return CmtData.size() > 0; } -bool Archive::ReadCommentData(Array *CmtData) +bool Archive::ReadCommentData(std::wstring &CmtData) { - Array CmtRaw; + std::vector CmtRaw; if (!ReadSubData(&CmtRaw,NULL,false)) return false; - size_t CmtSize=CmtRaw.Size(); - CmtRaw.Push(0); - CmtData->Alloc(CmtSize+1); + size_t CmtSize=CmtRaw.size(); + CmtRaw.push_back(0); +// CmtData->resize(CmtSize+1); if (Format==RARFMT50) - UtfToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); + UtfToWide((char *)CmtRaw.data(),CmtData); else if ((SubHead.SubFlags & SUBHEAD_FLAGS_CMT_UNICODE)!=0) { - RawToWide(&CmtRaw[0],CmtData->Addr(0),CmtSize/2); - (*CmtData)[CmtSize/2]=0; - + CmtData=RawToWide(CmtRaw); } else { - CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); + CharToWide((const char *)CmtRaw.data(),CmtData); } - CmtData->Alloc(wcslen(CmtData->Addr(0))); // Set buffer size to actual comment length. +// CmtData->resize(wcslen(CmtData->data())); // Set buffer size to actual comment length. return true; } @@ -170,15 +171,16 @@ void Archive::ViewComment() { if (Cmd->DisableComment) return; - Array CmtBuf; - if (GetComment(&CmtBuf)) // In GUI too, so "Test" command detects broken comments. + std::wstring CmtBuf; + if (GetComment(CmtBuf)) // In GUI too, so "Test" command detects broken comments. { - size_t CmtSize=CmtBuf.Size(); - wchar *ChPtr=wcschr(&CmtBuf[0],0x1A); - if (ChPtr!=NULL) - CmtSize=ChPtr-&CmtBuf[0]; - mprintf(L"\n"); - OutComment(&CmtBuf[0],CmtSize); + size_t CmtSize=CmtBuf.size(); + auto EndPos=CmtBuf.find(0x1A); + if (EndPos!=std::wstring::npos) + CmtSize=EndPos; + mprintf(St(MArcComment)); + mprintf(L":\n"); + OutComment(CmtBuf); } } diff --git a/unrar/archive.cpp b/unrar/archive.cpp index 8c5a1da8..4fc2a893 100644 --- a/unrar/archive.cpp +++ b/unrar/archive.cpp @@ -3,15 +3,15 @@ #include "arccmt.cpp" -Archive::Archive(RAROptions *InitCmd) +Archive::Archive(CommandData *InitCmd) { Cmd=NULL; // Just in case we'll have an exception in 'new' below. DummyCmd=(InitCmd==NULL); - Cmd=DummyCmd ? (new RAROptions):InitCmd; + Cmd=DummyCmd ? (new CommandData):InitCmd; OpenShared=Cmd->OpenShared; - Format=RARFMT15; + Format=RARFMT_NONE; Solid=false; Volume=false; MainComment=false; @@ -26,20 +26,21 @@ Archive::Archive(RAROptions *InitCmd) FailedHeaderDecryption=false; BrokenHeader=false; LastReadBlock=0; + CurHeaderType=HEAD_UNKNOWN; CurBlockPos=0; NextBlockPos=0; + RecoveryPercent=-1; - memset(&MainHead,0,sizeof(MainHead)); - memset(&CryptHead,0,sizeof(CryptHead)); - memset(&EndArcHead,0,sizeof(EndArcHead)); + MainHead.Reset(); + CryptHead={}; + EndArcHead.Reset(); VolNumber=0; VolWrite=0; AddingFilesSize=0; AddingHeadersSize=0; - *FirstVolumeName=0; Splitting=false; NewArchive=false; @@ -68,13 +69,13 @@ void Archive::CheckArc(bool EnableBroken) // password is incorrect. if (!FailedHeaderDecryption) uiMsg(UIERROR_BADARCHIVE,FileName); - ErrHandler.Exit(RARX_FATAL); + ErrHandler.Exit(RARX_BADARC); } } #if !defined(SFX_MODULE) -void Archive::CheckOpen(const wchar *Name) +void Archive::CheckOpen(const std::wstring &Name) { TOpen(Name); CheckArc(false); @@ -82,7 +83,7 @@ void Archive::CheckOpen(const wchar *Name) #endif -bool Archive::WCheckOpen(const wchar *Name) +bool Archive::WCheckOpen(const std::wstring &Name) { if (!WOpen(Name)) return false; @@ -110,9 +111,11 @@ RARFORMAT Archive::IsSignature(const byte *D,size_t Size) // We check the last signature byte, so we can return a sensible // warning in case we'll want to change the archive format // sometimes in the future. +#ifndef SFX_MODULE if (D[6]==0) Type=RARFMT15; else +#endif if (D[6]==1) Type=RARFMT50; else @@ -148,9 +151,9 @@ bool Archive::IsArchive(bool EnableBroken) } else { - Array Buffer(MAXSFXSIZE); + std::vector Buffer(MAXSFXSIZE); long CurPos=(long)Tell(); - int ReadSize=Read(&Buffer[0],Buffer.Size()-16); + int ReadSize=Read(Buffer.data(),Buffer.size()-16); for (int I=0;I0x10000000000ULL) + return 0; + uint64 Pow2=0x20000; // Power of 2 dictionary size. + for (;2*Pow2<=Size;Pow2*=2) + Flags+=FCI_DICT_BIT0; + if (Size==Pow2) + return Size; // If 'Size' is the power of 2, return it as is. + + // Get the number of Pow2/32 to add to Pow2 for nearest value not exceeding 'Size'. + uint64 Fraction=(Size-Pow2)/(Pow2/32); + Flags+=(uint)Fraction*FCI_DICT_FRACT0; + return Pow2+Fraction*(Pow2/32); +} diff --git a/unrar/archive.hpp b/unrar/archive.hpp index e02bd623..f4df7e9d 100644 --- a/unrar/archive.hpp +++ b/unrar/archive.hpp @@ -27,26 +27,27 @@ class Archive:public File { private: void UpdateLatestTime(FileHeader *CurBlock); - void ConvertNameCase(wchar *Name); + void ConvertNameCase(std::wstring &Name); void ConvertFileHeader(FileHeader *hd); size_t ReadHeader14(); size_t ReadHeader15(); size_t ReadHeader50(); - void ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb); - void RequestArcPassword(); + void ProcessExtra50(RawRead *Raw,size_t ExtraSize,const BaseBlock *bb); + void RequestArcPassword(RarCheckPassword *SelPwd); void UnexpEndArcMsg(); void BrokenHeaderMsg(); - void UnkEncVerMsg(const wchar *Name,const wchar *Info); - bool DoGetComment(Array *CmtData); - bool ReadCommentData(Array *CmtData); + void UnkEncVerMsg(const std::wstring &Name,const std::wstring &Info); + bool DoGetComment(std::wstring &CmtData); + bool ReadCommentData(std::wstring &CmtData); #if !defined(RAR_NOCRYPT) CryptData HeadersCrypt; #endif ComprDataIO SubDataIO; bool DummyCmd; - RAROptions *Cmd; + CommandData *Cmd; + int RecoveryPercent; RarTime LatestTime; int LastReadBlock; @@ -58,18 +59,19 @@ class Archive:public File bool ProhibitQOpen; #endif public: - Archive(RAROptions *InitCmd=NULL); + Archive(CommandData *InitCmd=nullptr); ~Archive(); static RARFORMAT IsSignature(const byte *D,size_t Size); bool IsArchive(bool EnableBroken); size_t SearchBlock(HEADER_TYPE HeaderType); size_t SearchSubBlock(const wchar *Type); size_t SearchRR(); + int GetRecoveryPercent() {return RecoveryPercent;} size_t ReadHeader(); void CheckArc(bool EnableBroken); - void CheckOpen(const wchar *Name); - bool WCheckOpen(const wchar *Name); - bool GetComment(Array *CmtData); + void CheckOpen(const std::wstring &Name); + bool WCheckOpen(const std::wstring &Name); + bool GetComment(std::wstring &CmtData); void ViewComment(); void SetLatestTime(RarTime *NewTime); void SeekToNext(); @@ -79,23 +81,25 @@ class Archive:public File void VolSubtractHeaderSize(size_t SubSize); uint FullHeaderSize(size_t Size); int64 GetStartPos(); - void AddSubData(byte *SrcData,uint64 DataSize,File *SrcFile, + void AddSubData(const byte *SrcData,uint64 DataSize,File *SrcFile, const wchar *Name,uint Flags); - bool ReadSubData(Array *UnpData,File *DestFile,bool TestMode); + bool ReadSubData(std::vector *UnpData,File *DestFile,bool TestMode); HEADER_TYPE GetHeaderType() {return CurHeaderType;} - RAROptions* GetRAROptions() {return Cmd;} + CommandData* GetCommandData() {return Cmd;} void SetSilentOpen(bool Mode) {SilentOpen=Mode;} -#if 0 - void GetRecoveryInfo(bool Required,int64 *Size,int *Percent); -#endif #ifdef USE_QOPEN - bool Open(const wchar *Name,uint Mode=FMF_READ); - int Read(void *Data,size_t Size); - void Seek(int64 Offset,int Method); - int64 Tell(); + bool Open(const std::wstring &Name,uint Mode=FMF_READ) override; + int Read(void *Data,size_t Size) override; + void Seek(int64 Offset,int Method) override; + int64 Tell() override; void QOpenUnload() {QOpen.Unload();} void SetProhibitQOpen(bool Mode) {ProhibitQOpen=Mode;} #endif + static uint64 GetWinSize(uint64 Size,uint &Flags); + + // Needed to see wstring based Open from File. Otherwise compiler finds + // Open in Archive and doesn't check the base class overloads. + using File::Open; BaseBlock ShortBlock; MarkHeader MarkHead; @@ -107,7 +111,6 @@ class Archive:public File FileHeader SubHead; CommentHeader CommHead; ProtectHeader ProtectHead; - UnixOwnersHeader UOHead; EAHeader EAHead; StreamHeader StreamHead; @@ -136,12 +139,19 @@ class Archive:public File uint VolNumber; int64 VolWrite; + + // Total size of files adding to archive. Might also include the size of + // files repacked in solid archive. uint64 AddingFilesSize; + uint64 AddingHeadersSize; bool NewArchive; - wchar FirstVolumeName[NM]; + std::wstring FirstVolumeName; +#ifdef PROPAGATE_MOTW + MarkOfTheWeb Motw; +#endif }; #endif diff --git a/unrar/arcread.cpp b/unrar/arcread.cpp index 4ef64871..e9448e23 100644 --- a/unrar/arcread.cpp +++ b/unrar/arcread.cpp @@ -20,10 +20,10 @@ size_t Archive::ReadHeader() case RARFMT14: ReadSize=ReadHeader14(); break; -#endif case RARFMT15: ReadSize=ReadHeader15(); break; +#endif case RARFMT50: ReadSize=ReadHeader50(); break; @@ -100,9 +100,15 @@ void Archive::UnexpEndArcMsg() // If block positions are equal to file size, this is not an error. // It can happen when we reached the end of older RAR 1.5 archive, // which did not have the end of archive block. + // We can't replace this check by checking that read size is exactly 0 + // in the beginning of file header, because in this case the read position + // still can be beyond the end of archive. if (CurBlockPos!=ArcSize || NextBlockPos!=ArcSize) { uiMsg(UIERROR_UNEXPEOF,FileName); + if (CurHeaderType!=HEAD_FILE && CurHeaderType!=HEAD_UNKNOWN) + uiMsg(UIERROR_TRUNCSERVICE,FileName,SubHead.FileName); + ErrHandler.SetErrorCode(RARX_WARNING); } } @@ -116,10 +122,10 @@ void Archive::BrokenHeaderMsg() } -void Archive::UnkEncVerMsg(const wchar *Name,const wchar *Info) +void Archive::UnkEncVerMsg(const std::wstring &Name,const std::wstring &Info) { uiMsg(UIERROR_UNKNOWNENCMETHOD,FileName,Name,Info); - ErrHandler.SetErrorCode(RARX_WARNING); + ErrHandler.SetErrorCode(RARX_FATAL); } @@ -134,6 +140,7 @@ inline int64 SafeAdd(int64 v1,int64 v2,int64 f) } +#ifndef SFX_MODULE size_t Archive::ReadHeader15() { RawRead Raw(this); @@ -142,10 +149,10 @@ size_t Archive::ReadHeader15() if (Decrypt) { -#ifdef RAR_NOCRYPT // For rarext.dll and unrar_nocrypt.dll. +#ifdef RAR_NOCRYPT // For rarext.dll, Setup.SFX and unrar_nocrypt.dll. return 0; #else - RequestArcPassword(); + RequestArcPassword(NULL); byte Salt[SIZE_SALT30]; if (Read(Salt,SIZE_SALT30)!=SIZE_SALT30) @@ -219,7 +226,7 @@ size_t Archive::ReadHeader15() { case HEAD_MAIN: MainHead.Reset(); - *(BaseBlock *)&MainHead=ShortBlock; + MainHead.SetBaseBlock(ShortBlock); MainHead.HighPosAV=Raw.Get2(); MainHead.PosAV=Raw.Get4(); @@ -245,13 +252,17 @@ size_t Archive::ReadHeader15() FileHeader *hd=FileBlock ? &FileHead:&SubHead; hd->Reset(); - *(BaseBlock *)hd=ShortBlock; + hd->SetBaseBlock(ShortBlock); hd->SplitBefore=(hd->Flags & LHD_SPLIT_BEFORE)!=0; hd->SplitAfter=(hd->Flags & LHD_SPLIT_AFTER)!=0; hd->Encrypted=(hd->Flags & LHD_PASSWORD)!=0; hd->SaltSet=(hd->Flags & LHD_SALT)!=0; + + // RAR versions earlier than 2.0 do not set the solid flag + // in file header. They use only a global solid archive flag. hd->Solid=FileBlock && (hd->Flags & LHD_SOLID)!=0; + hd->SubBlock=!FileBlock && (hd->Flags & LHD_SOLID)!=0; hd->Dir=(hd->Flags & LHD_WINDOWMASK)==LHD_DIRECTORY; hd->WinSize=hd->Dir ? 0:0x10000<<((hd->Flags & LHD_WINDOWMASK)>>5); @@ -300,7 +311,7 @@ size_t Archive::ReadHeader15() if (hd->HostOS==HOST_UNIX && (hd->FileAttr & 0xF000)==0xA000) { hd->RedirType=FSREDIR_UNIXSYMLINK; - *hd->RedirName=0; + hd->RedirName.clear(); } hd->Inherited=!FileBlock && (hd->SubFlags & SUBHEAD_FLAGS_INHERITED)!=0; @@ -327,27 +338,26 @@ size_t Archive::ReadHeader15() if (hd->UnknownUnpSize) hd->UnpSize=INT64NDF; - char FileName[NM*4]; - size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); - Raw.GetB((byte *)FileName,ReadNameSize); - FileName[ReadNameSize]=0; + size_t ReadNameSize=Min(NameSize,MAXPATHSIZE); + std::string FileName(ReadNameSize,0); + Raw.GetB((byte *)&FileName[0],ReadNameSize); if (FileBlock) { - *hd->FileName=0; + hd->FileName.clear(); if ((hd->Flags & LHD_UNICODE)!=0) { EncodeFileName NameCoder; - size_t Length=strlen(FileName); + size_t Length=strlen(FileName.data()); Length++; if (ReadNameSize>Length) - NameCoder.Decode(FileName,ReadNameSize,(byte *)FileName+Length, - ReadNameSize-Length,hd->FileName, - ASIZE(hd->FileName)); + NameCoder.Decode(FileName.data(),ReadNameSize, + (byte *)&FileName[Length], + ReadNameSize-Length,hd->FileName); } - if (*hd->FileName==0) - ArcCharToWide(FileName,hd->FileName,ASIZE(hd->FileName),ACTW_OEM); + if (hd->FileName.empty()) + ArcCharToWide(FileName.data(),hd->FileName,ACTW_OEM); #ifndef SFX_MODULE ConvertNameCase(hd->FileName); @@ -356,7 +366,7 @@ size_t Archive::ReadHeader15() } else { - CharToWide(FileName,hd->FileName,ASIZE(hd->FileName)); + CharToWide(FileName.data(),hd->FileName); // Calculate the size of optional data. int DataSize=int(hd->HeadSize-NameSize-SIZEOF_FILEHEAD3); @@ -367,14 +377,15 @@ size_t Archive::ReadHeader15() { // Here we read optional additional fields for subheaders. // They are stored after the file name and before salt. - hd->SubData.Alloc(DataSize); - Raw.GetB(&hd->SubData[0],DataSize); + hd->SubData.resize(DataSize); + Raw.GetB(hd->SubData.data(),DataSize); } if (hd->CmpName(SUBHEAD_TYPE_CMT)) MainComment=true; } + if ((hd->Flags & LHD_SALT)!=0) Raw.GetB(hd->Salt,SIZE_SALT30); hd->mtime.SetDos(FileTime); @@ -417,7 +428,7 @@ size_t Archive::ReadHeader15() NextBlockPos=SafeAdd(NextBlockPos,hd->PackSize,0); bool CRCProcessedOnly=hd->CommentInHeader; - ushort HeaderCRC=Raw.GetCRC15(CRCProcessedOnly); + uint HeaderCRC=Raw.GetCRC15(CRCProcessedOnly); if (hd->HeadCRC!=HeaderCRC) { BrokenHeader=true; @@ -434,7 +445,7 @@ size_t Archive::ReadHeader15() } break; case HEAD_ENDARC: - *(BaseBlock *)&EndArcHead=ShortBlock; + EndArcHead.SetBaseBlock(ShortBlock); EndArcHead.NextVolume=(EndArcHead.Flags & EARC_NEXT_VOLUME)!=0; EndArcHead.DataCRC=(EndArcHead.Flags & EARC_DATACRC)!=0; EndArcHead.RevSpace=(EndArcHead.Flags & EARC_REVSPACE)!=0; @@ -446,14 +457,14 @@ size_t Archive::ReadHeader15() break; #ifndef SFX_MODULE case HEAD3_CMT: - *(BaseBlock *)&CommHead=ShortBlock; + CommHead.SetBaseBlock(ShortBlock); CommHead.UnpSize=Raw.Get2(); CommHead.UnpVer=Raw.Get1(); CommHead.Method=Raw.Get1(); CommHead.CommCRC=Raw.Get2(); break; case HEAD3_PROTECT: - *(BaseBlock *)&ProtectHead=ShortBlock; + ProtectHead.SetBaseBlock(ShortBlock); ProtectHead.DataSize=Raw.Get4(); ProtectHead.Version=Raw.Get1(); ProtectHead.RecSectors=Raw.Get2(); @@ -462,26 +473,13 @@ size_t Archive::ReadHeader15() NextBlockPos+=ProtectHead.DataSize; break; case HEAD3_OLDSERVICE: // RAR 2.9 and earlier. - *(BaseBlock *)&SubBlockHead=ShortBlock; + SubBlockHead.SetBaseBlock(ShortBlock); SubBlockHead.DataSize=Raw.Get4(); NextBlockPos+=SubBlockHead.DataSize; SubBlockHead.SubType=Raw.Get2(); SubBlockHead.Level=Raw.Get1(); switch(SubBlockHead.SubType) { - case UO_HEAD: - *(SubBlockHeader *)&UOHead=SubBlockHead; - UOHead.OwnerNameSize=Raw.Get2(); - UOHead.GroupNameSize=Raw.Get2(); - if (UOHead.OwnerNameSize>=ASIZE(UOHead.OwnerName)) - UOHead.OwnerNameSize=ASIZE(UOHead.OwnerName)-1; - if (UOHead.GroupNameSize>=ASIZE(UOHead.GroupName)) - UOHead.GroupNameSize=ASIZE(UOHead.GroupName)-1; - Raw.GetB(UOHead.OwnerName,UOHead.OwnerNameSize); - Raw.GetB(UOHead.GroupName,UOHead.GroupNameSize); - UOHead.OwnerName[UOHead.OwnerNameSize]=0; - UOHead.GroupName[UOHead.GroupNameSize]=0; - break; case NTACL_HEAD: *(SubBlockHeader *)&EAHead=SubBlockHead; EAHead.UnpSize=Raw.Get4(); @@ -496,10 +494,13 @@ size_t Archive::ReadHeader15() StreamHead.Method=Raw.Get1(); StreamHead.StreamCRC=Raw.Get4(); StreamHead.StreamNameSize=Raw.Get2(); - if (StreamHead.StreamNameSize>=ASIZE(StreamHead.StreamName)) - StreamHead.StreamNameSize=ASIZE(StreamHead.StreamName)-1; - Raw.GetB(StreamHead.StreamName,StreamHead.StreamNameSize); - StreamHead.StreamName[StreamHead.StreamNameSize]=0; + + const size_t MaxStreamName20=260; // Maximum allowed stream name in RAR 2.x format. + if (StreamHead.StreamNameSize>MaxStreamName20) + StreamHead.StreamNameSize=MaxStreamName20; + + StreamHead.StreamName.resize(StreamHead.StreamNameSize); + Raw.GetB(&StreamHead.StreamName[0],StreamHead.StreamNameSize); break; } break; @@ -509,12 +510,16 @@ size_t Archive::ReadHeader15() NextBlockPos+=Raw.Get4(); break; } - - ushort HeaderCRC=Raw.GetCRC15(false); + + uint HeaderCRC=Raw.GetCRC15(false); // Old AV header does not have header CRC properly set. + // Old Unix owners header didn't include string fields into header size, + // but included them into CRC, so it couldn't be verified with generic + // approach here. if (ShortBlock.HeadCRC!=HeaderCRC && ShortBlock.HeaderType!=HEAD3_SIGN && - ShortBlock.HeaderType!=HEAD3_AV) + ShortBlock.HeaderType!=HEAD3_AV && + (ShortBlock.HeaderType!=HEAD3_OLDSERVICE || SubBlockHead.SubType!=UO_HEAD)) { bool Recovered=false; if (ShortBlock.HeaderType==HEAD_ENDARC && EndArcHead.RevSpace) @@ -544,6 +549,7 @@ size_t Archive::ReadHeader15() return Raw.Size(); } +#endif // #ifndef SFX_MODULE size_t Archive::ReadHeader50() @@ -558,6 +564,13 @@ size_t Archive::ReadHeader50() return 0; #else + if (Cmd->SkipEncrypted) + { + uiMsg(UIMSG_SKIPENCARC,FileName); + FailedHeaderDecryption=true; // Suppress error messages and quit quietly. + return 0; + } + byte HeadersInitV[SIZE_INITV]; if (Read(HeadersInitV,SIZE_INITV)!=SIZE_INITV) { @@ -570,15 +583,21 @@ size_t Archive::ReadHeader50() // in -p to not stop batch processing for encrypted archives. bool GlobalPassword=Cmd->Password.IsSet() || uiIsGlobalPasswordSet(); + RarCheckPassword CheckPwd; + if (CryptHead.UsePswCheck && !BrokenHeader) + CheckPwd.Set(CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,CryptHead.PswCheck); + while (true) // Repeat the password prompt for wrong passwords. { - RequestArcPassword(); - - byte PswCheck[SIZE_PSWCHECK]; - HeadersCrypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,NULL,PswCheck); - // Verify password validity. - if (CryptHead.UsePswCheck && memcmp(PswCheck,CryptHead.PswCheck,SIZE_PSWCHECK)!=0) - { + RequestArcPassword(CheckPwd.IsSet() ? &CheckPwd:NULL); + + byte PswCheck[SIZE_PSWCHECK]; + bool EncSet=HeadersCrypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,NULL,PswCheck); + // Verify password validity. If header is damaged, we cannot rely on + // password check value, because it can be damaged too. + if (EncSet && CryptHead.UsePswCheck && !BrokenHeader && + memcmp(PswCheck,CryptHead.PswCheck,SIZE_PSWCHECK)!=0) + { if (GlobalPassword) // For -p or Ctrl+P. { // This message is used by Android GUI to reset cached passwords. @@ -635,7 +654,7 @@ size_t Archive::ReadHeader50() } int SizeToRead=int(BlockSize); - SizeToRead-=FirstReadSize-SizeBytes-4; // Adjust overread size bytes if any. + SizeToRead-=int(FirstReadSize-SizeBytes-4); // Adjust overread size bytes if any. uint HeaderSize=4+SizeBytes+(uint)BlockSize; if (SizeToRead<0 || HeaderSizeCRYPT_VERSION) { - wchar Info[20]; - swprintf(Info,ASIZE(Info),L"h%u",CryptVersion); - UnkEncVerMsg(FileName,Info); + UnkEncVerMsg(FileName,L"h" + std::to_wstring(CryptVersion)); + FailedHeaderDecryption=true; return 0; } uint EncFlags=(uint)Raw.GetV(); @@ -714,9 +732,8 @@ size_t Archive::ReadHeader50() CryptHead.Lg2Count=Raw.Get1(); if (CryptHead.Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX) { - wchar Info[20]; - swprintf(Info,ASIZE(Info),L"hc%u",CryptHead.Lg2Count); - UnkEncVerMsg(FileName,Info); + UnkEncVerMsg(FileName,L"hc" + std::to_wstring(CryptHead.Lg2Count)); + FailedHeaderDecryption=true; return 0; } @@ -728,14 +745,15 @@ size_t Archive::ReadHeader50() byte csum[SIZE_PSWCHECK_CSUM]; Raw.GetB(csum,SIZE_PSWCHECK_CSUM); - sha256_context ctx; - sha256_init(&ctx); - sha256_process(&ctx, CryptHead.PswCheck, SIZE_PSWCHECK); - +// Exclude this code for rarext.dll, Setup.SFX and unrar_nocrypt.dll linked +// without sha256. But still set Encrypted=true for rarext.dll here, +// so it can recognize encrypted header archives in archive properties. +#ifndef RAR_NOCRYPT byte Digest[SHA256_DIGEST_SIZE]; - sha256_done(&ctx, Digest); + sha256_get(CryptHead.PswCheck, SIZE_PSWCHECK, Digest); CryptHead.UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; +#endif } Encrypted=true; } @@ -743,7 +761,7 @@ size_t Archive::ReadHeader50() case HEAD_MAIN: { MainHead.Reset(); - *(BaseBlock *)&MainHead=ShortBlock; + MainHead.SetBaseBlock(ShortBlock); uint ArcFlags=(uint)Raw.GetV(); Volume=(ArcFlags & MHFL_VOLUME)!=0; @@ -785,7 +803,7 @@ size_t Archive::ReadHeader50() case HEAD_SERVICE: { FileHeader *hd=ShortBlock.HeaderType==HEAD_FILE ? &FileHead:&SubHead; - hd->Reset(); + hd->Reset(); // Clear hash, time fields and other stuff like flags. *(BaseBlock *)hd=ShortBlock; bool FileBlock=ShortBlock.HeaderType==HEAD_FILE; @@ -821,9 +839,14 @@ size_t Archive::ReadHeader50() // we may need to use the compression algorithm 15 in the future, // but it was already used in RAR 1.5 and Unpack needs to distinguish // them. - hd->UnpVer=(CompInfo & 0x3f) + 50; - if (hd->UnpVer!=50) // Only 5.0 compression is known now. - hd->UnpVer=VER_UNKNOWN; + uint UnpVer=(CompInfo & 0x3f); + if (UnpVer==0) + hd->UnpVer=VER_PACK5; + else + if (UnpVer==1) + hd->UnpVer=VER_PACK7; + else + hd->UnpVer=VER_UNKNOWN; hd->HostOS=(byte)Raw.GetV(); size_t NameSize=(size_t)Raw.GetV(); @@ -841,16 +864,29 @@ size_t Archive::ReadHeader50() hd->SubBlock=(hd->Flags & HFL_CHILD)!=0; hd->Solid=FileBlock && (CompInfo & FCI_SOLID)!=0; hd->Dir=(hd->FileFlags & FHFL_DIRECTORY)!=0; - hd->WinSize=hd->Dir ? 0:size_t(0x20000)<<((CompInfo>>10)&0xf); - - hd->CryptMethod=hd->Encrypted ? CRYPT_RAR50:CRYPT_NONE; + if (hd->Dir || UnpVer>1) + hd->WinSize=0; + else + { + hd->WinSize=0x20000ULL<<((CompInfo>>10)&(UnpVer==0 ? 0x0f:0x1f)); + if (UnpVer==1) + { + hd->WinSize+=hd->WinSize/32*((CompInfo>>15)&0x1f); + + // RAR7 header with RAR5 compression. Needed to append RAR7 files + // to RAR5 solid stream if new dictionary is larger than existing. + if ((CompInfo & FCI_RAR5_COMPAT)!=0) + hd->UnpVer=VER_PACK5; + if (hd->WinSize>UNPACK_MAX_DICT) + hd->UnpVer=VER_UNKNOWN; + } + } - char FileName[NM*4]; - size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); - Raw.GetB((byte *)FileName,ReadNameSize); - FileName[ReadNameSize]=0; + size_t ReadNameSize=Min(NameSize,MAXPATHSIZE); + std::string FileName(ReadNameSize,0); + Raw.GetB((byte *)&FileName[0],ReadNameSize); - UtfToWide(FileName,hd->FileName,ASIZE(hd->FileName)); + UtfToWide(FileName.data(),hd->FileName); // Should do it before converting names, because extra fields can // affect name processing, like in case of NTFS streams. @@ -868,28 +904,25 @@ size_t Archive::ReadHeader50() if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_CMT)) MainComment=true; -#if 0 // For RAR5 format we read the user specified recovery percent here. - // It would be useful to do it for shell extension too, so we display - // the correct recovery record size in archive properties. But then - // we would need to include the entire recovery record processing - // code to shell extension, which is not done now. - if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_RR) && hd->SubData.Size()>0) + if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_RR) && hd->SubData.size()>0) { - RecoveryPercent=hd->SubData[0]; - RSBlockHeader Header; - GetRRInfo(this,&Header); - RecoverySize=Header.RecSectionSize*Header.RecCount; + // It is stored as a single byte up to RAR 6.02 and as vint since + // 6.10, where we extended the maximum RR size from 99% to 1000%. + RawRead RawPercent; + RawPercent.Read(hd->SubData.data(),hd->SubData.size()); + RecoveryPercent=(int)RawPercent.GetV(); + } -#endif - + + if (BadCRC) // Add the file name to broken header message displayed above. uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName); } break; case HEAD_ENDARC: { - *(BaseBlock *)&EndArcHead=ShortBlock; + EndArcHead.SetBaseBlock(ShortBlock); uint ArcFlags=(uint)Raw.GetV(); EndArcHead.NextVolume=(ArcFlags & EHFL_NEXTVOLUME)!=0; EndArcHead.StoreVolNumber=false; @@ -904,7 +937,7 @@ size_t Archive::ReadHeader50() #if !defined(RAR_NOCRYPT) -void Archive::RequestArcPassword() +void Archive::RequestArcPassword(RarCheckPassword *CheckPwd) { if (!Cmd->Password.IsSet()) { @@ -921,7 +954,7 @@ void Archive::RequestArcPassword() *PasswordA=0; if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) *PasswordA=0; - GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); + CharToWide(PasswordA,PasswordW,ASIZE(PasswordW)); cleandata(PasswordA,sizeof(PasswordA)); } Cmd->Password.Set(PasswordW); @@ -934,7 +967,7 @@ void Archive::RequestArcPassword() ErrHandler.Exit(RARX_USERBREAK); } #else - if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password)) + if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password,CheckPwd)) { Close(); uiMsg(UIERROR_INCERRCOUNT); // Prevent archive deleting if delete after extraction is on. @@ -947,7 +980,7 @@ void Archive::RequestArcPassword() #endif -void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) +void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,const BaseBlock *bb) { // Read extra data from the end of block skipping any fields before it. size_t ExtraStart=Raw->Size()-ExtraSize; @@ -970,22 +1003,52 @@ void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) if (bb->HeaderType==HEAD_MAIN) { MainHeader *hd=(MainHeader *)bb; - if (FieldType==MHEXTRA_LOCATOR) + switch(FieldType) { - hd->Locator=true; - uint Flags=(uint)Raw->GetV(); - if ((Flags & MHEXTRA_LOCATOR_QLIST)!=0) - { - uint64 Offset=Raw->GetV(); - if (Offset!=0) // 0 means that reserved space was not enough to write the offset. - hd->QOpenOffset=Offset+CurBlockPos; - } - if ((Flags & MHEXTRA_LOCATOR_RR)!=0) - { - uint64 Offset=Raw->GetV(); - if (Offset!=0) // 0 means that reserved space was not enough to write the offset. - hd->RROffset=Offset+CurBlockPos; - } + case MHEXTRA_LOCATOR: + { + hd->Locator=true; + uint Flags=(uint)Raw->GetV(); + if ((Flags & MHEXTRA_LOCATOR_QLIST)!=0) + { + uint64 Offset=Raw->GetV(); + if (Offset!=0) // 0 means that reserved space was not enough to write the offset. + hd->QOpenOffset=Offset+CurBlockPos; + } + if ((Flags & MHEXTRA_LOCATOR_RR)!=0) + { + uint64 Offset=Raw->GetV(); + if (Offset!=0) // 0 means that reserved space was not enough to write the offset. + hd->RROffset=Offset+CurBlockPos; + } + } + break; + case MHEXTRA_METADATA: + { + uint Flags=(uint)Raw->GetV(); + if ((Flags & MHEXTRA_METADATA_NAME)!=0) + { + uint64 NameSize=Raw->GetV(); + if (NameSize>0 && NameSizeGetB(&NameU[0],(size_t)NameSize); + // If starts from 0, the name was longer than reserved space + // when saving this extra field. + if (NameU[0]!=0) + UtfToWide(&NameU[0],hd->OrigName); + } + } + if ((Flags & MHEXTRA_METADATA_CTIME)!=0) + if ((Flags & MHEXTRA_METADATA_UNIXTIME)!=0) + if ((Flags & MHEXTRA_METADATA_UNIX_NS)!=0) + hd->OrigTime.SetUnixNS(Raw->Get8()); + else + hd->OrigTime.SetUnix((time_t)Raw->Get4()); + else + hd->OrigTime.SetWin(Raw->Get8()); + } + break; } } @@ -994,64 +1057,64 @@ void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) FileHeader *hd=(FileHeader *)bb; switch(FieldType) { +#ifndef RAR_NOCRYPT // Except rarext.dll, Setup.SFX and unrar_nocrypt.dll. case FHEXTRA_CRYPT: { FileHeader *hd=(FileHeader *)bb; uint EncVersion=(uint)Raw->GetV(); - if (EncVersion > CRYPT_VERSION) + if (EncVersion>CRYPT_VERSION) { - wchar Info[20]; - swprintf(Info,ASIZE(Info),L"x%u",EncVersion); - UnkEncVerMsg(hd->FileName,Info); + UnkEncVerMsg(hd->FileName,L"x" + std::to_wstring(EncVersion)); + hd->CryptMethod=CRYPT_UNKNOWN; } else { uint Flags=(uint)Raw->GetV(); - hd->UsePswCheck=(Flags & FHEXTRA_CRYPT_PSWCHECK)!=0; - hd->UseHashKey=(Flags & FHEXTRA_CRYPT_HASHMAC)!=0; hd->Lg2Count=Raw->Get1(); if (hd->Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX) { - wchar Info[20]; - swprintf(Info,ASIZE(Info),L"xc%u",hd->Lg2Count); - UnkEncVerMsg(hd->FileName,Info); + UnkEncVerMsg(hd->FileName,L"xc" + std::to_wstring(hd->Lg2Count)); + hd->CryptMethod=CRYPT_UNKNOWN; } - Raw->GetB(hd->Salt,SIZE_SALT50); - Raw->GetB(hd->InitV,SIZE_INITV); - if (hd->UsePswCheck) + else { - Raw->GetB(hd->PswCheck,SIZE_PSWCHECK); - - // It is important to know if password check data is valid. - // If it is damaged and header CRC32 fails to detect it, - // archiver would refuse to decompress a possibly valid file. - // Since we want to be sure distinguishing a wrong password - // or corrupt file data, we use 64-bit password check data - // and to control its validity we use 32 bits of password - // check data SHA-256 additionally to 32-bit header CRC32. - byte csum[SIZE_PSWCHECK_CSUM]; - Raw->GetB(csum,SIZE_PSWCHECK_CSUM); - - sha256_context ctx; - sha256_init(&ctx); - sha256_process(&ctx, hd->PswCheck, SIZE_PSWCHECK); - - byte Digest[SHA256_DIGEST_SIZE]; - sha256_done(&ctx, Digest); - - hd->UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; - - // RAR 5.21 and earlier set PswCheck field in service records to 0 - // even if UsePswCheck was present. - if (bb->HeaderType==HEAD_SERVICE && memcmp(hd->PswCheck,"\0\0\0\0\0\0\0\0",SIZE_PSWCHECK)==0) - hd->UsePswCheck=0; + hd->UsePswCheck=(Flags & FHEXTRA_CRYPT_PSWCHECK)!=0; + hd->UseHashKey=(Flags & FHEXTRA_CRYPT_HASHMAC)!=0; + + Raw->GetB(hd->Salt,SIZE_SALT50); + Raw->GetB(hd->InitV,SIZE_INITV); + if (hd->UsePswCheck) + { + Raw->GetB(hd->PswCheck,SIZE_PSWCHECK); + + // It is important to know if password check data is valid. + // If it is damaged and header CRC32 fails to detect it, + // archiver would refuse to decompress a possibly valid file. + // Since we want to be sure distinguishing a wrong password + // or corrupt file data, we use 64-bit password check data + // and to control its validity we use 32 bits of password + // check data SHA-256 additionally to 32-bit header CRC32. + byte csum[SIZE_PSWCHECK_CSUM]; + Raw->GetB(csum,SIZE_PSWCHECK_CSUM); + + byte Digest[SHA256_DIGEST_SIZE]; + sha256_get(hd->PswCheck, SIZE_PSWCHECK, Digest); + + hd->UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; + + // RAR 5.21 and earlier set PswCheck field in service records to 0 + // even if UsePswCheck was present. + if (bb->HeaderType==HEAD_SERVICE && memcmp(hd->PswCheck,"\0\0\0\0\0\0\0\0",SIZE_PSWCHECK)==0) + hd->UsePswCheck=0; + } + hd->SaltSet=true; + hd->CryptMethod=CRYPT_RAR50; + hd->Encrypted=true; } - hd->SaltSet=true; - hd->CryptMethod=CRYPT_RAR50; - hd->Encrypted=true; } } break; +#endif case FHEXTRA_HASH: { FileHeader *hd=(FileHeader *)bb; @@ -1103,31 +1166,27 @@ void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) if (Version!=0) { hd->Version=true; - - wchar VerText[20]; - swprintf(VerText,ASIZE(VerText),L";%u",Version); - wcsncatz(hd->FileName,VerText,ASIZE(hd->FileName)); + hd->FileName += L';' + std::to_wstring(Version); } } break; case FHEXTRA_REDIR: { - hd->RedirType=(FILE_SYSTEM_REDIRECT)Raw->GetV(); + FILE_SYSTEM_REDIRECT RedirType=(FILE_SYSTEM_REDIRECT)Raw->GetV(); uint Flags=(uint)Raw->GetV(); - hd->DirTarget=(Flags & FHEXTRA_REDIR_DIR)!=0; size_t NameSize=(size_t)Raw->GetV(); - char UtfName[NM*4]; - *UtfName=0; - if (NameSize0 && NameSizeGetB(UtfName,NameSize); - UtfName[NameSize]=0; - } + std::string UtfName(NameSize,0); + hd->RedirType=RedirType; + hd->DirTarget=(Flags & FHEXTRA_REDIR_DIR)!=0; + Raw->GetB(&UtfName[0],NameSize); + UtfToWide(&UtfName[0],hd->RedirName); #ifdef _WIN_ALL - UnixSlashToDos(UtfName,UtfName,ASIZE(UtfName)); + UnixSlashToDos(hd->RedirName,hd->RedirName); #endif - UtfToWide(UtfName,hd->RedirName,ASIZE(hd->RedirName)); + } } break; case FHEXTRA_UOWNER: @@ -1184,8 +1243,8 @@ void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) // We cannot allocate too much memory here, because above // we check FieldSize againt Raw size and we control that Raw size // is sensible when reading headers. - hd->SubData.Alloc((size_t)FieldSize); - Raw->GetB(hd->SubData.Addr(0),(size_t)FieldSize); + hd->SubData.resize((size_t)FieldSize); + Raw->GetB(hd->SubData.data(),(size_t)FieldSize); } break; } @@ -1208,7 +1267,7 @@ size_t Archive::ReadHeader14() Raw.GetB(Mark,4); uint HeadSize=Raw.Get2(); if (HeadSize<7) - return false; + return 0; byte Flags=Raw.Get1(); NextBlockPos=CurBlockPos+HeadSize; CurHeaderType=HEAD_MAIN; @@ -1231,7 +1290,7 @@ size_t Archive::ReadHeader14() FileHead.FileHash.CRC32=Raw.Get2(); FileHead.HeadSize=Raw.Get2(); if (FileHead.HeadSize<21) - return false; + return 0; uint FileTime=Raw.Get4(); FileHead.FileAttr=Raw.Get1(); FileHead.Flags=Raw.Get1()|LONG_BLOCK; @@ -1255,12 +1314,13 @@ size_t Archive::ReadHeader14() Raw.Read(NameSize); - char FileName[NM]; - size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); - Raw.GetB((byte *)FileName,ReadNameSize); - FileName[ReadNameSize]=0; - IntToExt(FileName,FileName,ASIZE(FileName)); - CharToWide(FileName,FileHead.FileName,ASIZE(FileHead.FileName)); + // RAR 1.4 name size is stored in a single byte field and it can't + // exceed 255, so additional checks are not needed. + std::string FileName(NameSize,0); + Raw.GetB((byte *)&FileName[0],NameSize); + std::string NameA; + OemToExt(FileName,NameA); + CharToWide(NameA,FileHead.FileName); ConvertNameCase(FileHead.FileName); ConvertFileHeader(&FileHead); @@ -1274,7 +1334,7 @@ size_t Archive::ReadHeader14() #ifndef SFX_MODULE -void Archive::ConvertNameCase(wchar *Name) +void Archive::ConvertNameCase(std::wstring &Name) { if (Cmd->ConvertNames==NAMES_UPPERCASE) wcsupper(Name); @@ -1292,7 +1352,7 @@ bool Archive::IsArcDir() void Archive::ConvertAttributes() { -#if defined(_WIN_ALL) || defined(_EMX) +#ifdef _WIN_ALL if (FileHead.HSType!=HSYS_WINDOWS) FileHead.FileAttr=FileHead.Dir ? 0x10 : 0x20; #endif @@ -1357,19 +1417,23 @@ void Archive::ConvertAttributes() void Archive::ConvertFileHeader(FileHeader *hd) { +/* if (hd->HSType==HSYS_UNKNOWN) if (hd->Dir) hd->FileAttr=0x10; else hd->FileAttr=0x20; +*/ #ifdef _WIN_ALL if (hd->HSType==HSYS_UNIX) // Convert Unix, OS X and Android decomposed chracters to Windows precomposed. - ConvertToPrecomposed(hd->FileName,ASIZE(hd->FileName)); + ConvertToPrecomposed(hd->FileName); #endif - for (wchar *s=hd->FileName;*s!=0;s++) + for (uint I=0;IFileName.size();I++) { + wchar *s=&hd->FileName[I]; + #ifdef _UNIX // Backslash is the invalid character for Windows file headers, // but it can present in Unix file names extracted in Unix. @@ -1377,7 +1441,7 @@ void Archive::ConvertFileHeader(FileHeader *hd) *s='_'; #endif -#if defined(_WIN_ALL) || defined(_EMX) +#ifdef _WIN_ALL // RAR 5.0 archives do not use '\' as path separator, so if we see it, // it means that it is a part of Unix file name, which we cannot // extract in Windows. @@ -1402,6 +1466,9 @@ void Archive::ConvertFileHeader(FileHeader *hd) if (*s=='/' || *s=='\\' && Format!=RARFMT50) *s=CPATHDIVIDER; } + + // Zeroes inside might be possible in broken Unicode names decoded with EncodeFileName::Decode. + TruncateAtZero(hd->FileName); // Ensure there are no zeroes inside of string. } @@ -1416,7 +1483,7 @@ int64 Archive::GetStartPos() } -bool Archive::ReadSubData(Array *UnpData,File *DestFile,bool TestMode) +bool Archive::ReadSubData(std::vector *UnpData,File *DestFile,bool TestMode) { if (BrokenHeader) { @@ -1424,7 +1491,7 @@ bool Archive::ReadSubData(Array *UnpData,File *DestFile,bool TestMode) ErrHandler.SetErrorCode(RARX_CRC); return false; } - if (SubHead.Method>5 || SubHead.UnpVer>(Format==RARFMT50 ? VER_UNPACK5:VER_UNPACK)) + if (SubHead.Method>5 || SubHead.UnpVer>(Format==RARFMT50 ? VER_UNPACK7:VER_UNPACK)) { uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); return false; @@ -1441,7 +1508,9 @@ bool Archive::ReadSubData(Array *UnpData,File *DestFile,bool TestMode) { if (SubHead.UnpSize>0x1000000) { - // So huge allocation must never happen in valid archives. + // Prevent the excessive allocation. When reading to memory, normally + // this function operates with reasonably small blocks, such as + // the archive comment, NTFS ACL or "Zone.Identifier" NTFS stream. uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); return false; } @@ -1449,7 +1518,7 @@ bool Archive::ReadSubData(Array *UnpData,File *DestFile,bool TestMode) SubDataIO.SetTestMode(true); else { - UnpData->Alloc((size_t)SubHead.UnpSize); + UnpData->resize((size_t)SubHead.UnpSize); SubDataIO.SetUnpackToMemory(&(*UnpData)[0],(uint)SubHead.UnpSize); } } @@ -1478,7 +1547,7 @@ bool Archive::ReadSubData(Array *UnpData,File *DestFile,bool TestMode) uiMsg(UIERROR_SUBHEADERDATABROKEN,FileName,SubHead.FileName); ErrHandler.SetErrorCode(RARX_CRC); if (UnpData!=NULL) - UnpData->Reset(); + UnpData->clear(); return false; } return true; diff --git a/unrar/array.hpp b/unrar/array.hpp deleted file mode 100644 index 20d258d5..00000000 --- a/unrar/array.hpp +++ /dev/null @@ -1,191 +0,0 @@ -#ifndef _RAR_ARRAY_ -#define _RAR_ARRAY_ - -extern ErrorHandler ErrHandler; - -template class Array -{ - private: - T *Buffer; - size_t BufSize; - size_t AllocSize; - size_t MaxSize; - bool Secure; // Clean memory if true. - public: - Array(); - Array(size_t Size); - Array(const Array &Src); // Copy constructor. - ~Array(); - inline void CleanData(); - inline T& operator [](size_t Item) const; - inline T* operator + (size_t Pos); - inline size_t Size(); // Returns the size in items, not in bytes. - void Add(size_t Items); - void Alloc(size_t Items); - void Reset(); - void SoftReset(); - void operator = (Array &Src); - void Push(T Item); - void Append(T *Item,size_t Count); - T* Addr(size_t Item) {return Buffer+Item;} - void SetMaxSize(size_t Size) {MaxSize=Size;} - T* Begin() {return Buffer;} - T* End() {return Buffer==NULL ? NULL:Buffer+BufSize;} - void SetSecure() {Secure=true;} -}; - - -template void Array::CleanData() -{ - Buffer=NULL; - BufSize=0; - AllocSize=0; - MaxSize=0; - Secure=false; -} - - -template Array::Array() -{ - CleanData(); -} - - -template Array::Array(size_t Size) -{ - CleanData(); - Add(Size); -} - - -// Copy constructor in case we need to pass an object as value. -template Array::Array(const Array &Src) -{ - CleanData(); - Alloc(Src.BufSize); - if (Src.BufSize!=0) - memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); -} - - -template Array::~Array() -{ - if (Buffer!=NULL) - { - if (Secure) - cleandata(Buffer,AllocSize*sizeof(T)); - free(Buffer); - } -} - - -template inline T& Array::operator [](size_t Item) const -{ - return Buffer[Item]; -} - - -template inline T* Array::operator +(size_t Pos) -{ - return Buffer+Pos; -} - - -template inline size_t Array::Size() -{ - return BufSize; -} - - -template void Array::Add(size_t Items) -{ - BufSize+=Items; - if (BufSize>AllocSize) - { - if (MaxSize!=0 && BufSize>MaxSize) - { - ErrHandler.GeneralErrMsg(L"Maximum allowed array size (%u) is exceeded",MaxSize); - ErrHandler.MemoryError(); - } - - size_t Suggested=AllocSize+AllocSize/4+32; - size_t NewSize=Max(BufSize,Suggested); - - T *NewBuffer; - if (Secure) - { - NewBuffer=(T *)malloc(NewSize*sizeof(T)); - if (NewBuffer==NULL) - ErrHandler.MemoryError(); - if (Buffer!=NULL) - { - memcpy(NewBuffer,Buffer,AllocSize*sizeof(T)); - cleandata(Buffer,AllocSize*sizeof(T)); - free(Buffer); - } - } - else - { - NewBuffer=(T *)realloc(Buffer,NewSize*sizeof(T)); - if (NewBuffer==NULL) - ErrHandler.MemoryError(); - } - Buffer=NewBuffer; - AllocSize=NewSize; - } -} - - -template void Array::Alloc(size_t Items) -{ - if (Items>AllocSize) - Add(Items-BufSize); - else - BufSize=Items; -} - - -template void Array::Reset() -{ - if (Buffer!=NULL) - { - free(Buffer); - Buffer=NULL; - } - BufSize=0; - AllocSize=0; -} - - -// Reset buffer size, but preserve already allocated memory if any, -// so we can reuse it without wasting time to allocation. -template void Array::SoftReset() -{ - BufSize=0; -} - - -template void Array::operator =(Array &Src) -{ - Reset(); - Alloc(Src.BufSize); - if (Src.BufSize!=0) - memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); -} - - -template void Array::Push(T Item) -{ - Add(1); - (*this)[Size()-1]=Item; -} - - -template void Array::Append(T *Items,size_t Count) -{ - size_t CurSize=Size(); - Add(Count); - memcpy(Buffer+CurSize,Items,Count*sizeof(T)); -} - -#endif diff --git a/unrar/blake2s.cpp b/unrar/blake2s.cpp index 317603da..ae9d4e02 100644 --- a/unrar/blake2s.cpp +++ b/unrar/blake2s.cpp @@ -2,6 +2,20 @@ #include "rar.hpp" +static const byte blake2s_sigma[10][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , +}; + #ifdef USE_SSE #include "blake2s_sse.cpp" #endif @@ -18,20 +32,6 @@ static const uint32 blake2s_IV[8] = 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; -static const byte blake2s_sigma[10][16] = -{ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , - { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , - { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , - { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , - { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , - { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , -}; - static inline void blake2s_set_lastnode( blake2s_state *S ) { S->f[1] = ~0U; @@ -134,11 +134,7 @@ void blake2s_update( blake2s_state *S, const byte *in, size_t inlen ) blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); #ifdef USE_SSE -#ifdef _WIN_32 // We use SSSE3 _mm_shuffle_epi8 only in x64 mode. - if (_SSE_Version>=SSE_SSE2) -#else if (_SSE_Version>=SSE_SSSE3) -#endif blake2s_compress_sse( S, S->buf ); else blake2s_compress( S, S->buf ); // Compress diff --git a/unrar/blake2s.hpp b/unrar/blake2s.hpp index f88ef378..90b7885f 100644 --- a/unrar/blake2s.hpp +++ b/unrar/blake2s.hpp @@ -5,12 +5,9 @@ #define BLAKE2_DIGEST_SIZE 32 #define BLAKE2_THREADS_NUMBER 8 -enum blake2s_constant -{ - BLAKE2S_BLOCKBYTES = 64, - BLAKE2S_OUTBYTES = 32 -}; - +// Use constexpr instead of enums for -std=c++20 compatibility. +constexpr size_t BLAKE2S_BLOCKBYTES = 64; +constexpr size_t BLAKE2S_OUTBYTES = 32; // Alignment to 64 improves performance of both SSE and non-SSE versions. // Alignment to n*16 is required for SSE version, so we selected 64. @@ -20,10 +17,15 @@ enum blake2s_constant // 'new' operator. struct blake2s_state { - enum { BLAKE_ALIGNMENT = 64 }; + // Use constexpr instead of enums, because otherwise clang -std=c++20 + // issues a warning about "arithmetic between different enumeration types" + // in ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT] declaration. + static constexpr size_t BLAKE_ALIGNMENT = 64; // buffer and uint32 h[8], t[2], f[2]; - enum { BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES }; + // 2 * BLAKE2S_BLOCKBYTES is the buf size in blake2_code_20140114.zip. + // It might differ in later versions. + static constexpr size_t BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES; byte ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT]; diff --git a/unrar/blake2s_sse.cpp b/unrar/blake2s_sse.cpp index 1a02f210..933fcad8 100644 --- a/unrar/blake2s_sse.cpp +++ b/unrar/blake2s_sse.cpp @@ -1,15 +1,14 @@ // Based on public domain code written in 2012 by Samuel Neves -extern const byte blake2s_sigma[10][16]; - // Initialization vector. static __m128i blake2s_IV_0_3, blake2s_IV_4_7; -#ifdef _WIN_64 -// Constants for cyclic rotation. Used in 64-bit mode in mm_rotr_epi32 macro. +// Constants for cyclic rotation. static __m128i crotr8, crotr16; -#endif +#ifdef __GNUC__ +__attribute__((target("sse2"))) +#endif static void blake2s_init_sse() { // We cannot initialize these 128 bit variables in place when declaring @@ -24,28 +23,18 @@ static void blake2s_init_sse() blake2s_IV_0_3 = _mm_setr_epi32( 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A ); blake2s_IV_4_7 = _mm_setr_epi32( 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 ); -#ifdef _WIN_64 crotr8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 ); crotr16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 ); -#endif } #define LOAD(p) _mm_load_si128( (__m128i *)(p) ) #define STORE(p,r) _mm_store_si128((__m128i *)(p), r) -#ifdef _WIN_32 -// 32-bit mode has less SSE2 registers and in MSVC2008 it is more efficient -// to not use _mm_shuffle_epi8 here. -#define mm_rotr_epi32(r, c) ( \ - _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) ) -#else #define mm_rotr_epi32(r, c) ( \ c==8 ? _mm_shuffle_epi8(r,crotr8) \ : c==16 ? _mm_shuffle_epi8(r,crotr16) \ : _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) ) -#endif - #define G1(row1,row2,row3,row4,buf) \ row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ @@ -73,14 +62,6 @@ static void blake2s_init_sse() row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \ row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(2,1,0,3) ); -#ifdef _WIN_64 - // MSVC 2008 in x64 mode expands _mm_set_epi32 to store to stack and load - // from stack operations, which are slower than this code. - #define _mm_set_epi32(i3,i2,i1,i0) \ - _mm_unpacklo_epi32(_mm_unpacklo_epi32(_mm_cvtsi32_si128(i0),_mm_cvtsi32_si128(i2)), \ - _mm_unpacklo_epi32(_mm_cvtsi32_si128(i1),_mm_cvtsi32_si128(i3))) -#endif - // Original BLAKE2 SSE4.1 message loading code was a little slower in x86 mode // and about the same in x64 mode in our test. Perhaps depends on compiler. // We also tried _mm_i32gather_epi32 and _mm256_i32gather_epi32 AVX2 gather @@ -101,6 +82,9 @@ static void blake2s_init_sse() } +#ifdef __GNUC__ +__attribute__((target("ssse3"))) +#endif static int blake2s_compress_sse( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] ) { __m128i row[4]; diff --git a/unrar/blake2sp.cpp b/unrar/blake2sp.cpp index da645883..91dbd144 100644 --- a/unrar/blake2sp.cpp +++ b/unrar/blake2sp.cpp @@ -20,7 +20,7 @@ void blake2sp_init( blake2sp_state *S ) blake2s_init_param( &S->R, 0, 1 ); // Init root. - for( uint i = 0; i < PARALLELISM_DEGREE; ++i ) + for( uint32 i = 0; i < PARALLELISM_DEGREE; ++i ) blake2s_init_param( &S->S[i], i, 0 ); // Init leaf. S->R.last_node = 1; @@ -49,6 +49,8 @@ void Blake2ThreadData::Update() if (_SSE_Version>=SSE_SSE && inlen__ >= 2 * PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES) _mm_prefetch((char*)(in__ + PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES), _MM_HINT_T0); #endif + // We tried to _forceinline blake2s_update and blake2s_compress_sse, + // but it didn't improve performance. blake2s_update( S, in__, BLAKE2S_BLOCKBYTES ); in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; diff --git a/unrar/cmddata.cpp b/unrar/cmddata.cpp index f3a1d142..d3a40b1f 100644 --- a/unrar/cmddata.cpp +++ b/unrar/cmddata.cpp @@ -13,8 +13,18 @@ void CommandData::Init() { RAROptions::Init(); - *Command=0; - *ArcName=0; + Command.clear(); + ArcName.clear(); + ExtrPath.clear(); + TempPath.clear(); + SFXModule.clear(); + CommentFile.clear(); + ArcPath.clear(); + ExclArcPath.clear(); + LogName.clear(); + EmailTo.clear(); + UseStdin.clear(); + FileLists=false; NoMoreSwitches=false; @@ -26,60 +36,52 @@ void CommandData::Init() FileArgs.Reset(); ExclArgs.Reset(); InclArgs.Reset(); - StoreArgs.Reset(); ArcNames.Reset(); - NextVolSizes.Reset(); -} - - -// Return the pointer to next position in the string and store dynamically -// allocated command line parameter in Par. -static const wchar *AllocCmdParam(const wchar *CmdLine,wchar **Par) -{ - const wchar *NextCmd=GetCmdParam(CmdLine,NULL,0); - if (NextCmd==NULL) - return NULL; - size_t ParSize=NextCmd-CmdLine+2; // Parameter size including the trailing zero. - *Par=(wchar *)malloc(ParSize*sizeof(wchar)); - if (*Par==NULL) - return NULL; - return GetCmdParam(CmdLine,*Par,ParSize); + StoreArgs.Reset(); +#ifdef PROPAGATE_MOTW + MotwList.Reset(); +#endif + Password.Clean(); + NextVolSizes.clear(); +#ifdef RARDLL + DllDestName.clear(); +#endif } #if !defined(SFX_MODULE) void CommandData::ParseCommandLine(bool Preprocess,int argc, char *argv[]) { - *Command=0; + Command.clear(); NoMoreSwitches=false; #ifdef CUSTOM_CMDLINE_PARSER // In Windows we may prefer to implement our own command line parser // to avoid replacing \" by " in standard parser. Such replacing corrupts // destination paths like "dest path\" in extraction commands. - const wchar *CmdLine=GetCommandLine(); + std::wstring CmdLine=GetCommandLine(); + + std::wstring Param; + std::wstring::size_type Pos=0; - wchar *Par; for (bool FirstParam=true;;FirstParam=false) { - if ((CmdLine=AllocCmdParam(CmdLine,&Par))==NULL) + if (!GetCmdParam(CmdLine,Pos,Param)) break; if (!FirstParam) // First parameter is the executable name. if (Preprocess) - PreprocessArg(Par); + PreprocessArg(Param.c_str()); else - ParseArg(Par); - free(Par); + ParseArg(Param.c_str()); } #else - Array Arg; for (int I=1;I0 && Arg[L-1]=='.' && (L==1 || L>=2 && (IsPathDiv(Arg[L-2]) || + Arg[L-2]=='.' && (L==2 || L>=3 && IsPathDiv(Arg[L-3]))))) + FolderArg=true; + + wchar CmdChar=toupperw(Command[0]); bool Add=wcschr(L"AFUM",CmdChar)!=NULL; bool Extract=CmdChar=='X' || CmdChar=='E'; bool Repair=CmdChar=='R' && Command[1]==0; - if (EndSeparator && !Add) - wcsncpyz(ExtrPath,Arg,ASIZE(ExtrPath)); + if (FolderArg && !Add) + ExtrPath=Arg; else if ((Add || CmdChar=='T') && (*Arg!='@' || ListMode==RCLM_REJECT_LISTS)) FileArgs.AddString(Arg); @@ -141,10 +161,10 @@ void CommandData::ParseArg(wchar *Arg) } else // We use 'destpath\' when extracting and reparing. - if (Found && FileData.IsDir && (Extract || Repair) && *ExtrPath==0) + if (Found && FileData.IsDir && (Extract || Repair) && ExtrPath.empty()) { - wcsncpyz(ExtrPath,Arg,ASIZE(ExtrPath)); - AddEndSlash(ExtrPath,ASIZE(ExtrPath)); + ExtrPath=Arg; + AddEndSlash(ExtrPath); } else FileArgs.AddString(Arg); @@ -172,12 +192,12 @@ void CommandData::ParseDone() #if !defined(SFX_MODULE) void CommandData::ParseEnvVar() { - char *EnvStr=getenv("RAR"); - if (EnvStr!=NULL) + char *EnvVar=getenv("RARINISWITCHES"); + if (EnvVar!=NULL) { - Array EnvStrW(strlen(EnvStr)+1); - CharToWide(EnvStr,&EnvStrW[0],EnvStrW.Size()); - ProcessSwitchesString(&EnvStrW[0]); + std::wstring EnvStr; + CharToWide(EnvVar,EnvStr); + ProcessSwitchesString(EnvStr); } } #endif @@ -186,7 +206,7 @@ void CommandData::ParseEnvVar() #if !defined(SFX_MODULE) // Preprocess those parameters, which must be processed before the rest of -// command line. Return 'false' to stop further processing. +// command line. void CommandData::PreprocessArg(const wchar *Arg) { if (IsSwitch(Arg[0]) && !NoMoreSwitches) @@ -195,7 +215,7 @@ void CommandData::PreprocessArg(const wchar *Arg) if (Arg[0]=='-' && Arg[1]==0) // Switch "--". NoMoreSwitches=true; if (wcsicomp(Arg,L"cfg-")==0) - ConfigDisabled=true; + ProcessSwitch(Arg); if (wcsnicomp(Arg,L"ilog",4)==0) { // Ensure that correct log file name is already set @@ -207,13 +227,13 @@ void CommandData::PreprocessArg(const wchar *Arg) { // Process -sc before reading any file lists. ProcessSwitch(Arg); - if (*LogName!=0) + if (!LogName.empty()) InitLogOptions(LogName,ErrlogCharset); } } else - if (*Command==0) - wcsncpy(Command,Arg,ASIZE(Command)); // Need for rar.ini. + if (Command.empty()) + Command=Arg; // Need for rar.ini. } #endif @@ -231,10 +251,10 @@ void CommandData::ReadConfig() Str++; if (wcsnicomp(Str,L"switches=",9)==0) ProcessSwitchesString(Str+9); - if (*Command!=0) + if (!Command.empty()) { wchar Cmd[16]; - wcsncpyz(Cmd,Command,ASIZE(Cmd)); + wcsncpyz(Cmd,Command.c_str(),ASIZE(Cmd)); wchar C0=toupperw(Cmd[0]); wchar C1=toupperw(Cmd[1]); if (C0=='I' || C0=='L' || C0=='M' || C0=='S' || C0=='V') @@ -254,14 +274,19 @@ void CommandData::ReadConfig() #if !defined(SFX_MODULE) -void CommandData::ProcessSwitchesString(const wchar *Str) +void CommandData::ProcessSwitchesString(const std::wstring &Str) { - wchar *Par; - while ((Str=AllocCmdParam(Str,&Par))!=NULL) + std::wstring Par; + std::wstring::size_type Pos=0; + while (GetCmdParam(Str,Pos,Par)) { - if (IsSwitch(*Par)) - ProcessSwitch(Par+1); - free(Par); + if (IsSwitch(Par[0])) + ProcessSwitch(&Par[1]); + else + { + mprintf(St(MSwSyntaxError),Par.c_str()); + ErrHandler.Exit(RARX_USERERROR); + } } } #endif @@ -271,6 +296,9 @@ void CommandData::ProcessSwitchesString(const wchar *Str) void CommandData::ProcessSwitch(const wchar *Switch) { + if (LargePageAlloc::ProcessSwitch(this,Switch)) + return; + switch(toupperw(Switch[0])) { case '@': @@ -309,13 +337,27 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'I': IgnoreGeneralAttr=true; break; - case 'N': // Reserved for archive name. + case 'M': + switch(toupperw(Switch[2])) + { + case 0: + case 'S': + ArcMetadata=ARCMETA_SAVE; + break; + case 'R': + ArcMetadata=ARCMETA_RESTORE; + break; + default: + BadSwitch(Switch); + break; + } break; case 'O': AddArcOnly=true; break; case 'P': - wcsncpyz(ArcPath,Switch+2,ASIZE(ArcPath)); + // Convert slashes here than before every comparison. + SlashToNative(Switch+2,ArcPath); break; case 'S': SyncFiles=true; @@ -326,7 +368,14 @@ void CommandData::ProcessSwitch(const wchar *Switch) } break; case 'C': - if (Switch[2]==0) + if (Switch[2]!=0) + { + if (wcsicomp(Switch+1,L"FG-")==0) + ConfigDisabled=true; + else + BadSwitch(Switch); + } + else switch(toupperw(Switch[1])) { case '-': @@ -338,10 +387,15 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'L': ConvertNames=NAMES_LOWERCASE; break; + default: + BadSwitch(Switch); + break; } break; case 'D': - if (Switch[2]==0) + if (Switch[2]!=0) + BadSwitch(Switch); + else switch(toupperw(Switch[1])) { case 'S': @@ -353,6 +407,9 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'F': DeleteFiles=true; break; + default: + BadSwitch(Switch); + break; } break; case 'E': @@ -373,6 +430,13 @@ void CommandData::ProcessSwitch(const wchar *Switch) case '3': ExclPath=EXCL_ABSPATH; break; + case '4': + // Convert slashes here than before every comparison. + SlashToNative(Switch+3,ExclArcPath); + break; + default: + BadSwitch(Switch); + break; } break; default: @@ -399,13 +463,17 @@ void CommandData::ProcessSwitch(const wchar *Switch) EncryptHeaders=true; if (Switch[2]!=0) { + // We use this code for other archive formats too, so MAXPASSWORD + // instead of MAXPASSWORD_RAR. + if (wcslen(Switch+2)>=MAXPASSWORD) + uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1); Password.Set(Switch+2); cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0])); } else if (!Password.IsSet()) { - uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + uiGetPassword(UIPASSWORD_GLOBAL,L"",&Password,NULL); eprintf(L"\n"); } break; @@ -417,7 +485,7 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'I': if (wcsnicomp(Switch+1,L"LOG",3)==0) { - wcsncpyz(LogName,Switch[4]!=0 ? Switch+4:DefLogName,ASIZE(LogName)); + LogName=Switch[4]!=0 ? Switch+4:DefLogName; break; } if (wcsnicomp(Switch+1,L"SND",3)==0) @@ -435,7 +503,7 @@ void CommandData::ProcessSwitch(const wchar *Switch) } if (wcsnicomp(Switch+1,L"EML",3)==0) { - wcsncpyz(EmailTo,Switch[4]!=0 ? Switch+4:L"@",ASIZE(EmailTo)); + EmailTo=Switch[4]!=0 ? Switch+4:L"@"; break; } if (wcsicomp(Switch+1,L"M")==0) // For compatibility with pre-WinRAR 6.0 -im syntax. Replaced with -idv. @@ -542,12 +610,14 @@ void CommandData::ProcessSwitch(const wchar *Switch) } switch(toupperw(*(Str++))) { - case 'T': Type=FILTER_PPM; break; +// case 'T': Type=FILTER_TEXT; break; case 'E': Type=FILTER_E8; break; case 'D': Type=FILTER_DELTA; break; - case 'A': Type=FILTER_AUDIO; break; - case 'C': Type=FILTER_RGB; break; - case 'R': Type=FILTER_ARM; break; +// case 'A': Type=FILTER_AUDIO; break; +// case 'C': Type=FILTER_RGB; break; +// case 'R': Type=FILTER_ARM; break; + case 'L': Type=FILTER_LONGRANGE; break; + case 'X': Type=FILTER_EXHAUSTIVE; break; } if (*Str=='+' || *Str=='-') State=*(Str++)=='+' ? FILTER_FORCE:FILTER_DISABLE; @@ -557,42 +627,74 @@ void CommandData::ProcessSwitch(const wchar *Switch) } } break; - case 'M': - break; case 'D': - break; - case 'S': { - wchar StoreNames[1024]; - wcsncpyz(StoreNames,(Switch[2]==0 ? DefaultStoreList:Switch+2),ASIZE(StoreNames)); - wchar *Names=StoreNames; - while (*Names!=0) + bool SetDictLimit=toupperw(Switch[2])=='X'; + + uint64 Size=atoiw(Switch+(SetDictLimit ? 3 : 2)); + wchar LastChar=toupperw(Switch[wcslen(Switch)-1]); + if (IsDigit(LastChar)) + LastChar=SetDictLimit ? 'G':'M'; // Treat -md128 as -md128m and -mdx32 as -mdx32g. + switch(LastChar) { - wchar *End=wcschr(Names,';'); - if (End!=NULL) - *End=0; - if (*Names=='.') - Names++; - wchar Mask[NM]; - if (wcspbrk(Names,L"*?.")==NULL) - swprintf(Mask,ASIZE(Mask),L"*.%ls",Names); - else - wcsncpyz(Mask,Names,ASIZE(Mask)); - StoreArgs.AddString(Mask); - if (End==NULL) + case 'K': + Size*=1024; + break; + case 'M': + Size*=1024*1024; break; - Names=End+1; + case 'G': + Size*=1024*1024*1024; + break; + default: + BadSwitch(Switch); } + + // 2023.07.22: For 4 GB and less we also check that it is power of 2, + // so archives are compatible with RAR 5.0+. + // We allow Size>PACK_MAX_DICT here, so we can use -md[x] to unpack + // archives created by future versions with higher PACK_MAX_DICT. + uint Flags; + if ((Size=Archive::GetWinSize(Size,Flags))==0 || + Size<=0x100000000ULL && !IsPow2(Size)) + BadSwitch(Switch); + else + if (SetDictLimit) + WinSizeLimit=Size; + else + { + WinSize=Size; + } } break; + case 'E': + if (toupperw(Switch[2])=='S' && Switch[3]==0) + SkipEncrypted=true; + break; + case 'L': + if (toupperw(Switch[2])=='P') + { + UseLargePages=true; + if (!LargePageAlloc::IsPrivilegeAssigned() && LargePageAlloc::AssignConfirmation()) + { + LargePageAlloc::AssignPrivilege(); + + // Quit immediately. We do not want to interrupt the current copy + // archive processing with reboot after assigning privilege. + SetupComplete=true; + } + } + break; + case 'M': + break; + case 'S': + GetBriefMaskList(Switch[2]==0 ? DefaultStoreList:Switch+2,StoreArgs); + break; #ifdef RAR_SMP case 'T': Threads=atoiw(Switch+2); if (Threads>MaxPoolThreads || Threads<1) BadSwitch(Switch); - else - { - } break; #endif default: @@ -638,8 +740,31 @@ void CommandData::ProcessSwitch(const wchar *Switch) #ifdef SAVE_LINKS case 'L': SaveSymLinks=true; - if (toupperw(Switch[2])=='A') - AbsoluteLinks=true; + for (uint I=2;Switch[I]!=0;I++) + switch(toupperw(Switch[I])) + { + case 'A': + AbsoluteLinks=true; + break; + case '-': + SkipSymLinks=true; + break; + default: + BadSwitch(Switch); + break; + } + break; +#endif +#ifdef PROPAGATE_MOTW + case 'M': + { + MotwAllFields=Switch[2]=='1'; + const wchar *Sep=wcschr(Switch+2,'='); + if (Switch[2]=='-') + MotwList.Reset(); + else + GetBriefMaskList(Sep==nullptr ? L"*":Sep+1,MotwList); + } break; #endif #ifdef _WIN_ALL @@ -648,6 +773,10 @@ void CommandData::ProcessSwitch(const wchar *Switch) AllowIncompatNames=true; break; #endif + case 'P': + ExtrPath=Switch+2; + AddEndSlash(ExtrPath); + break; case 'R': Overwrite=OVERWRITE_AUTORENAME; break; @@ -667,11 +796,13 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'P': if (Switch[1]==0) { - uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + uiGetPassword(UIPASSWORD_GLOBAL,L"",&Password,NULL); eprintf(L"\n"); } else { + if (wcslen(Switch+1)>=MAXPASSWORD) + uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1); Password.Set(Switch+1); cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0])); } @@ -738,7 +869,52 @@ void CommandData::ProcessSwitch(const wchar *Switch) switch(toupperw(Switch[1])) { case 0: + case '=': Solid|=SOLID_NORMAL; + if (Switch[1]=='=') + { + uint Par=0; + for (const wchar *S=Switch+2;*S!=0;S++) + { + if (IsDigit(*S)) + Par=Par*10+*S-'0'; + switch(toupperw(*S)) + { + case '-': + Solid=SOLID_NONE; + break; + case 'D': + Solid|=SOLID_VOLUME_DEPENDENT; + break; + case 'E': + Solid|=SOLID_FILEEXT; + break; + case 'F': + Solid|=SOLID_COUNT; + SolidCount=Par; + break; + case 'K': + Solid|=SOLID_BLOCK_SIZE; + SolidBlockSize=Par*1024LL; + break; + case 'M': + Solid|=SOLID_BLOCK_SIZE; + SolidBlockSize=Par*1024LL*1024LL; + break; + case 'G': + Solid|=SOLID_BLOCK_SIZE; + SolidBlockSize=Par*1024LL*1024LL*1024LL; + break; + case 'R': + Solid=SOLID_RESET; + break; + case 'V': + Solid|=SOLID_VOLUME_INDEPENDENT; + break; + } + + } + } break; case '-': Solid=SOLID_NONE; @@ -752,13 +928,22 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'D': Solid|=SOLID_VOLUME_DEPENDENT; break; + case 'I': + ProhibitConsoleInput(); + // We do not assign the archive name automatically for -si + // if archive name is omitted and require the archive name to + // present always. Otherwise for"type arc.rar|rar x -si arc2.rar" + // if arc2.rar is a dummy archive name or file inside of arc.rar, + // which needs to be extracted. + UseStdin=Switch[2] ? Switch+2:L"stdin"; + break; case 'L': if (IsDigit(Switch[2])) - FileSizeLess=atoilw(Switch+2); + FileSizeLess=GetModSize(Switch+2,1); break; case 'M': if (IsDigit(Switch[2])) - FileSizeMore=atoilw(Switch+2); + FileSizeMore=GetModSize(Switch+2,1); break; case 'C': { @@ -816,12 +1001,6 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'T': switch(toupperw(Switch[1])) { - case 'K': - ArcTime=ARCTIME_KEEP; - break; - case 'L': - ArcTime=ARCTIME_LATEST; - break; case 'O': SetTimeFilters(Switch+2,true,true); break; @@ -873,8 +1052,8 @@ void CommandData::ProcessSwitch(const wchar *Switch) } break; case 'W': - wcsncpyz(TempPath,Switch+1,ASIZE(TempPath)); - AddEndSlash(TempPath,ASIZE(TempPath)); + TempPath=Switch+1; + AddEndSlash(TempPath); break; case 'Y': AllYes=true; @@ -883,10 +1062,10 @@ void CommandData::ProcessSwitch(const wchar *Switch) if (Switch[1]==0) { // If comment file is not specified, we read data from stdin. - wcsncpyz(CommentFile,L"stdin",ASIZE(CommentFile)); + CommentFile=L"stdin"; } else - wcsncpyz(CommentFile,Switch+1,ASIZE(CommentFile)); + CommentFile=Switch+1; break; case '?' : OutHelp(RARX_SUCCESS); @@ -913,34 +1092,41 @@ void CommandData::ProcessCommand() #ifndef SFX_MODULE const wchar *SingleCharCommands=L"FUADPXETK"; - if (Command[0]!=0 && Command[1]!=0 && wcschr(SingleCharCommands,Command[0])!=NULL || *ArcName==0) - OutHelp(*Command==0 ? RARX_SUCCESS:RARX_USERERROR); // Return 'success' for 'rar' without parameters. - const wchar *ArcExt=GetExt(ArcName); + // RAR -mlp command is the legitimate way to assign the required privilege. + if (Command.empty() && UseLargePages || SetupComplete) + return; + + if (Command[0]!=0 && Command[1]!=0 && wcschr(SingleCharCommands,Command[0])!=NULL || ArcName.empty()) + OutHelp(Command.empty() ? RARX_SUCCESS:RARX_USERERROR); // Return 'success' for 'rar' without parameters. + + size_t ExtPos=GetExtPos(ArcName); #ifdef _UNIX - if (ArcExt==NULL && (!FileExist(ArcName) || IsDir(GetFileAttr(ArcName)))) - wcsncatz(ArcName,L".rar",ASIZE(ArcName)); + // If we want to update an archive without extension, in Windows we can use + // "arcname." and it will be treated as "arcname". In Unix "arcname" + // and "arcname." are two different names, so we check if "arcname" exists + // and do not append ".rar", allowing user to update such archive. + if (ExtPos==std::wstring::npos && (!FileExist(ArcName) || IsDir(GetFileAttr(ArcName)))) + ArcName+=L".rar"; #else - if (ArcExt==NULL) - wcsncatz(ArcName,L".rar",ASIZE(ArcName)); + if (ExtPos==std::wstring::npos) + ArcName+=L".rar"; #endif // Treat arcname.part1 as arcname.part1.rar. - if (ArcExt!=NULL && wcsnicomp(ArcExt,L".part",5)==0 && IsDigit(ArcExt[5]) && - !FileExist(ArcName)) + if (ExtPos!=std::wstring::npos && wcsnicomp(&ArcName[ExtPos],L".part",5)==0 && + IsDigit(ArcName[ExtPos+5]) && !FileExist(ArcName)) { - wchar Name[NM]; - wcsncpyz(Name,ArcName,ASIZE(Name)); - wcsncatz(Name,L".rar",ASIZE(Name)); + std::wstring Name=ArcName+L".rar"; if (FileExist(Name)) - wcsncpyz(ArcName,Name,ASIZE(ArcName)); + ArcName=Name; } - if (wcschr(L"AFUMD",*Command)==NULL) + if (wcschr(L"AFUMD",Command[0])==NULL && UseStdin.empty()) { if (GenerateArcName) { const wchar *Mask=*GenerateMask!=0 ? GenerateMask:DefGenerateMask; - GenerateArchiveName(ArcName,ASIZE(ArcName),Mask,false); + GenerateArchiveName(ArcName,Mask,false); } StringList ArcMasks; @@ -974,12 +1160,22 @@ void CommandData::ProcessCommand() OutHelp(RARX_USERERROR); #endif } + + // Since messages usually include '\n' in the beginning, we also issue + // the final '\n'. It is especially important in Unix, where otherwise + // the shell can display the prompt on the same line as the last message. + // mprintf is blocked with -idq and if error messages had been displayed + // in this mode, we use eprintf to separate them from shell prompt. + // If nothing was displayed with -idq, we avoid the excessive empty line. if (!BareOutput) - mprintf(L"\n"); + if (MsgStream==MSG_ERRONLY && IsConsoleOutputPresent()) + eprintf(L"\n"); + else + mprintf(L"\n"); } -void CommandData::AddArcName(const wchar *Name) +void CommandData::AddArcName(const std::wstring &Name) { ArcNames.AddString(Name); } @@ -991,9 +1187,15 @@ bool CommandData::GetArcName(wchar *Name,int MaxSize) } +bool CommandData::GetArcName(std::wstring &Name) +{ + return ArcNames.GetString(Name); +} + + bool CommandData::IsSwitch(int Ch) { -#if defined(_WIN_ALL) || defined(_EMX) +#ifdef _WIN_ALL return Ch=='-' || Ch=='/'; #else return Ch=='-'; @@ -1019,7 +1221,7 @@ uint CommandData::GetExclAttr(const wchar *Str,bool &Dir) case 'V': Attr|=S_IFCHR; break; -#elif defined(_WIN_ALL) || defined(_EMX) +#elif defined(_WIN_ALL) case 'R': Attr|=0x1; break; @@ -1043,21 +1245,6 @@ uint CommandData::GetExclAttr(const wchar *Str,bool &Dir) -#ifndef SFX_MODULE -bool CommandData::CheckWinSize() -{ - // Define 0x100000000 as macro to avoid troubles with older compilers. - const uint64 MaxDictSize=INT32TO64(1,0); - // Limit the dictionary size to 4 GB. - for (uint64 I=0x10000;I<=MaxDictSize;I*=2) - if (WinSize==I) - return true; - WinSize=0x400000; - return false; -} -#endif - - #ifndef SFX_MODULE void CommandData::ReportWrongSwitches(RARFORMAT Format) { @@ -1087,3 +1274,57 @@ void CommandData::ReportWrongSwitches(RARFORMAT Format) } } #endif + + +// Get size for string with optional trailing modifiers like "100m". +int64 CommandData::GetModSize(const wchar *S,uint DefMultiplier) +{ + int64 Size=0,FloatingDivider=0; + for (uint I=0;S[I]!=0;I++) + if (IsDigit(S[I])) + { + Size=Size*10+S[I]-'0'; + FloatingDivider*=10; + } + else + if (S[I]=='.') + FloatingDivider=1; + + if (*S!=0) + { + const wchar *ModList=L"bBkKmMgGtT"; + const wchar *Mod=wcschr(ModList,S[wcslen(S)-1]); + if (Mod==nullptr) + Size*=DefMultiplier; + else + for (ptrdiff_t I=2;I<=Mod-ModList;I+=2) + Size*=((Mod-ModList)&1)!=0 ? 1000:1024; + } + if (FloatingDivider!=0) + Size/=FloatingDivider; + return Size; +} + + +// Treat the list like rar;zip as *.rar;*.zip for -ms and similar switches. +void CommandData::GetBriefMaskList(const std::wstring &Masks,StringList &Args) +{ + size_t Pos=0; + while (Pos. + std::wstring ExclArcPath; // For -ep4 switch. + std::wstring LogName; + std::wstring EmailTo; - wchar ArcName[NM]; + // Read data from stdin and store in archive under a name specified here + // when archiving. Read an archive from stdin if any non-empty string + // is specified here when extracting. + std::wstring UseStdin; StringList FileArgs; StringList ExclArgs; StringList InclArgs; StringList ArcNames; StringList StoreArgs; +#ifdef PROPAGATE_MOTW + StringList MotwList; // Extensions to assign the mark of the web. +#endif + + SecPassword Password; + + std::vector NextVolSizes; + + +#ifdef RARDLL + std::wstring DllDestName; +#endif }; #endif diff --git a/unrar/cmdfilter.cpp b/unrar/cmdfilter.cpp index d6517ceb..2591a45f 100644 --- a/unrar/cmdfilter.cpp +++ b/unrar/cmdfilter.cpp @@ -1,7 +1,7 @@ // Return 'true' if we need to exclude the file from processing as result // of -x switch. If CheckInclList is true, we also check the file against // the include list created with -n switch. -bool CommandData::ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,bool CheckInclList) +bool CommandData::ExclCheck(const std::wstring &CheckName,bool Dir,bool CheckFullPath,bool CheckInclList) { if (CheckArgs(&ExclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH)) return true; @@ -13,17 +13,21 @@ bool CommandData::ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,b } -bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,bool CheckFullPath,int MatchMode) +bool CommandData::CheckArgs(StringList *Args,bool Dir,const std::wstring &CheckName,bool CheckFullPath,int MatchMode) { - wchar *Name=ConvertPath(CheckName,NULL,0); - wchar FullName[NM]; - wchar CurMask[NM]; - *FullName=0; + std::wstring Name,FullName,CurMask; + ConvertPath(&CheckName,&Name); Args->Rewind(); - while (Args->GetString(CurMask,ASIZE(CurMask))) + while (Args->GetString(CurMask)) { - wchar *LastMaskChar=PointToLastChar(CurMask); - bool DirMask=IsPathDiv(*LastMaskChar); // Mask for directories only. +#ifdef _WIN_ALL + // 2025.09.11: Unix allows DOS slashes as a part of file name, so we do not + // convert it for Unix. In Windows we wish -xdir\file and -xdir/file both + // to exclude the file. + UnixSlashToDos(CurMask,CurMask); +#endif + wchar LastMaskChar=GetLastChar(CurMask); + bool DirMask=IsPathDiv(LastMaskChar); // Mask for directories only. if (Dir) { @@ -33,16 +37,33 @@ bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,boo // We process the directory and have the directory exclusion mask. // So let's convert "mask\" to "mask" and process it normally. - *LastMaskChar=0; + CurMask.pop_back(); } else { - // REMOVED, we want -npath\* to match empty folders too. - // If mask has wildcards in name part and does not have the trailing - // '\' character, we cannot use it for directories. - - // if (IsWildcard(PointToName(CurMask))) - // continue; + // This code doesn't allow to apply -n and -x wildcard masks without + // trailing slash to folders unless these masks are * and *.*. + // See the changes history below. + // 2023.03.26: Previously we removed this code completely to let + // 'rar a arc dir -ndir\path\*' include empty folders in 'path' too. + // But then we received an email from user not willing -x*.avi to + // exclude folders like dir.avi with non-avi files. Also rar.txt + // mentions that masks like *.avi exclude only files. Initially + // we wanted masks like -npath\* or -xpath\* to match the entire + // contents of path including empty folders and added the special + // check for "*" and "*.*". But this is not very straightforward, + // when *.* and *.avi are processed differently, especially taking + // into account that we can specify the exact folder name without + // wildcards to process it and masks like 'dir*\' can be used to + // exclude folders. So we decided to skip all usual wildcard masks + // for folders. + // 2023.11.22: We returned the special check for "*" and "*.*", + // because users expected 'rar a arc dir -xdir\*' to exclude + // everything including subfolders in 'dir'. For now we returned it + // both for -n and -x, but we can limit it to -x only if needed. + std::wstring Name=PointToName(CurMask); + if (IsWildcard(Name) && Name!=L"*" && Name!=L"*.*") + continue; } } else @@ -54,7 +75,7 @@ bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,boo // is excluded from further scanning. if (DirMask) - wcsncatz(CurMask,L"*",ASIZE(CurMask)); + CurMask+=L"*"; } #ifndef SFX_MODULE @@ -66,19 +87,20 @@ bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,boo // correctly. Moreover, removing "*\" from mask would break // the comparison, because now all names have the path. - if (*FullName==0) - ConvertNameToFull(CheckName,FullName,ASIZE(FullName)); + if (FullName.empty()) + ConvertNameToFull(CheckName,FullName); if (CmpName(CurMask,FullName,MatchMode)) return true; } else #endif { - wchar NewName[NM+2],*CurName=Name; + std::wstring CurName=Name; // Important to convert before "*\" check below, so masks like // d:*\something are processed properly. - wchar *CmpMask=ConvertPath(CurMask,NULL,0); + size_t MaskOffset=ConvertPath(&CurMask,nullptr); + std::wstring CmpMask=CurMask.substr(MaskOffset); if (CmpMask[0]=='*' && IsPathDiv(CmpMask[1])) { @@ -86,10 +108,9 @@ bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,boo // but also in the current directory. We convert the name // from 'name' to '.\name' to be matched by "*\" part even if it is // in current directory. - NewName[0]='.'; - NewName[1]=CPATHDIVIDER; - wcsncpyz(NewName+2,Name,ASIZE(NewName)-2); - CurName=NewName; + CurName=L'.'; + CurName+=CPATHDIVIDER; + CurName+=Name; } if (CmpName(CmpMask,CurName,MatchMode)) @@ -262,6 +283,8 @@ bool CommandData::TimeCheck(RarTime &ftm,RarTime &ftc,RarTime &fta) // Return 'true' if we need to exclude the file from processing. bool CommandData::SizeCheck(int64 Size) { + if (Size==INT64NDF) // If called from archive formats like bzip2, not storing the file size. + return false; if (FileSizeLess!=INT64NDF && Size>=FileSizeLess) return true; if (FileSizeMore!=INT64NDF && Size<=FileSizeMore) @@ -275,10 +298,10 @@ bool CommandData::SizeCheck(int64 Size) // Return 0 if file must not be processed or a number of matched parameter otherwise. int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType, - bool Flags,wchar *MatchedArg,uint MatchedArgSize) + bool Flags,std::wstring *MatchedArg) { - if (MatchedArg!=NULL && MatchedArgSize>0) - *MatchedArg=0; + if (MatchedArg!=NULL) + MatchedArg->clear(); bool Dir=FileHead.Dir; if (ExclCheck(FileHead.FileName,Dir,false,true)) return 0; @@ -287,23 +310,28 @@ int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchTy return 0; if ((FileHead.FileAttr & ExclFileAttr)!=0 || FileHead.Dir && ExclDir) return 0; - if (InclAttrSet && (!FileHead.Dir && (FileHead.FileAttr & InclFileAttr)==0 || - FileHead.Dir && !InclDir)) + if (InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0 && + (!FileHead.Dir || !InclDir)) return 0; if (!Dir && SizeCheck(FileHead.UnpSize)) return 0; #endif - wchar *ArgName; + std::wstring ArgName; FileArgs.Rewind(); - for (int StringCount=1;(ArgName=FileArgs.GetString())!=NULL;StringCount++) + for (int StringCount=1;FileArgs.GetString(ArgName);StringCount++) + { + // Ensure that both parameters of CmpName are either C++ strings or + // pointers, so we avoid time consuming string construction for one of + // parameters in this expensive loop. if (CmpName(ArgName,FileHead.FileName,MatchType)) { if (ExactMatch!=NULL) *ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0; if (MatchedArg!=NULL) - wcsncpyz(MatchedArg,ArgName,MatchedArgSize); + *MatchedArg=ArgName; return StringCount; } + } return 0; } diff --git a/unrar/cmdmix.cpp b/unrar/cmdmix.cpp index 3990cc18..2decf308 100644 --- a/unrar/cmdmix.cpp +++ b/unrar/cmdmix.cpp @@ -2,7 +2,7 @@ void CommandData::OutTitle() { if (BareOutput || DisableCopyright) return; -#if defined(__GNUC__) && defined(SFX_MODULE) +#ifdef SFX_MODULE mprintf(St(MCopyrightS)); #else #ifndef SILENT @@ -61,26 +61,24 @@ void CommandData::OutHelp(RAR_EXIT ExitCode) MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL, MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm, MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP, - MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU, - MCHelpSwDH,MCHelpSwEP,MCHelpSwEP3,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR, - MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwN,MCHelpSwNa,MCHelpSwNal, - MCHelpSwO,MCHelpSwOC,MCHelpSwOL,MCHelpSwOR,MCHelpSwOW,MCHelpSwP, - MCHelpSwPm,MCHelpSwR,MCHelpSwRI,MCHelpSwSC,MCHelpSwSL,MCHelpSwSM, - MCHelpSwTA,MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU, - MCHelpSwVUnr,MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal, - MCHelpSwY + MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU,MCHelpSwDH,MCHelpSwEP, + MCHelpSwEP3,MCHelpSwEP4,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR, + MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwME,MCHelpSwMLP, + MCHelpSwN,MCHelpSwNa,MCHelpSwNal,MCHelpSwO,MCHelpSwOC,MCHelpSwOL, + MCHelpSwOM,MCHelpSwOP,MCHelpSwOR,MCHelpSwOW,MCHelpSwP,MCHelpSwR, + MCHelpSwRI,MCHelpSwSC,MCHelpSwSI,MCHelpSwSL,MCHelpSwTA, + MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU,MCHelpSwVUnr, + MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal,MCHelpSwY #endif }; for (uint I=0;IGetChar()); + return UnpackRead->GetChar(); } @@ -11,8 +11,8 @@ void RangeCoder::InitDecoder(Unpack *UnpackRead) RangeCoder::UnpackRead=UnpackRead; low=code=0; - range=uint(-1); - for (int i=0;i < 4;i++) + range=0xffffffff; + for (uint i = 0; i < 4; i++) code=(code << 8) | GetChar(); } diff --git a/unrar/coder.hpp b/unrar/coder.hpp index 7b36ff21..42c3f338 100644 --- a/unrar/coder.hpp +++ b/unrar/coder.hpp @@ -11,7 +11,7 @@ class RangeCoder inline uint GetCurrentShiftCount(uint SHIFT); inline void Decode(); inline void PutChar(unsigned int c); - inline unsigned int GetChar(); + inline byte GetChar(); uint low, code, range; struct SUBRANGE diff --git a/unrar/compress.hpp b/unrar/compress.hpp index 73f7ee41..018909a0 100644 --- a/unrar/compress.hpp +++ b/unrar/compress.hpp @@ -17,13 +17,16 @@ class PackDef static const uint MAX_INC_LZ_MATCH = MAX_LZ_MATCH + 3; static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + static const uint MAX3_INC_LZ_MATCH = MAX3_LZ_MATCH + 3; static const uint LOW_DIST_REP_COUNT = 16; static const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ - static const uint DC = 64; + static const uint DCB = 64; // Base distance codes up to 4 GB. + static const uint DCX = 80; // Extended distance codes up to 1 TB. static const uint LDC = 16; static const uint RC = 44; - static const uint HUFF_TABLE_SIZE = NC + DC + RC + LDC; + static const uint HUFF_TABLE_SIZEB = NC + DCB + RC + LDC; + static const uint HUFF_TABLE_SIZEX = NC + DCX + RC + LDC; static const uint BC = 20; static const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ @@ -42,10 +45,6 @@ class PackDef // Largest alphabet size among all values listed above. static const uint LARGEST_TABLE_SIZE = 306; - enum { - CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, - CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA - }; }; @@ -53,7 +52,10 @@ enum FilterType { // These values must not be changed, because we use them directly // in RAR5 compression and decompression code. FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, - FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE + FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_TEXT, + + // These values can be changed. + FILTER_LONGRANGE,FILTER_EXHAUSTIVE,FILTER_NONE }; #endif diff --git a/unrar/consio.cpp b/unrar/consio.cpp index fedd5c05..72e7951c 100644 --- a/unrar/consio.cpp +++ b/unrar/consio.cpp @@ -3,8 +3,8 @@ static MESSAGE_TYPE MsgStream=MSG_STDOUT; static RAR_CHARSET RedirectCharset=RCH_DEFAULT; - -const int MaxMsgSize=2*NM+2048; +static bool ProhibitInput=false; +static bool ConsoleOutputPresent=false; static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false; @@ -61,47 +61,53 @@ void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset) } +void ProhibitConsoleInput() +{ + ProhibitInput=true; +} + + #ifndef SILENT static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist) { - // This buffer is for format string only, not for entire output, - // so it can be short enough. - wchar fmtw[1024]; - PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw)); + ConsoleOutputPresent=true; + + // No need for PrintfPrepareFmt here, vwstrprintf calls it. + std::wstring s=vwstrprintf(fmt,arglist); + + ReplaceEsc(s); + #ifdef _WIN_ALL - safebuf wchar Msg[MaxMsgSize]; if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected) { HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE); - vswprintf(Msg,ASIZE(Msg),fmtw,arglist); DWORD Written; if (RedirectCharset==RCH_UNICODE) - WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL); + WriteFile(hOut,s.data(),(DWORD)s.size()*sizeof(s[0]),&Written,NULL); else { // Avoid Unicode for redirect in Windows, it does not work with pipes. - safebuf char MsgA[MaxMsgSize]; + std::string MsgA; if (RedirectCharset==RCH_UTF8) - WideToUtf(Msg,MsgA,ASIZE(MsgA)); + WideToUtf(s,MsgA); else - WideToChar(Msg,MsgA,ASIZE(MsgA)); + WideToChar(s,MsgA); if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM) - CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding. + CharToOemA(&MsgA[0],&MsgA[0]); // Console tools like 'more' expect OEM encoding. // We already converted \n to \r\n above, so we use WriteFile instead // of C library to avoid unnecessary additional conversion. - WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL); + WriteFile(hOut,MsgA.data(),(DWORD)MsgA.size(),&Written,NULL); } return; } // MSVC2008 vfwprintf writes every character to console separately // and it is too slow. We use direct WriteConsole call instead. - vswprintf(Msg,ASIZE(Msg),fmtw,arglist); HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE); DWORD Written; - WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL); + WriteConsole(hOut,s.data(),(DWORD)s.size(),&Written,NULL); #else - vfwprintf(dest,fmtw,arglist); + fputws(s.c_str(),dest); // We do not use setbuf(NULL) in Unix (see comments in InitConsole). fflush(dest); #endif @@ -141,81 +147,109 @@ void eprintf(const wchar *fmt,...) #ifndef SILENT -static void GetPasswordText(wchar *Str,uint MaxLength) +static void QuitIfInputProhibited() { - if (MaxLength==0) - return; + // We cannot handle user prompts if -si is used to read file or archive data + // from stdin. + if (ProhibitInput) + { + mprintf(St(MStdinNoInput)); + ErrHandler.Exit(RARX_FATAL); + } +} + + +static void GetPasswordText(std::wstring &Str) +{ + QuitIfInputProhibited(); if (StdinRedirected) - getwstr(Str,MaxLength); // Read from pipe or redirected file. + getwstr(Str); // Read from pipe or redirected file. else { #ifdef _WIN_ALL HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE); - HANDLE hConOut=GetStdHandle(STD_OUTPUT_HANDLE); - DWORD ConInMode,ConOutMode; - DWORD Read=0; + DWORD ConInMode; GetConsoleMode(hConIn,&ConInMode); - GetConsoleMode(hConOut,&ConOutMode); - SetConsoleMode(hConIn,ENABLE_LINE_INPUT); - SetConsoleMode(hConOut,ENABLE_PROCESSED_OUTPUT|ENABLE_WRAP_AT_EOL_OUTPUT); + SetConsoleMode(hConIn,ENABLE_LINE_INPUT); // Remove ENABLE_ECHO_INPUT. + + std::vector Buf(MAXPASSWORD); + + // We prefer ReadConsole to ReadFile, so we can read Unicode input. + DWORD Read=0; + ReadConsole(hConIn,Buf.data(),(DWORD)Buf.size()-1,&Read,NULL); + Buf[Read]=0; + Str=Buf.data(); + cleandata(Buf.data(),Buf.size()*sizeof(Buf[0])); - ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL); - Str[Read]=0; SetConsoleMode(hConIn,ConInMode); - SetConsoleMode(hConOut,ConOutMode); + + // 2023.03.12: Previously we checked for presence of "\n" in entered + // passwords, supposing that truncated strings do not include it. + // We did it to read the rest of excessively long string, so it is not + // read later as the second password for -p switch. But this "\n" check + // doesn't seem to work in Windows 10 anymore and "\r" is present even + // in truncated strings. Also we increased MAXPASSWORD, so it is larger + // than MAXPASSWORD_RAR. Thus we removed this check as not working + // and not that necessary. Low level FlushConsoleInputBuffer doesn't help + // for high level ReadConsole, which in line input mode seems to store + // the rest of string in its own internal buffer. #else - char StrA[MAXPASSWORD*4]; // "*4" for multibyte UTF-8 characters. -#if defined(_EMX) || defined (__VMS) - fgets(StrA,ASIZE(StrA)-1,stdin); + std::vector StrA(MAXPASSWORD*4); // "*4" for multibyte UTF-8 characters. +#ifdef __VMS + fgets(StrA.data(),StrA.size()-1,stdin); #elif defined(__sun) - strncpyz(StrA,getpassphrase(""),ASIZE(StrA)); + strncpyz(StrA.data(),getpassphrase(""),StrA.size()); #else - strncpyz(StrA,getpass(""),ASIZE(StrA)); + strncpyz(StrA.data(),getpass(""),StrA.size()); #endif - CharToWide(StrA,Str,MaxLength); - cleandata(StrA,sizeof(StrA)); + CharToWide(StrA.data(),Str); + cleandata(StrA.data(),StrA.size()*sizeof(StrA[0])); #endif } - Str[MaxLength-1]=0; RemoveLF(Str); } #endif #ifndef SILENT -bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) +bool GetConsolePassword(UIPASSWORD_TYPE Type,const std::wstring &FileName,SecPassword *Password) { if (!StdinRedirected) uiAlarm(UIALARM_QUESTION); while (true) { - if (!StdinRedirected) +// if (!StdinRedirected) if (Type==UIPASSWORD_GLOBAL) eprintf(L"\n%s: ",St(MAskPsw)); else - eprintf(St(MAskPswFor),FileName); + eprintf(St(MAskPswFor),FileName.c_str()); - wchar PlainPsw[MAXPASSWORD]; - GetPasswordText(PlainPsw,ASIZE(PlainPsw)); - if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL) + std::wstring PlainPsw; + GetPasswordText(PlainPsw); + if (PlainPsw.empty() && Type==UIPASSWORD_GLOBAL) return false; + if (PlainPsw.size()>=MAXPASSWORD) + { + PlainPsw.erase(MAXPASSWORD-1); + uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1); + } if (!StdinRedirected && Type==UIPASSWORD_GLOBAL) { eprintf(St(MReAskPsw)); - wchar CmpStr[MAXPASSWORD]; - GetPasswordText(CmpStr,ASIZE(CmpStr)); - if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0) + std::wstring CmpStr; + GetPasswordText(CmpStr); + if (CmpStr.empty() || PlainPsw!=CmpStr) { eprintf(St(MNotMatchPsw)); - cleandata(PlainPsw,sizeof(PlainPsw)); - cleandata(CmpStr,sizeof(CmpStr)); + cleandata(&PlainPsw[0],PlainPsw.size()*sizeof(PlainPsw[0])); + cleandata(&CmpStr[0],CmpStr.size()*sizeof(CmpStr[0])); continue; } - cleandata(CmpStr,sizeof(CmpStr)); + cleandata(&CmpStr[0],CmpStr.size()*sizeof(CmpStr[0])); } - Password->Set(PlainPsw); - cleandata(PlainPsw,sizeof(PlainPsw)); + Password->Set(PlainPsw.c_str()); + cleandata(&PlainPsw[0],PlainPsw.size()*sizeof(PlainPsw[0])); break; } return true; @@ -224,12 +258,17 @@ bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword * #ifndef SILENT -bool getwstr(wchar *str,size_t n) +void getwstr(std::wstring &str) { // Print buffered prompt title function before waiting for input. fflush(stderr); - *str=0; + QuitIfInputProhibited(); + + str.clear(); + + const size_t MaxRead=MAXPATHSIZE; // Large enough to read a file name. + #if defined(_WIN_ALL) // fgetws does not work well with non-English text in Windows, // so we do not use it. @@ -237,15 +276,16 @@ bool getwstr(wchar *str,size_t n) { // fgets does not work well with pipes in Windows in our test. // Let's use files. - Array StrA(n*4); // Up to 4 UTF-8 characters per wchar_t. + std::vector StrA(MaxRead*4); // Up to 4 UTF-8 characters per wchar_t. File SrcFile; SrcFile.SetHandleType(FILE_HANDLESTD); - int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1); + SrcFile.SetLineInputMode(true); + int ReadSize=SrcFile.Read(&StrA[0],StrA.size()-1); if (ReadSize<=0) { // Looks like stdin is a null device. We can enter to infinite loop - // calling Ask(), so let's better exit. - ErrHandler.Exit(RARX_USERBREAK); + // calling Ask() or set an empty password, so let's better exit. + ErrHandler.ReadError(L"stdin"); } StrA[ReadSize]=0; @@ -254,22 +294,32 @@ bool getwstr(wchar *str,size_t n) // use "chcp" in console. But we avoid OEM to ANSI conversion, // because we also want to handle ANSI files redirection correctly, // like "rar ... < ansifile.txt". - CharToWide(&StrA[0],str,n); - cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords. + CharToWide(&StrA[0],str); + cleandata(&StrA[0],StrA.size()); // We can use this function to enter passwords. } else { + std::vector Buf(MaxRead); // Up to 4 UTF-8 characters per wchar_t. + DWORD SizeToRead=(DWORD)Buf.size()-1; + + // ReadConsole fails in Windows 7 for requested input exceeding 30 KB. + // Not certain about Windows 8, so check for Windows 10 here. + if (WinNT()<=WNT_W10) + SizeToRead=Min(SizeToRead,0x4000); + DWORD ReadSize=0; - if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0) - return false; - str[ReadSize]=0; + if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),&Buf[0],SizeToRead,&ReadSize,NULL)==0) + ErrHandler.ReadError(L"stdin"); // Unknown user input, safer to abort. + Buf[ReadSize]=0; + str=Buf.data(); } #else - if (fgetws(str,n,stdin)==NULL) - ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop. + std::vector Buf(MaxRead); // Up to 4 UTF-8 characters per wchar_t. + if (fgetws(&Buf[0],Buf.size(),stdin)==NULL) + ErrHandler.ReadError(L"stdin"); // Avoid infinite Ask() loop. + str=Buf.data(); #endif RemoveLF(str); - return true; } #endif @@ -283,22 +333,22 @@ int Ask(const wchar *AskStr) { uiAlarm(UIALARM_QUESTION); - const int MaxItems=10; + const uint MaxItems=10; wchar Item[MaxItems][40]; - int ItemKeyPos[MaxItems],NumItems=0; + uint ItemKeyPos[MaxItems],NumItems=0; - for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_')) + for (const wchar *NextItem=AskStr;NextItem!=nullptr;NextItem=wcschr(NextItem+1,'_')) { wchar *CurItem=Item[NumItems]; wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0])); wchar *EndItem=wcschr(CurItem,'_'); - if (EndItem!=NULL) + if (EndItem!=nullptr) *EndItem=0; - int KeyPos=0,CurKey; + uint KeyPos=0,CurKey; while ((CurKey=CurItem[KeyPos])!=0) { bool Found=false; - for (int I=0;I3 ? L"\n":L" "):L", "); - int KeyPos=ItemKeyPos[I]; - for (int J=0;J[{key};"{string}"p used to redefine // a keyboard key on some terminals. @@ -346,18 +396,21 @@ static bool IsCommentUnsafe(const wchar *Data,size_t Size) } -void OutComment(const wchar *Comment,size_t Size) +void OutComment(const std::wstring &Comment) { - if (IsCommentUnsafe(Comment,Size)) + if (IsCommentUnsafe(Comment)) return; const size_t MaxOutSize=0x400; - for (size_t I=0;I>1)^0xEDB88320 : (C>>1); CRCTab[I]=C; } + +#ifdef USE_NEON_CRC32 + #ifdef _APPLE + // getauxval isn't available in OS X + uint Value=0; + size_t Size=sizeof(Value); + int RetCode=sysctlbyname("hw.optional.armv8_crc32",&Value,&Size,NULL,0); + CRC_Neon=RetCode==0 && Value!=0; + #else + CRC_Neon=(getauxval(AT_HWCAP) & HWCAP_CRC32)!=0; + #endif +#endif + } @@ -37,15 +60,17 @@ static void InitTables() { InitCRC32(crc_tables[0]); +#ifdef USE_SLICING for (uint I=0;I<256;I++) // Build additional lookup tables. { uint C=crc_tables[0][I]; - for (uint J=1;J<8;J++) + for (uint J=1;J<16;J++) { C=crc_tables[0][(byte)C]^(C>>8); crc_tables[J][I]=C; } } +#endif } @@ -55,28 +80,68 @@ uint CRC32(uint StartCRC,const void *Addr,size_t Size) { byte *Data=(byte *)Addr; - // Align Data to 8 for better performance. - for (;Size>0 && ((size_t)Data & 7);Size--,Data++) +#ifdef USE_NEON_CRC32 + if (CRC_Neon) + { + for (;Size>=8;Size-=8,Data+=8) +#ifdef __clang__ + StartCRC = __builtin_arm_crc32d(StartCRC, RawGet8(Data)); +#else + StartCRC = __builtin_aarch64_crc32x(StartCRC, RawGet8(Data)); +#endif + for (;Size>0;Size--,Data++) // Process left data. +#ifdef __clang__ + StartCRC = __builtin_arm_crc32b(StartCRC, *Data); +#else + StartCRC = __builtin_aarch64_crc32b(StartCRC, *Data); +#endif + return StartCRC; + } +#endif + +#ifdef USE_SLICING + // Align Data to 16 for better performance and to avoid ALLOW_MISALIGNED + // check below. + for (;Size>0 && ((size_t)Data & 15)!=0;Size--,Data++) StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); - for (;Size>=8;Size-=8,Data+=8) + // 2023.12.06: We switched to slicing-by-16, which seems to be faster than + // slicing-by-8 on modern CPUs. Slicing-by-32 would require 32 KB for tables + // and could be limited by L1 cache size on some CPUs. + for (;Size>=16;Size-=16,Data+=16) { #ifdef BIG_ENDIAN - StartCRC ^= Data[0]|(Data[1] << 8)|(Data[2] << 16)|(Data[3] << 24); - uint NextData = Data[4]|(Data[5] << 8)|(Data[6] << 16)|(Data[7] << 24); + StartCRC ^= RawGet4(Data); + uint D1 = RawGet4(Data+4); + uint D2 = RawGet4(Data+8); + uint D3 = RawGet4(Data+12); #else + // We avoid RawGet4 here for performance reason, to access uint32 + // directly even if ALLOW_MISALIGNED isn't defined. We can do it, + // because we aligned 'Data' above. StartCRC ^= *(uint32 *) Data; - uint NextData = *(uint32 *) (Data+4); + uint D1 = *(uint32 *) (Data+4); + uint D2 = *(uint32 *) (Data+8); + uint D3 = *(uint32 *) (Data+12); #endif - StartCRC = crc_tables[7][(byte) StartCRC ] ^ - crc_tables[6][(byte)(StartCRC >> 8) ] ^ - crc_tables[5][(byte)(StartCRC >> 16)] ^ - crc_tables[4][(byte)(StartCRC >> 24)] ^ - crc_tables[3][(byte) NextData ] ^ - crc_tables[2][(byte)(NextData >> 8) ] ^ - crc_tables[1][(byte)(NextData >> 16)] ^ - crc_tables[0][(byte)(NextData >> 24)]; + StartCRC = crc_tables[15][(byte) StartCRC ] ^ + crc_tables[14][(byte)(StartCRC >> 8) ] ^ + crc_tables[13][(byte)(StartCRC >> 16)] ^ + crc_tables[12][(byte)(StartCRC >> 24)] ^ + crc_tables[11][(byte) D1 ] ^ + crc_tables[10][(byte)(D1 >> 8) ] ^ + crc_tables[ 9][(byte)(D1 >> 16)] ^ + crc_tables[ 8][(byte)(D1 >> 24)] ^ + crc_tables[ 7][(byte) D2 ] ^ + crc_tables[ 6][(byte)(D2 >> 8)] ^ + crc_tables[ 5][(byte)(D2 >> 16)] ^ + crc_tables[ 4][(byte)(D2 >> 24)] ^ + crc_tables[ 3][(byte) D3 ] ^ + crc_tables[ 2][(byte)(D3 >> 8)] ^ + crc_tables[ 1][(byte)(D3 >> 16)] ^ + crc_tables[ 0][(byte)(D3 >> 24)]; } +#endif for (;Size>0;Size--,Data++) // Process left data. StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); @@ -100,3 +165,116 @@ ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size) #endif + + +#if 0 +static void TestCRC(); +struct TestCRCStruct {TestCRCStruct() {TestCRC();exit(0);}} GlobalTesCRC; + +void TestCRC() +{ + // This function is invoked from global object and _SSE_Version is global + // and can be initialized after this function. So we explicitly initialize + // it here to enable SSE support in Blake2sp. + _SSE_Version=GetSSEVersion(); + + const uint FirstSize=300; + byte b[FirstSize]; + + if ((CRC32(0xffffffff,(byte*)"testtesttest",12)^0xffffffff)==0x44608e84) + mprintf(L"\nCRC32 test1 OK"); + else + mprintf(L"\nCRC32 test1 FAILED"); + + if (CRC32(0,(byte*)"te\x80st",5)==0xB2E5C5AE) + mprintf(L"\nCRC32 test2 OK"); + else + mprintf(L"\nCRC32 test2 FAILED"); + + for (uint I=0;I<14;I++) // Check for possible int sign extension. + b[I]=(byte)0x7f+I; + if ((CRC32(0xffffffff,b,14)^0xffffffff)==0x1DFA75DA) + mprintf(L"\nCRC32 test3 OK"); + else + mprintf(L"\nCRC32 test3 FAILED"); + + for (uint I=0;IIsSet() || Method==CRYPT_NONE) + if (Method==CRYPT_NONE || !Password->IsSet()) return false; CryptData::Method=Method; wchar PwdW[MAXPASSWORD]; Password->Get(PwdW,ASIZE(PwdW)); + + // Display this warning only when encrypting. Users complained that + // it is distracting when decrypting. It still can be useful when encrypting, + // so users do not waste time to excessively long passwords. + if (Encrypt && wcslen(PwdW)>=MAXPASSWORD_RAR) + uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD_RAR-1); + + PwdW[Min(MAXPASSWORD_RAR,MAXPASSWORD)-1]=0; // For compatibility with existing archives. + char PwdA[MAXPASSWORD]; WideToChar(PwdW,PwdA,ASIZE(PwdA)); + PwdA[Min(MAXPASSWORD_RAR,MAXPASSWORD)-1]=0; // For compatibility with existing archives. + bool Success=true; + switch(Method) { #ifndef SFX_MODULE @@ -83,12 +86,12 @@ bool CryptData::SetCryptKeys(bool Encrypt,CRYPT_METHOD Method, SetKey30(Encrypt,Password,PwdW,Salt); break; case CRYPT_RAR50: - SetKey50(Encrypt,Password,PwdW,Salt,InitV,Lg2Cnt,HashKey,PswCheck); + Success=SetKey50(Encrypt,Password,PwdW,Salt,InitV,Lg2Cnt,HashKey,PswCheck); break; } cleandata(PwdA,sizeof(PwdA)); cleandata(PwdW,sizeof(PwdW)); - return true; + return Success; } @@ -117,7 +120,7 @@ void GetRnd(byte *RndBuf,size_t BufSize) HCRYPTPROV hProvider = 0; if (CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { - Success=CryptGenRandom(hProvider, (DWORD)BufSize, RndBuf) == TRUE; + Success=CryptGenRandom(hProvider, (DWORD)BufSize, RndBuf) != FALSE; CryptReleaseContext(hProvider, 0); } #elif defined(_UNIX) diff --git a/unrar/crypt.hpp b/unrar/crypt.hpp index f6382ef5..286c8480 100644 --- a/unrar/crypt.hpp +++ b/unrar/crypt.hpp @@ -3,7 +3,8 @@ enum CRYPT_METHOD { - CRYPT_NONE,CRYPT_RAR13,CRYPT_RAR15,CRYPT_RAR20,CRYPT_RAR30,CRYPT_RAR50 + CRYPT_NONE,CRYPT_RAR13,CRYPT_RAR15,CRYPT_RAR20,CRYPT_RAR30,CRYPT_RAR50, + CRYPT_UNKNOWN }; #define SIZE_SALT50 16 @@ -30,6 +31,18 @@ class CryptData uint Lg2Count; // Log2 of PBKDF2 repetition count. byte PswCheckValue[SHA256_DIGEST_SIZE]; byte HashKeyValue[SHA256_DIGEST_SIZE]; + + KDF5CacheItem() {Clean();} + ~KDF5CacheItem() {Clean();} + + void Clean() + { + cleandata(Salt,sizeof(Salt)); + cleandata(Key,sizeof(Key)); + cleandata(&Lg2Count,sizeof(Lg2Count)); + cleandata(PswCheckValue,sizeof(PswCheckValue)); + cleandata(HashKeyValue,sizeof(HashKeyValue)); + } }; struct KDF3CacheItem @@ -39,6 +52,17 @@ class CryptData byte Key[16]; byte Init[16]; bool SaltPresent; + + KDF3CacheItem() {Clean();} + ~KDF3CacheItem() {Clean();} + + void Clean() + { + cleandata(Salt,sizeof(Salt)); + cleandata(Key,sizeof(Key)); + cleandata(Init,sizeof(Init)); + cleandata(&SaltPresent,sizeof(SaltPresent)); + } }; @@ -56,7 +80,7 @@ class CryptData void DecryptBlock20(byte *Buf); void SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt); - void SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck); + bool SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck); KDF3CacheItem KDF3Cache[4]; uint KDF3CachePos; @@ -77,17 +101,63 @@ class CryptData ushort Key15[4]; public: CryptData(); - ~CryptData(); bool SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password, const byte *Salt,const byte *InitV,uint Lg2Cnt, byte *HashKey,byte *PswCheck); - void SetAV15Encryption(); void SetCmt13Encryption(); void EncryptBlock(byte *Buf,size_t Size); void DecryptBlock(byte *Buf,size_t Size); static void SetSalt(byte *Salt,size_t SaltSize); }; + +class CheckPassword +{ + public: + enum CONFIDENCE {CONFIDENCE_HIGH,CONFIDENCE_MEDIUM,CONFIDENCE_LOW}; + virtual CONFIDENCE GetConfidence()=0; + virtual bool Check(SecPassword *Password)=0; +}; + +class RarCheckPassword:public CheckPassword +{ + private: + CryptData *Crypt; + uint Lg2Count; + byte Salt[SIZE_SALT50]; + byte InitV[SIZE_INITV]; + byte PswCheck[SIZE_PSWCHECK]; + public: + RarCheckPassword() + { + Crypt=NULL; + } + ~RarCheckPassword() + { + delete Crypt; + } + void Set(byte *Salt,byte *InitV,uint Lg2Count,byte *PswCheck) + { + if (Crypt==NULL) + Crypt=new CryptData; + memcpy(this->Salt,Salt,sizeof(this->Salt)); + memcpy(this->InitV,InitV,sizeof(this->InitV)); + this->Lg2Count=Lg2Count; + memcpy(this->PswCheck,PswCheck,sizeof(this->PswCheck)); + } + bool IsSet() {return Crypt!=NULL;} + + // RAR5 provides the higly reliable 64 bit password verification value. + CONFIDENCE GetConfidence() {return CONFIDENCE_HIGH;} + + bool Check(SecPassword *Password) + { + byte PswCheck[SIZE_PSWCHECK]; + Crypt->SetCryptKeys(false,CRYPT_RAR50,Password,Salt,InitV,Lg2Count,NULL,PswCheck); + return memcmp(PswCheck,this->PswCheck,sizeof(this->PswCheck))==0; + } +}; + void GetRnd(byte *RndBuf,size_t BufSize); void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, diff --git a/unrar/crypt1.cpp b/unrar/crypt1.cpp index 14263937..088719af 100644 --- a/unrar/crypt1.cpp +++ b/unrar/crypt1.cpp @@ -1,5 +1,3 @@ -extern uint CRCTab[256]; - void CryptData::SetKey13(const char *Password) { Key13[0]=Key13[1]=Key13[2]=0; @@ -25,22 +23,11 @@ void CryptData::SetKey15(const char *Password) { byte P=Password[I]; Key15[2]^=P^CRCTab[P]; - Key15[3]+=P+(CRCTab[P]>>16); + Key15[3]+=ushort(P+(CRCTab[P]>>16)); } } -void CryptData::SetAV15Encryption() -{ - InitCRC32(CRCTab); - Method=CRYPT_RAR15; - Key15[0]=0x4765; - Key15[1]=0x9021; - Key15[2]=0x7382; - Key15[3]=0x5215; -} - - void CryptData::SetCmt13Encryption() { Method=CRYPT_RAR13; @@ -68,7 +55,7 @@ void CryptData::Crypt15(byte *Data,size_t Count) { Key15[0]+=0x1234; Key15[1]^=CRCTab[(Key15[0] & 0x1fe)>>1]; - Key15[2]-=CRCTab[(Key15[0] & 0x1fe)>>1]>>16; + Key15[2]-=ushort(CRCTab[(Key15[0] & 0x1fe)>>1]>>16); Key15[0]^=Key15[2]; Key15[3]=rotrs(Key15[3]&0xffff,1,16)^Key15[1]; Key15[3]=rotrs(Key15[3]&0xffff,1,16); diff --git a/unrar/crypt3.cpp b/unrar/crypt3.cpp index fe3bf97b..e6e3a82c 100644 --- a/unrar/crypt3.cpp +++ b/unrar/crypt3.cpp @@ -18,8 +18,9 @@ void CryptData::SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,co if (!Cached) { byte RawPsw[2*MAXPASSWORD+SIZE_SALT30]; - WideToRaw(PwdW,RawPsw,ASIZE(RawPsw)); - size_t RawLength=2*wcslen(PwdW); + size_t PswLength=wcslen(PwdW); + size_t RawLength=2*PswLength; + WideToRaw(PwdW,PswLength,RawPsw,RawLength); if (Salt!=NULL) { memcpy(RawPsw+RawLength,Salt,SIZE_SALT30); diff --git a/unrar/crypt5.cpp b/unrar/crypt5.cpp index 7562469f..2183c8f0 100644 --- a/unrar/crypt5.cpp +++ b/unrar/crypt5.cpp @@ -21,7 +21,7 @@ static void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, sha256_context ICtx; if (ICtxOpt!=NULL && *SetIOpt) - ICtx=*ICtxOpt; // Use already calculated first block context. + ICtx=*ICtxOpt; // Use already calculated the first block context. else { // This calculation is the same for all iterations with same password. @@ -90,10 +90,10 @@ void pbkdf2(const byte *Pwd, size_t PwdLength, byte SaltData[MaxSalt+4]; memcpy(SaltData, Salt, Min(SaltLength,MaxSalt)); - SaltData[SaltLength + 0] = 0; // Salt concatenated to 1. - SaltData[SaltLength + 1] = 0; - SaltData[SaltLength + 2] = 0; - SaltData[SaltLength + 3] = 1; + SaltData[SaltLength + 0] = 0; // Block index appened to salt. + SaltData[SaltLength + 1] = 0; // + SaltData[SaltLength + 2] = 0; // Since we do not request the key width + SaltData[SaltLength + 3] = 1; // exceeding HMAC width, it is always 1. // First iteration: HMAC of password, salt and block index (1). byte U1[SHA256_DIGEST_SIZE]; @@ -128,19 +128,19 @@ void pbkdf2(const byte *Pwd, size_t PwdLength, } -void CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW, +bool CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW, const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey, byte *PswCheck) { if (Lg2Cnt>CRYPT5_KDF_LG2_COUNT_MAX) - return; + return false; byte Key[32],PswCheckValue[SHA256_DIGEST_SIZE],HashKeyValue[SHA256_DIGEST_SIZE]; bool Found=false; for (uint I=0;ILg2Count==Lg2Cnt && Item->Pwd==*Password && + if (Item->Pwd==*Password && Item->Lg2Count==Lg2Cnt && memcmp(Item->Salt,Salt,SIZE_SALT50)==0) { memcpy(Key,Item->Key,sizeof(Key)); @@ -186,6 +186,7 @@ void CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW, rin.Init(Encrypt, Key, 256, InitV); cleandata(Key,sizeof(Key)); + return true; } @@ -200,6 +201,7 @@ void ConvertHashToMAC(HashValue *Value,byte *Key) Value->CRC32=0; for (uint I=0;ICRC32^=Digest[I] << ((I & 3) * 8); + Value->CRC32&=0xffffffff; // In case the variable size is larger than 32-bit. } if (Value->Type==HASH_BLAKE2) { diff --git a/unrar/dll.cpp b/unrar/dll.cpp index 003da972..5950f839 100644 --- a/unrar/dll.cpp +++ b/unrar/dll.cpp @@ -16,8 +16,7 @@ struct DataSet HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *r) { - RAROpenArchiveDataEx rx; - memset(&rx,0,sizeof(rx)); + RAROpenArchiveDataEx rx{}; rx.ArcName=r->ArcName; rx.OpenMode=r->OpenMode; rx.CmtBuf=r->CmtBuf; @@ -32,7 +31,7 @@ HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *r) HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) { - DataSet *Data=NULL; + DataSet *Data=nullptr; try { ErrHandler.Clean(); @@ -44,22 +43,21 @@ HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) Data->Cmd.FileArgs.AddString(L"*"); Data->Cmd.KeepBroken=(r->OpFlags&ROADOF_KEEPBROKEN)!=0; - char AnsiArcName[NM]; - *AnsiArcName=0; - if (r->ArcName!=NULL) + std::string AnsiArcName; + if (r->ArcName!=nullptr) { - strncpyz(AnsiArcName,r->ArcName,ASIZE(AnsiArcName)); + AnsiArcName=r->ArcName; #ifdef _WIN_ALL if (!AreFileApisANSI()) - { - OemToCharBuffA(r->ArcName,AnsiArcName,ASIZE(AnsiArcName)); - AnsiArcName[ASIZE(AnsiArcName)-1]=0; - } + OemToExt(r->ArcName,AnsiArcName); #endif } - wchar ArcName[NM]; - GetWideName(AnsiArcName,r->ArcNameW,ArcName,ASIZE(ArcName)); + std::wstring ArcName; + if (r->ArcNameW!=nullptr && *r->ArcNameW!=0) + ArcName=r->ArcNameW; + else + CharToWide(AnsiArcName,ArcName); Data->Cmd.AddArcName(ArcName); Data->Cmd.Overwrite=OVERWRITE_ALL; @@ -75,7 +73,7 @@ HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) { r->OpenResult=ERAR_EOPEN; delete Data; - return NULL; + return nullptr; } if (!Data->Arc.IsArchive(true)) { @@ -90,7 +88,7 @@ HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) r->OpenResult=ERAR_BAD_ARCHIVE; } delete Data; - return NULL; + return nullptr; } r->Flags=0; @@ -113,35 +111,47 @@ HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) if (Data->Arc.FirstVolume) r->Flags|=ROADF_FIRSTVOLUME; - Array CmtDataW; - if (r->CmtBufSize!=0 && Data->Arc.GetComment(&CmtDataW)) + std::wstring CmtDataW; + if (r->CmtBufSize!=0 && Data->Arc.GetComment(CmtDataW)) { - if (r->CmtBufW!=NULL) + if (r->CmtBufW!=nullptr) { - CmtDataW.Push(0); - size_t Size=wcslen(&CmtDataW[0])+1; +// CmtDataW.push_back(0); + size_t Size=wcslen(CmtDataW.data())+1; r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1; r->CmtSize=(uint)Min(Size,r->CmtBufSize); - memcpy(r->CmtBufW,&CmtDataW[0],(r->CmtSize-1)*sizeof(*r->CmtBufW)); + memcpy(r->CmtBufW,CmtDataW.data(),(r->CmtSize-1)*sizeof(*r->CmtBufW)); r->CmtBufW[r->CmtSize-1]=0; } else if (r->CmtBuf!=NULL) { - Array CmtData(CmtDataW.Size()*4+1); - memset(&CmtData[0],0,CmtData.Size()); - WideToChar(&CmtDataW[0],&CmtData[0],CmtData.Size()-1); - size_t Size=strlen(&CmtData[0])+1; - - r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1; - r->CmtSize=(uint)Min(Size,r->CmtBufSize); - memcpy(r->CmtBuf,&CmtData[0],r->CmtSize-1); - r->CmtBuf[r->CmtSize-1]=0; - } + std::vector CmtData(CmtDataW.size()*4+1); + WideToChar(&CmtDataW[0],&CmtData[0],CmtData.size()-1); + size_t Size=strlen(CmtData.data())+1; + + r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1; + r->CmtSize=(uint)Min(Size,r->CmtBufSize); + memcpy(r->CmtBuf,CmtData.data(),r->CmtSize-1); + r->CmtBuf[r->CmtSize-1]=0; + } } else r->CmtState=r->CmtSize=0; + +#ifdef PROPAGATE_MOTW + if (r->MarkOfTheWeb!=nullptr) + { + Data->Cmd.MotwAllFields=r->MarkOfTheWeb[0]=='1'; + const wchar *Sep=wcschr(r->MarkOfTheWeb,'='); + if (r->MarkOfTheWeb[0]=='-') + Data->Cmd.MotwList.Reset(); + else + Data->Cmd.GetBriefMaskList(Sep==nullptr ? L"*":Sep+1,Data->Cmd.MotwList); + } +#endif + Data->Extract.ExtractArchiveInit(Data->Arc); return (HANDLE)Data; } @@ -183,8 +193,7 @@ int PASCAL RARCloseArchive(HANDLE hArcData) int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *D) { - struct RARHeaderDataEx X; - memset(&X,0,sizeof(X)); + struct RARHeaderDataEx X{}; int Code=RARReadHeaderEx(hArcData,&X); @@ -241,14 +250,18 @@ int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D) else return Code; } - wcsncpy(D->ArcNameW,Data->Arc.FileName,ASIZE(D->ArcNameW)); + wcsncpyz(D->ArcNameW,Data->Arc.FileName.c_str(),ASIZE(D->ArcNameW)); WideToChar(D->ArcNameW,D->ArcName,ASIZE(D->ArcName)); + if (D->ArcNameEx!=nullptr) + wcsncpyz(D->ArcNameEx,Data->Arc.FileName.c_str(),D->ArcNameExSize); - wcsncpy(D->FileNameW,hd->FileName,ASIZE(D->FileNameW)); + wcsncpyz(D->FileNameW,hd->FileName.c_str(),ASIZE(D->FileNameW)); WideToChar(D->FileNameW,D->FileName,ASIZE(D->FileName)); #ifdef _WIN_ALL CharToOemA(D->FileName,D->FileName); #endif + if (D->FileNameEx!=nullptr) + wcsncpyz(D->FileNameEx,hd->FileName.c_str(),D->FileNameExSize); D->Flags=0; if (hd->SplitBefore) @@ -310,7 +323,7 @@ int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D) // this RedirNameSize check sometimes later. if (hd->RedirType!=FSREDIR_NONE && D->RedirName!=NULL && D->RedirNameSize>0 && D->RedirNameSize<100000) - wcsncpyz(D->RedirName,hd->RedirName,D->RedirNameSize); + wcsncpyz(D->RedirName,hd->RedirName.c_str(),D->RedirNameSize); D->DirTarget=hd->DirTarget; /* added by me */ @@ -320,7 +333,16 @@ int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D) { return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode); } - return ERAR_SUCCESS; + if (Data->Cmd.DllError!=0) + return Data->Cmd.DllError; + // Non-fatal errors like RARX_CRC (bad header checksum) or RARX_WARNING + // can be set during a successful header read and should not be reported + // as failures here; callers use BrokenHeader flag or the listing result to + // detect them. Only propagate errors severe enough to warrant stopping. + RAR_EXIT ErrCode=ErrHandler.GetErrorCode(); + if (ErrCode==RARX_SUCCESS || ErrCode==RARX_WARNING || ErrCode==RARX_CRC) + return ERAR_SUCCESS; + return RarErrorToDll(ErrCode); } @@ -365,45 +387,43 @@ int PASCAL ProcessFile(HANDLE hArcData, int Operation, char *DestPath, { Data->Cmd.DllOpMode=Operation; - *Data->Cmd.ExtrPath=0; - *Data->Cmd.DllDestName=0; + Data->Cmd.ExtrPath.clear(); + Data->Cmd.DllDestName.clear(); if (DestPath!=NULL) { - char ExtrPathA[NM]; - strncpyz(ExtrPathA,DestPath,ASIZE(ExtrPathA)-2); + std::string ExtrPathA=DestPath; #ifdef _WIN_ALL // We must not apply OemToCharBuffA directly to DestPath, // because we do not know DestPath length and OemToCharBuffA // does not stop at 0. - OemToCharA(ExtrPathA,ExtrPathA); + OemToExt(ExtrPathA,ExtrPathA); #endif - CharToWide(ExtrPathA,Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); - AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + CharToWide(ExtrPathA,Data->Cmd.ExtrPath); + AddEndSlash(Data->Cmd.ExtrPath); } if (DestName!=NULL) { - char DestNameA[NM]; - strncpyz(DestNameA,DestName,ASIZE(DestNameA)-2); + std::string DestNameA=DestName; #ifdef _WIN_ALL // We must not apply OemToCharBuffA directly to DestName, // because we do not know DestName length and OemToCharBuffA // does not stop at 0. - OemToCharA(DestNameA,DestNameA); + OemToExt(DestNameA,DestNameA); #endif - CharToWide(DestNameA,Data->Cmd.DllDestName,ASIZE(Data->Cmd.DllDestName)); + CharToWide(DestNameA,Data->Cmd.DllDestName); } if (DestPathW!=NULL) { - wcsncpy(Data->Cmd.ExtrPath,DestPathW,ASIZE(Data->Cmd.ExtrPath)); - AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + Data->Cmd.ExtrPath=DestPathW; + AddEndSlash(Data->Cmd.ExtrPath); } if (DestNameW!=NULL) - wcsncpyz(Data->Cmd.DllDestName,DestNameW,ASIZE(Data->Cmd.DllDestName)); + Data->Cmd.DllDestName=DestNameW; - wcsncpyz(Data->Cmd.Command,Operation==RAR_EXTRACT ? L"X":L"T",ASIZE(Data->Cmd.Command)); + Data->Cmd.Command=Operation==RAR_EXTRACT ? L"X":L"T"; Data->Cmd.Test=Operation!=RAR_EXTRACT; if (Operation == RAR_EXTRACT_CHUNK) { @@ -437,6 +457,10 @@ int PASCAL ProcessFile(HANDLE hArcData, int Operation, char *DestPath, } // Now we process extra file information if any. + // It is important to do it in the same ProcessFile(), because caller + // app can rely on this behavior, for example, to overwrite + // the extracted Mark of the Web with propagated from archive + // immediately after ProcessFile() call. // // Archive can be closed if we process volumes, next volume is missing // and current one is already removed or deleted. So we need to check @@ -466,7 +490,17 @@ int PASCAL ProcessFile(HANDLE hArcData, int Operation, char *DestPath, { return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode); } - return Data->Cmd.DllError; + if (Data->Cmd.DllError!=0) + return Data->Cmd.DllError; + // Non-fatal errors like RARX_CRC (bad header checksum) or RARX_WARNING + // can be set during a successful header read and should not cause skip/list + // operations to fail. Only propagate errors severe enough to warrant stopping. + { + RAR_EXIT ErrCode=ErrHandler.GetErrorCode(); + if (ErrCode==RARX_SUCCESS || ErrCode==RARX_WARNING || ErrCode==RARX_CRC) + return ERAR_SUCCESS; + return RarErrorToDll(ErrCode); + } } @@ -534,7 +568,7 @@ void PASCAL RARSetPassword(HANDLE hArcData,char *Password) #ifndef RAR_NOCRYPT DataSet *Data=(DataSet *)hArcData; wchar PasswordW[MAXPASSWORD]; - GetWideName(Password,NULL,PasswordW,ASIZE(PasswordW)); + CharToWide(Password,PasswordW,ASIZE(PasswordW)); Data->Cmd.Password.Set(PasswordW); cleandata(PasswordW,sizeof(PasswordW)); #endif @@ -568,6 +602,8 @@ static int RarErrorToDll(RAR_EXIT ErrCode) return ERAR_BAD_PASSWORD; case RARX_SUCCESS: return ERAR_SUCCESS; // 0. + case RARX_BADARC: + return ERAR_BAD_ARCHIVE; default: return ERAR_UNKNOWN; } diff --git a/unrar/dll.hpp b/unrar/dll.hpp index 085826d8..46c9287f 100644 --- a/unrar/dll.hpp +++ b/unrar/dll.hpp @@ -19,6 +19,7 @@ #define ERAR_MISSING_PASSWORD 22 #define ERAR_EREFERENCE 23 #define ERAR_BAD_PASSWORD 24 +#define ERAR_LARGE_DICT 25 #define RAR_OM_LIST 0 #define RAR_OM_EXTRACT 1 @@ -32,7 +33,7 @@ #define RAR_VOL_ASK 0 #define RAR_VOL_NOTIFY 1 -#define RAR_DLL_VERSION 8 +#define RAR_DLL_VERSION 9 #define RAR_DLL_EXT_VERSION 1 //added by me #define RAR_HASH_NONE 0 @@ -130,9 +131,13 @@ struct RARHeaderDataEx unsigned int CtimeHigh; unsigned int AtimeLow; unsigned int AtimeHigh; + wchar_t *ArcNameEx; + unsigned int ArcNameExSize; + wchar_t *FileNameEx; + unsigned int FileNameExSize; /* removed by me: we don't need to retain binary compatibility in case new * fields are added, so we avoid wasting space here */ - /* unsigned int Reserved[988]; */ + /* unsigned int Reserved[982]; */ /* added by me */ size_t WinSize; /* window size */ }; @@ -178,13 +183,14 @@ struct RAROpenArchiveDataEx LPARAM UserData; unsigned int OpFlags; wchar_t *CmtBufW; - /* removed by me */ - /* unsigned int Reserved[25]; */ + wchar_t *MarkOfTheWeb; + /* removed by me: we don't need to retain binary compatibility */ + /* unsigned int Reserved[23]; */ }; enum UNRARCALLBACK_MESSAGES { UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD,UCM_CHANGEVOLUMEW, - UCM_NEEDPASSWORDW + UCM_NEEDPASSWORDW,UCM_LARGEDICT }; typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode); diff --git a/unrar/dll.rc b/unrar/dll.rc index d4bb3e4c..33ff097e 100644 --- a/unrar/dll.rc +++ b/unrar/dll.rc @@ -2,8 +2,8 @@ #include VS_VERSION_INFO VERSIONINFO -FILEVERSION 6, 0, 2, 3610 -PRODUCTVERSION 6, 0, 2, 3610 +FILEVERSION 7, 20, 100, 1861 +PRODUCTVERSION 7, 20, 100, 1861 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP { @@ -14,9 +14,9 @@ FILETYPE VFT_APP VALUE "CompanyName", "Alexander Roshal\0" VALUE "ProductName", "RAR decompression library\0" VALUE "FileDescription", "RAR decompression library\0" - VALUE "FileVersion", "6.0.2\0" - VALUE "ProductVersion", "6.0.2\0" - VALUE "LegalCopyright", "Copyright Alexander Roshal 1993-2020\0" + VALUE "FileVersion", "7.20.0\0" + VALUE "ProductVersion", "7.20.0\0" + VALUE "LegalCopyright", "Copyright Alexander Roshal 1993-2026\0" VALUE "OriginalFilename", "Unrar.dll\0" } } diff --git a/unrar/encname.cpp b/unrar/encname.cpp index 84731a71..d602090e 100644 --- a/unrar/encname.cpp +++ b/unrar/encname.cpp @@ -11,17 +11,16 @@ EncodeFileName::EncodeFileName() -void EncodeFileName::Decode(char *Name,size_t NameSize,byte *EncName,size_t EncSize, - wchar *NameW,size_t MaxDecSize) +void EncodeFileName::Decode(const char *Name,size_t NameSize, + const byte *EncName,size_t EncSize, + std::wstring &NameW) { size_t EncPos=0,DecPos=0; byte HighByte=EncPos=EncSize) - break; Flags=EncName[EncPos++]; FlagBits=8; } @@ -30,16 +29,20 @@ void EncodeFileName::Decode(char *Name,size_t NameSize,byte *EncName,size_t EncS case 0: if (EncPos>=EncSize) break; + // We need DecPos also for ASCII "Name", so resize() instead of push_back(). + NameW.resize(DecPos+1); NameW[DecPos++]=EncName[EncPos++]; break; case 1: if (EncPos>=EncSize) break; + NameW.resize(DecPos+1); NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8); break; case 2: if (EncPos+1>=EncSize) break; + NameW.resize(DecPos+1); NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8); EncPos+=2; break; @@ -53,17 +56,22 @@ void EncodeFileName::Decode(char *Name,size_t NameSize,byte *EncName,size_t EncS if (EncPos>=EncSize) break; byte Correction=EncName[EncPos++]; - for (Length=(Length&0x7f)+2;Length>0 && DecPos0 && DecPos0 && DecPos0 && DecPos &EncName); - byte *EncName; byte Flags; uint FlagBits; size_t FlagsPos; size_t DestSize; public: EncodeFileName(); - size_t Encode(char *Name,wchar *NameW,byte *EncName); - void Decode(char *Name,size_t NameSize,byte *EncName,size_t EncSize,wchar *NameW,size_t MaxDecSize); + void Encode(const std::string &Name,const std::wstring &NameW,std::vector &EncName); + void Decode(const char *Name,size_t NameSize,const byte *EncName,size_t EncSize,std::wstring &NameW); }; #endif diff --git a/unrar/errhnd.cpp b/unrar/errhnd.cpp index 18e91973..b347678f 100644 --- a/unrar/errhnd.cpp +++ b/unrar/errhnd.cpp @@ -26,7 +26,7 @@ void ErrorHandler::MemoryError() } -void ErrorHandler::OpenError(const wchar *FileName) +void ErrorHandler::OpenError(const std::wstring &FileName) { #ifndef SILENT OpenErrorMsg(FileName); @@ -35,7 +35,7 @@ void ErrorHandler::OpenError(const wchar *FileName) } -void ErrorHandler::CloseError(const wchar *FileName) +void ErrorHandler::CloseError(const std::wstring &FileName) { if (!UserBreak) { @@ -51,7 +51,7 @@ void ErrorHandler::CloseError(const wchar *FileName) } -void ErrorHandler::ReadError(const wchar *FileName) +void ErrorHandler::ReadError(const std::wstring &FileName) { #ifndef SILENT ReadErrorMsg(FileName); @@ -62,13 +62,13 @@ void ErrorHandler::ReadError(const wchar *FileName) } -void ErrorHandler::AskRepeatRead(const wchar *FileName,bool &Ignore,bool &Retry,bool &Quit) +void ErrorHandler::AskRepeatRead(const std::wstring &FileName,bool &Ignore,bool &Retry,bool &Quit) { SetErrorCode(RARX_READ); #if !defined(SILENT) && !defined(SFX_MODULE) if (!Silent) { - uiMsg(UIERROR_FILEREAD,UINULL,FileName); + uiMsg(UIERROR_FILEREAD,L"",FileName); SysErrMsg(); if (ReadErrIgnoreAll) Ignore=true; @@ -88,7 +88,7 @@ void ErrorHandler::AskRepeatRead(const wchar *FileName,bool &Ignore,bool &Retry, } -void ErrorHandler::WriteError(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::WriteError(const std::wstring &ArcName,const std::wstring &FileName) { #ifndef SILENT WriteErrorMsg(ArcName,FileName); @@ -100,7 +100,7 @@ void ErrorHandler::WriteError(const wchar *ArcName,const wchar *FileName) #ifdef _WIN_ALL -void ErrorHandler::WriteErrorFAT(const wchar *FileName) +void ErrorHandler::WriteErrorFAT(const std::wstring &FileName) { SysErrMsg(); uiMsg(UIERROR_NTFSREQUIRED,FileName); @@ -111,7 +111,7 @@ void ErrorHandler::WriteErrorFAT(const wchar *FileName) #endif -bool ErrorHandler::AskRepeatWrite(const wchar *FileName,bool DiskFull) +bool ErrorHandler::AskRepeatWrite(const std::wstring &FileName,bool DiskFull) { #ifndef SILENT if (!Silent) @@ -129,7 +129,7 @@ bool ErrorHandler::AskRepeatWrite(const wchar *FileName,bool DiskFull) } -void ErrorHandler::SeekError(const wchar *FileName) +void ErrorHandler::SeekError(const std::wstring &FileName) { if (!UserBreak) { @@ -144,13 +144,16 @@ void ErrorHandler::SeekError(const wchar *FileName) void ErrorHandler::GeneralErrMsg(const wchar *fmt,...) { +#ifndef RARDLL va_list arglist; va_start(arglist,fmt); - wchar Msg[1024]; - vswprintf(Msg,ASIZE(Msg),fmt,arglist); + + std::wstring Msg=vwstrprintf(fmt,arglist); uiMsg(UIERROR_GENERALERRMSG,Msg); SysErrMsg(); + va_end(arglist); +#endif } @@ -161,28 +164,31 @@ void ErrorHandler::MemoryErrorMsg() } -void ErrorHandler::OpenErrorMsg(const wchar *FileName) +void ErrorHandler::OpenErrorMsg(const std::wstring &FileName) { - OpenErrorMsg(NULL,FileName); + OpenErrorMsg(L"",FileName); } -void ErrorHandler::OpenErrorMsg(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::OpenErrorMsg(const std::wstring &ArcName,const std::wstring &FileName) { - Wait(); // Keep GUI responsive if many files cannot be opened when archiving. uiMsg(UIERROR_FILEOPEN,ArcName,FileName); SysErrMsg(); SetErrorCode(RARX_OPEN); + + // Keep GUI responsive if many files cannot be opened when archiving. + // Call after SysErrMsg to avoid modifying the error code and SysErrMsg text. + Wait(); } -void ErrorHandler::CreateErrorMsg(const wchar *FileName) +void ErrorHandler::CreateErrorMsg(const std::wstring &FileName) { - CreateErrorMsg(NULL,FileName); + CreateErrorMsg(L"",FileName); } -void ErrorHandler::CreateErrorMsg(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::CreateErrorMsg(const std::wstring &ArcName,const std::wstring &FileName) { uiMsg(UIERROR_FILECREATE,ArcName,FileName); SysErrMsg(); @@ -190,13 +196,13 @@ void ErrorHandler::CreateErrorMsg(const wchar *ArcName,const wchar *FileName) } -void ErrorHandler::ReadErrorMsg(const wchar *FileName) +void ErrorHandler::ReadErrorMsg(const std::wstring &FileName) { - ReadErrorMsg(NULL,FileName); + ReadErrorMsg(L"",FileName); } -void ErrorHandler::ReadErrorMsg(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::ReadErrorMsg(const std::wstring &ArcName,const std::wstring &FileName) { uiMsg(UIERROR_FILEREAD,ArcName,FileName); SysErrMsg(); @@ -204,7 +210,7 @@ void ErrorHandler::ReadErrorMsg(const wchar *ArcName,const wchar *FileName) } -void ErrorHandler::WriteErrorMsg(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::WriteErrorMsg(const std::wstring &ArcName,const std::wstring &FileName) { uiMsg(UIERROR_FILEWRITE,ArcName,FileName); SysErrMsg(); @@ -212,21 +218,21 @@ void ErrorHandler::WriteErrorMsg(const wchar *ArcName,const wchar *FileName) } -void ErrorHandler::ArcBrokenMsg(const wchar *ArcName) +void ErrorHandler::ArcBrokenMsg(const std::wstring &ArcName) { uiMsg(UIERROR_ARCBROKEN,ArcName); SetErrorCode(RARX_CRC); } -void ErrorHandler::ChecksumFailedMsg(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::ChecksumFailedMsg(const std::wstring &ArcName,const std::wstring &FileName) { uiMsg(UIERROR_CHECKSUM,ArcName,FileName); SetErrorCode(RARX_CRC); } -void ErrorHandler::UnknownMethodMsg(const wchar *ArcName,const wchar *FileName) +void ErrorHandler::UnknownMethodMsg(const std::wstring &ArcName,const std::wstring &FileName) { uiMsg(UIERROR_UNKNOWNMETHOD,ArcName,FileName); ErrHandler.SetErrorCode(RARX_FATAL); @@ -250,7 +256,8 @@ void ErrorHandler::SetErrorCode(RAR_EXIT Code) ExitCode=Code; break; case RARX_CRC: - if (ExitCode!=RARX_BADPWD) + // 2025.10.25: RARX_OPEN is set if next volume is missing. + if (ExitCode!=RARX_BADPWD && ExitCode!=RARX_OPEN) ExitCode=Code; break; case RARX_FATAL: @@ -329,33 +336,44 @@ void ErrorHandler::Throw(RAR_EXIT Code) if (Code==RARX_USERBREAK && !EnableBreak) return; #if !defined(SILENT) - // Do not write "aborted" when just displaying online help. - if (Code!=RARX_SUCCESS && Code!=RARX_USERERROR) - mprintf(L"\n%s\n",St(MProgAborted)); + if (Code!=RARX_SUCCESS) + if (Code==RARX_USERERROR) // Do not write "aborted" when just displaying the online help. + mprintf(L"\n"); // For consistency with other errors, which print the final "\n". + else + mprintf(L"\n%s\n",St(MProgAborted)); #endif SetErrorCode(Code); throw Code; } -bool ErrorHandler::GetSysErrMsg(wchar *Msg,size_t Size) +bool ErrorHandler::GetSysErrMsg(std::wstring &Msg) { #ifndef SILENT #ifdef _WIN_ALL int ErrType=GetLastError(); if (ErrType!=0) - return FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - NULL,ErrType,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), - Msg,(DWORD)Size,NULL)!=0; + { + wchar *Buf=nullptr; + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM| + FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL,ErrType,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), + (LPTSTR)&Buf,0,NULL)!=0) + { + Msg=Buf; + LocalFree(Buf); + return true; + } + } #endif -#if defined(_UNIX) || defined(_EMX) +#ifdef _UNIX if (errno!=0) { char *err=strerror(errno); if (err!=NULL) { - CharToWide(err,Msg,Size); + CharToWide(err,Msg); return true; } } @@ -367,32 +385,27 @@ bool ErrorHandler::GetSysErrMsg(wchar *Msg,size_t Size) void ErrorHandler::SysErrMsg() { -#if !defined(SFX_MODULE) && !defined(SILENT) - wchar Msg[1024]; - if (!GetSysErrMsg(Msg,ASIZE(Msg))) +#ifndef SILENT + std::wstring Msg; + if (!GetSysErrMsg(Msg)) return; #ifdef _WIN_ALL - wchar *CurMsg=Msg; - while (CurMsg!=NULL) // Print string with \r\n as several strings to multiple lines. + // Print string with \r\n as several strings to multiple lines. + size_t Pos=0; + while (Pos!=std::wstring::npos) { - while (*CurMsg=='\r' || *CurMsg=='\n') - CurMsg++; - if (*CurMsg==0) + while (Msg[Pos]=='\r' || Msg[Pos]=='\n') + Pos++; + if (Pos==Msg.size()) break; - wchar *EndMsg=wcschr(CurMsg,'\r'); - if (EndMsg==NULL) - EndMsg=wcschr(CurMsg,'\n'); - if (EndMsg!=NULL) - { - *EndMsg=0; - EndMsg++; - } + size_t EndPos=Msg.find_first_of(L"\r\n",Pos); + std::wstring CurMsg=Msg.substr(Pos,EndPos==std::wstring::npos ? EndPos:EndPos-Pos); uiMsg(UIERROR_SYSERRMSG,CurMsg); - CurMsg=EndMsg; + Pos=EndPos; } #endif -#if defined(_UNIX) || defined(_EMX) +#ifdef _UNIX uiMsg(UIERROR_SYSERRMSG,Msg); #endif diff --git a/unrar/errhnd.hpp b/unrar/errhnd.hpp index 06f4f616..065350f9 100644 --- a/unrar/errhnd.hpp +++ b/unrar/errhnd.hpp @@ -16,6 +16,7 @@ enum RAR_EXIT // RAR exit code. RARX_NOFILES = 10, RARX_BADPWD = 11, RARX_READ = 12, + RARX_BADARC = 13, RARX_USERBREAK = 255 }; @@ -33,26 +34,26 @@ class ErrorHandler ErrorHandler(); void Clean(); void MemoryError(); - void OpenError(const wchar *FileName); - void CloseError(const wchar *FileName); - void ReadError(const wchar *FileName); - void AskRepeatRead(const wchar *FileName,bool &Ignore,bool &Retry,bool &Quit); - void WriteError(const wchar *ArcName,const wchar *FileName); - void WriteErrorFAT(const wchar *FileName); - bool AskRepeatWrite(const wchar *FileName,bool DiskFull); - void SeekError(const wchar *FileName); + void OpenError(const std::wstring &FileName); + void CloseError(const std::wstring &FileName); + void ReadError(const std::wstring &FileName); + void AskRepeatRead(const std::wstring &FileName,bool &Ignore,bool &Retry,bool &Quit); + void WriteError(const std::wstring &ArcName,const std::wstring &FileName); + void WriteErrorFAT(const std::wstring &FileName); + bool AskRepeatWrite(const std::wstring &FileName,bool DiskFull); + void SeekError(const std::wstring &FileName); void GeneralErrMsg(const wchar *fmt,...); void MemoryErrorMsg(); - void OpenErrorMsg(const wchar *FileName); - void OpenErrorMsg(const wchar *ArcName,const wchar *FileName); - void CreateErrorMsg(const wchar *FileName); - void CreateErrorMsg(const wchar *ArcName,const wchar *FileName); - void ReadErrorMsg(const wchar *FileName); - void ReadErrorMsg(const wchar *ArcName,const wchar *FileName); - void WriteErrorMsg(const wchar *ArcName,const wchar *FileName); - void ArcBrokenMsg(const wchar *ArcName); - void ChecksumFailedMsg(const wchar *ArcName,const wchar *FileName); - void UnknownMethodMsg(const wchar *ArcName,const wchar *FileName); + void OpenErrorMsg(const std::wstring &FileName); + void OpenErrorMsg(const std::wstring &ArcName,const std::wstring &FileName); + void CreateErrorMsg(const std::wstring &FileName); + void CreateErrorMsg(const std::wstring &ArcName,const std::wstring &FileName); + void ReadErrorMsg(const std::wstring &FileName); + void ReadErrorMsg(const std::wstring &ArcName,const std::wstring &FileName); + void WriteErrorMsg(const std::wstring &ArcName,const std::wstring &FileName); + void ArcBrokenMsg(const std::wstring &ArcName); + void ChecksumFailedMsg(const std::wstring &ArcName,const std::wstring &FileName); + void UnknownMethodMsg(const std::wstring &ArcName,const std::wstring &FileName); void Exit(RAR_EXIT ExitCode); void SetErrorCode(RAR_EXIT Code); RAR_EXIT GetErrorCode() {return ExitCode;} @@ -60,7 +61,7 @@ class ErrorHandler void SetSignalHandlers(bool Enable); void Throw(RAR_EXIT Code); void SetSilent(bool Mode) {Silent=Mode;} - bool GetSysErrMsg(wchar *Msg,size_t Size); + bool GetSysErrMsg(std::wstring &Msg); void SysErrMsg(); int GetSystemErrorCode(); void SetSystemErrorCode(int Code); diff --git a/unrar/extinfo.cpp b/unrar/extinfo.cpp index 5cb90a40..b3fe7495 100644 --- a/unrar/extinfo.cpp +++ b/unrar/extinfo.cpp @@ -19,19 +19,13 @@ // RAR2 service header extra records. #ifndef SFX_MODULE -void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name) +void SetExtraInfo20(CommandData *Cmd,Archive &Arc,const std::wstring &Name) { +#ifdef _WIN_ALL if (Cmd->Test) return; switch(Arc.SubBlockHead.SubType) { -#ifdef _UNIX - case UO_HEAD: - if (Cmd->ProcessOwners) - ExtractUnixOwner20(Arc,Name); - break; -#endif -#ifdef _WIN_ALL case NTACL_HEAD: if (Cmd->ProcessOwners) ExtractACL20(Arc,Name); @@ -39,19 +33,19 @@ void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name) case STREAM_HEAD: ExtractStreams20(Arc,Name); break; -#endif } +#endif } #endif // RAR3 and RAR5 service header extra records. -void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name) +void SetExtraInfo(CommandData *Cmd,Archive &Arc,const std::wstring &Name) { #ifdef _UNIX if (!Cmd->Test && Cmd->ProcessOwners && Arc.Format==RARFMT15 && Arc.SubHead.CmpName(SUBHEAD_TYPE_UOWNER)) - ExtractUnixOwner30(Arc,Name); + ExtractUnixOwner30(Arc,Name.c_str()); #endif #ifdef _WIN_ALL if (!Cmd->Test && Cmd->ProcessOwners && Arc.SubHead.CmpName(SUBHEAD_TYPE_ACL)) @@ -63,7 +57,7 @@ void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name) // Extra data stored directly in file header. -void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name) +void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,const std::wstring &Name) { #ifdef _UNIX if (Cmd->ProcessOwners && Arc.Format==RARFMT50 && Arc.FileHead.UnixOwnerSet) @@ -74,36 +68,34 @@ void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name) -// Calculate a number of path components except \. and \.. -static int CalcAllowedDepth(const wchar *Name) +// Calculate the number of path components except \. and \.. +static int CalcAllowedDepth(const std::wstring &Name) { int AllowedDepth=0; - while (*Name!=0) - { - if (IsPathDiv(Name[0]) && Name[1]!=0 && !IsPathDiv(Name[1])) + for (size_t I=0;I=ASIZE(Path)) - return true; // It should not be that long, skip. - wcsncpyz(Path,Name,ASIZE(Path)); - for (wchar *s=Path+wcslen(Path)-1;s>Path;s--) - if (IsPathDiv(*s)) + if (Path.empty()) // So we can safely use Path.size()-1 below. + return false; + for (size_t I=Path.size()-1;I>0;I--) + if (IsPathDiv(Path[I])) { - *s=0; + Path.erase(I); FindData FD; if (FindFile::FastFind(Path,&FD,true) && (FD.IsLink || !FD.IsDir)) return true; @@ -112,7 +104,7 @@ static bool LinkInPath(const wchar *Name) } -bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName) +bool IsRelativeSymlinkSafe(CommandData *Cmd,const std::wstring &SrcName,std::wstring PrepSrcName,const std::wstring &TargetName) { // Catch root dir based /path/file paths also as stuff like \\?\. // Do not check PrepSrcName here, it can be root based if destination path @@ -122,19 +114,22 @@ bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *Pr // Number of ".." in link target. int UpLevels=0; - for (int Pos=0;*TargetName!=0;Pos++) + for (uint Pos=0;Pos "." first and "lnk1/lnk2" -> ".." next - // or "dir/lnk1" -> ".." first and "dir/lnk1/lnk2" -> ".." next. + // or "dir/lnk1" -> ".." first, "dir/lnk1/lnk2" -> ".." next and + // file "dir/lnk1/lnk2/poc.txt" last. + // Do not confuse with link chains in target, this is in link source path. + // It is important for Windows too, though this check can be omitted + // if LinksToDirs is invoked in Windows as well. if (UpLevels>0 && LinkInPath(PrepSrcName)) return false; @@ -147,12 +142,12 @@ bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *Pr // Remove the destination path from prepared name if any. We should not // count the destination path depth, because the link target must point // inside of this path, not outside of it. - size_t ExtrPathLength=wcslen(Cmd->ExtrPath); - if (ExtrPathLength>0 && wcsncmp(PrepSrcName,Cmd->ExtrPath,ExtrPathLength)==0) + size_t ExtrPathLength=Cmd->ExtrPath.size(); + if (ExtrPathLength>0 && PrepSrcName.compare(0,ExtrPathLength,Cmd->ExtrPath)==0) { - PrepSrcName+=ExtrPathLength; - while (IsPathDiv(*PrepSrcName)) - PrepSrcName++; + while (IsPathDiv(PrepSrcName[ExtrPathLength])) + ExtrPathLength++; + PrepSrcName.erase(0,ExtrPathLength); } int PrepAllowedDepth=CalcAllowedDepth(PrepSrcName); @@ -160,19 +155,30 @@ bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *Pr } -bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName) +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const std::wstring &LinkName,bool &UpLink) { + // Returning true in Uplink indicates that link target might include ".." + // and enables additional checks. It is ok to falsely return true here, + // as it implies only the minor performance penalty. But we shall always + // return true for links with ".." in target for security reason. + + UpLink=true; // Assume the target might include potentially unsafe "..". +#if defined(SAVE_LINKS) && defined(_UNIX) || defined(_WIN_ALL) + if (Arc.Format==RARFMT50) // For RAR5 archives we can check RedirName for both Unix and Windows. + UpLink=Arc.FileHead.RedirName.find(L"..")!=std::wstring::npos; +#endif + #if defined(SAVE_LINKS) && defined(_UNIX) // For RAR 3.x archives we process links even in test mode to skip link data. if (Arc.Format==RARFMT15) - return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName); + return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName.c_str(),UpLink); if (Arc.Format==RARFMT50) - return ExtractUnixLink50(Cmd,LinkName,&Arc.FileHead); -#elif defined _WIN_ALL + return ExtractUnixLink50(Cmd,LinkName.c_str(),&Arc.FileHead); +#elif defined(_WIN_ALL) // RAR 5.0 archives store link information in file header, so there is // no need to additionally test it if we do not create a file. if (Arc.Format==RARFMT50) - return CreateReparsePoint(Cmd,LinkName,&Arc.FileHead); + return CreateReparsePoint(Cmd,LinkName.c_str(),&Arc.FileHead); #endif return false; } diff --git a/unrar/extinfo.hpp b/unrar/extinfo.hpp index 2b0005da..4d0967a6 100644 --- a/unrar/extinfo.hpp +++ b/unrar/extinfo.hpp @@ -1,23 +1,19 @@ #ifndef _RAR_EXTINFO_ #define _RAR_EXTINFO_ -bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName); -bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName); +bool IsRelativeSymlinkSafe(CommandData *Cmd,const std::wstring &SrcName,std::wstring PrepSrcName,const std::wstring &TargetName); +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const std::wstring &LinkName,bool &UpLink); #ifdef _UNIX -void SetUnixOwner(Archive &Arc,const wchar *FileName); +void SetUnixOwner(Archive &Arc,const std::wstring &FileName); #endif -bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); +bool ExtractHardlink(CommandData *Cmd,const std::wstring &NameNew,const std::wstring &NameExisting); -void GetStreamNameNTFS(Archive &Arc,wchar *StreamName,size_t MaxSize); +std::wstring GetStreamNameNTFS(Archive &Arc); -#ifdef _WIN_ALL -bool SetPrivilege(LPCTSTR PrivName); -#endif - -void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name); -void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name); -void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name); +void SetExtraInfo20(CommandData *Cmd,Archive &Arc,const std::wstring &Name); +void SetExtraInfo(CommandData *Cmd,Archive &Arc,const std::wstring &Name); +void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,const std::wstring &Name); #endif diff --git a/unrar/extract.cpp b/unrar/extract.cpp index 7ee17190..d1422c88 100644 --- a/unrar/extract.cpp +++ b/unrar/extract.cpp @@ -4,9 +4,8 @@ CmdExtract::CmdExtract(CommandData *Cmd) { CmdExtract::Cmd=Cmd; - *ArcName=0; - - *DestFileName=0; + ArcAnalyzed=false; + Analyze={}; TotalFileCount=0; @@ -14,34 +13,66 @@ CmdExtract::CmdExtract(CommandData *Cmd) Buffer = NULL; BufferSize = 0; + // Common for all archives involved. Set here instead of DoExtract() + // to use in unrar.dll too. + // We enable it by default in Unix to care about the case when several + // archives are unpacked to same directory with several independent RAR runs. + // Worst case performance penalty for a lot of small files seems to be ~3%. + // 2023.09.15: Windows performance impact seems to be negligible, + // less than 0.5% when extracting mix of small files and folders. + // So for extra security we enabled it for Windows too, even though + // unlike Unix, Windows doesn't expand lnk1 in symlink targets like + // "lnk1/../dir", but converts such path to "dir". + ConvertSymlinkPaths=true; + Unp=new Unpack(&DataIO); #ifdef RAR_SMP Unp->SetThreads(Cmd->Threads); #endif + Unp->AllowLargePages(Cmd->UseLargePages); } CmdExtract::~CmdExtract() { + FreeAnalyzeData(); delete Unp; } +void CmdExtract::FreeAnalyzeData() +{ + for (size_t I=0;ICommand[0]); - FindData FD; - while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) - if (FindFile::FastFind(ArcName,&FD)) - DataIO.TotalArcSize+=FD.Size; + if (Cmd->UseStdin.empty()) + { + FindData FD; + while (Cmd->GetArcName(ArcName)) + if (FindFile::FastFind(ArcName,&FD)) + DataIO.TotalArcSize+=FD.Size; + } Cmd->ArcNames.Rewind(); - while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + for (uint ArcCount=0;Cmd->GetArcName(ArcName);ArcCount++) { if (Cmd->ManualPassword) Cmd->Password.Clean(); // Clean user entered password before processing next archive. @@ -50,12 +81,17 @@ void CmdExtract::DoExtract() UseExactVolName=false; // Must be reset here, not in ExtractArchiveInit(). while (true) { + // 2025.05.11: Add the empty line between tested archives here instead + // of printing two leading "\n" in "\n\nExtracting from", which caused + // the extra empty line after the copyright message. + if (ArcCount>0) + mprintf(L"\n"); + EXTRACT_ARC_CODE Code=ExtractArchive(); if (Code!=EXTRACT_ARC_REPEAT) break; } - if (FindFile::FastFind(ArcName,&FD)) - DataIO.ProcessedArcSize+=FD.Size; + DataIO.ProcessedArcSize+=DataIO.LastArcSize; } // Clean user entered password. Not really required, just for extra safety. @@ -65,7 +101,7 @@ void CmdExtract::DoExtract() if (TotalFileCount==0 && Cmd->Command[0]!='I' && ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password. { - if (!PasswordCancelled) + if (!SuppressNoFilesMessage) uiMsg(UIERROR_NOFILESTOEXTRACT,ArcName); // Other error codes may explain a reason of "no files extracted" clearer, @@ -87,7 +123,16 @@ void CmdExtract::DoExtract() void CmdExtract::ExtractArchiveInit(Archive &Arc) { - DataIO.UnpArcSize=Arc.FileLength(); + if (Cmd->Command[0]=='T' || Cmd->Command[0]=='I') + Cmd->Test=true; + +#ifdef PROPAGATE_MOTW + // Invoke here, so it is also supported by unrar.dll. + if (!Cmd->Test && Cmd->MotwList.ItemsCount()>0) + Arc.Motw.ReadZoneIdStream(Arc.FileName,Cmd->MotwAllFields); +#endif + + DataIO.AdjustTotalArcSize(&Arc); FileCount=0; MatchedArgs=0; @@ -103,23 +148,43 @@ void CmdExtract::ExtractArchiveInit(Archive &Arc) AllMatchesExact=true; AnySolidDataUnpackedWell=false; + ArcAnalyzed=false; + StartTime.SetCurrentTime(); + + LastCheckedSymlink.clear(); } EXTRACT_ARC_CODE CmdExtract::ExtractArchive() { Archive Arc(Cmd); - if (!Arc.WOpen(ArcName)) - return EXTRACT_ARC_NEXT; + if (!Cmd->UseStdin.empty()) + { + Arc.SetHandleType(FILE_HANDLESTD); +#ifdef USE_QOPEN + Arc.SetProhibitQOpen(true); +#endif + } + else + { + // We commented out "&& !defined(WINRAR)", because WinRAR GUI code resets + // the cache for usual test command, but not for test after archiving. +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + if (Cmd->Command[0]=='T' || Cmd->Test) + ResetFileCache(ArcName); // Reset the file cache when testing an archive. +#endif + if (!Arc.WOpen(ArcName)) + return EXTRACT_ARC_NEXT; + } if (!Arc.IsArchive(true)) { #if !defined(SFX_MODULE) && !defined(RARDLL) if (CmpExt(ArcName,L"rev")) { - wchar FirstVolName[NM]; - VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),true); + std::wstring FirstVolName; + VolNameToFirstName(ArcName,FirstVolName,true); // If several volume names from same volume set are specified // and current volume is not first in set and first volume is present @@ -133,12 +198,18 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() } #endif - mprintf(St(MNotRAR),ArcName); - + bool RarExt=false; #ifndef SFX_MODULE - if (CmpExt(ArcName,L"rar")) + RarExt=CmpExt(ArcName,L"rar"); #endif - ErrHandler.SetErrorCode(RARX_WARNING); + + if (RarExt) + uiMsg(UIERROR_BADARCHIVE,ArcName); // Non-archive .rar file. + else + mprintf(St(MNotRAR),ArcName.c_str()); // Non-archive not .rar file, likely in "rar x *.*". + + if (RarExt) + ErrHandler.SetErrorCode(RARX_BADARC); return EXTRACT_ARC_NEXT; } @@ -148,8 +219,8 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() #ifndef SFX_MODULE if (Arc.Volume && !Arc.FirstVolume && !UseExactVolName) { - wchar FirstVolName[NM]; - VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),Arc.NewNumbering); + std::wstring FirstVolName; + VolNameToFirstName(ArcName,FirstVolName,Arc.NewNumbering); // If several volume names from same volume set are specified // and current volume is not first in set and first volume is present @@ -160,32 +231,44 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() } #endif + Arc.ViewComment(); // Must be before possible EXTRACT_ARC_REPEAT. + int64 VolumeSetSize=0; // Total size of volumes after the current volume. +#ifndef SFX_MODULE + if (!ArcAnalyzed && Cmd->UseStdin.empty()) + { + AnalyzeArchive(Arc.FileName,Arc.Volume,Arc.NewNumbering); + ArcAnalyzed=true; // Avoid repeated analysis on EXTRACT_ARC_REPEAT. + } +#endif + if (Arc.Volume) { #ifndef SFX_MODULE // Try to speed up extraction for independent solid volumes by starting // extraction from non-first volume if we can. - if (!UseExactVolName && Arc.Solid && DetectStartVolume(Arc.FileName,Arc.NewNumbering)) + if (!Analyze.StartName.empty()) { + ArcName=Analyze.StartName; + Analyze.StartName.clear(); + UseExactVolName=true; return EXTRACT_ARC_REPEAT; } #endif - + // Calculate the total size of all accessible volumes. // This size is necessary to display the correct total progress indicator. - wchar NextName[NM]; - wcsncpyz(NextName,Arc.FileName,ASIZE(NextName)); + std::wstring NextName=Arc.FileName; while (true) { // First volume is already added to DataIO.TotalArcSize // in initial TotalArcSize calculation in DoExtract. // So we skip it and start from second volume. - NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); + NextVolumeName(NextName,!Arc.NewNumbering); FindData FD; if (FindFile::FastFind(NextName,&FD)) VolumeSetSize+=FD.Size; @@ -197,18 +280,21 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() ExtractArchiveInit(Arc); - if (*Cmd->Command=='T' || *Cmd->Command=='I') - Cmd->Test=true; - - if (*Cmd->Command=='I') + if (Cmd->Command[0]=='I') { Cmd->DisablePercentage=true; } else uiStartArchiveExtract(!Cmd->Test,ArcName); - Arc.ViewComment(); +#ifndef SFX_MODULE + if (Analyze.StartPos!=0) + { + Arc.Seek(Analyze.StartPos,SEEK_SET); + Analyze.StartPos=0; + } +#endif while (1) @@ -221,14 +307,11 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() if (Repeat) { // If we started extraction from not first volume and need to - // restart it from first, we must correct DataIO.TotalArcSize - // for correct total progress display. We subtract the size - // of current volume and all volumes after it and add the size - // of new (first) volume. - FindData OldArc,NewArc; - if (FindFile::FastFind(Arc.FileName,&OldArc) && - FindFile::FastFind(ArcName,&NewArc)) - DataIO.TotalArcSize-=VolumeSetSize+OldArc.Size-NewArc.Size; + // restart it from first, we must set DataIO.TotalArcSize to size + // of new first volume to display the total progress correctly. + FindData NewArc; + if (FindFile::FastFind(ArcName,&NewArc)) + DataIO.TotalArcSize=NewArc.Size; return EXTRACT_ARC_REPEAT; } else @@ -267,7 +350,14 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) return false; HEADER_TYPE HeaderType=Arc.GetHeaderType(); - if (HeaderType!=HEAD_FILE) + if (HeaderType==HEAD_FILE) + { + // Unlike Arc.FileName, ArcName might store an old volume name here. + if (Analyze.EndPos!=0 && Analyze.EndPos==Arc.CurBlockPos && + (Analyze.EndName.empty() || Analyze.EndName==Arc.FileName)) + return false; + } + else { #ifndef SFX_MODULE if (Arc.Format==RARFMT15 && HeaderType==HEAD3_OLDSERVICE && PrevProcessed) @@ -303,29 +393,30 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) // when reading an archive. But we prefer to do it here, because this // function is called directly in unrar.dll, so we fix bad parameters // passed to dll. Also we want to see real negative sizes in the listing - // of corrupt archive. To prevent uninitialized data access perform + // of corrupt archive. To prevent the uninitialized data access, perform // these checks after rejecting zero length and non-file headers above. if (Arc.FileHead.PackSize<0) Arc.FileHead.PackSize=0; if (Arc.FileHead.UnpSize<0) Arc.FileHead.UnpSize=0; + // This check duplicates Analyze.EndPos and Analyze.EndName + // in all cases except volumes on removable media. if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) return false; int MatchType=MATCH_WILDSUBPATH; bool EqualNames=false; - wchar MatchedArg[NM]; - int MatchNumber=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,0,MatchedArg,ASIZE(MatchedArg)); - bool MatchFound=MatchNumber!=0; + std::wstring MatchedArg; + bool MatchFound=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,0,&MatchedArg)!=0; #ifndef SFX_MODULE if (Cmd->ExclPath==EXCL_BASEPATH) { - wcsncpyz(Cmd->ArcPath,MatchedArg,ASIZE(Cmd->ArcPath)); - *PointToName(Cmd->ArcPath)=0; + Cmd->ArcPath=MatchedArg; + GetPathWithSep(Cmd->ArcPath,Cmd->ArcPath); if (IsWildcard(Cmd->ArcPath)) // Cannot correctly process path*\* masks here. - *Cmd->ArcPath=0; + Cmd->ArcPath.clear(); } #endif if (MatchFound && !EqualNames) @@ -336,13 +427,13 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) #if !defined(SFX_MODULE) && !defined(RARDLL) if (Arc.FileHead.SplitBefore && FirstFile && !UseExactVolName) { - wchar CurVolName[NM]; - wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName)); - GetFirstVolIfFullSet(ArcName,Arc.NewNumbering,ArcName,ASIZE(ArcName)); + std::wstring StartVolName; + GetFirstVolIfFullSet(ArcName,Arc.NewNumbering,StartVolName); - if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName)) + if (StartVolName!=ArcName && FileExist(StartVolName)) { - wcsncpyz(Cmd->ArcName,ArcName,ASIZE(ArcName)); // For GUI "Delete archive after extraction". + ArcName=StartVolName; + Cmd->ArcName=ArcName; // For GUI "Delete archive after extraction". // If first volume name does not match the current name and if such // volume name really exists, let's unpack from this first volume. Repeat=true; @@ -359,12 +450,11 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) } } #endif - wcsncpyz(ArcName,CurVolName,ASIZE(ArcName)); } #endif - wchar ArcFileName[NM]; - ConvertPath(Arc.FileHead.FileName,ArcFileName,ASIZE(ArcFileName)); + std::wstring ArcFileName; + ConvertPath(&Arc.FileHead.FileName,&ArcFileName); if (Arc.FileHead.Version) { @@ -408,7 +498,39 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) FirstFile=false; #endif - if (MatchFound || (SkipSolid=Arc.Solid)!=0) + bool RefTarget=false; + if (!MatchFound) + for (size_t I=0;ITest) // While harmless, it is useless for 't'. + { + // If reference source isn't selected, but target is selected, + // we unpack the source under the temporary name and then rename + // or copy it to target name. We do not unpack it under the target + // name immediately, because the same source can be used by multiple + // targets and it is possible that first target isn't unpacked + // for some reason. Also targets might have associated service blocks + // like ACLs. All this would complicate processing a lot. + DestFileName=!Cmd->TempPath.empty() ? Cmd->TempPath:Cmd->ExtrPath; + AddEndSlash(DestFileName); + DestFileName+=L"__tmp_reference_source_"; + MkTemp(DestFileName,nullptr); + MatchedRef.TmpName=DestFileName; + } + RefTarget=true; // Need it even for 't' to test the reference source. + break; + } + + if (Arc.FileHead.Encrypted && Cmd->SkipEncrypted) + if (Arc.Solid) + return false; // Abort the entire extraction for solid archive. + else + MatchFound=false; // Skip only the current file for non-solid archive. + + if (MatchFound || RefTarget || (SkipSolid=Arc.Solid)!=false) { // First common call of uiStartFileExtract. It is done before overwrite // prompts, so if SkipSolid state is changed below, we'll need to make @@ -416,10 +538,11 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) return false; - ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); + if (!RefTarget) + ExtrPrepareName(Arc,ArcFileName,DestFileName); // DestFileName can be set empty in case of excessive -ap switch. - ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; + ExtrFile=!SkipSolid && !DestFileName.empty() && !Arc.FileHead.SplitBefore; if ((Cmd->FreshFiles || Cmd->UpdateFiles) && (Command=='E' || Command=='X')) { @@ -453,9 +576,14 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) return !Arc.Solid; // Can try extracting next file only in non-solid archive. } - while (true) // Repeat the password prompt for wrong and empty passwords. +#ifndef RAR_NOCRYPT // For rarext.dll, Setup.SFX and unrar_nocrypt.dll. + if (Arc.FileHead.Encrypted) { - if (Arc.FileHead.Encrypted) + RarCheckPassword CheckPwd; + if (Arc.Format==RARFMT50 && Arc.FileHead.UsePswCheck && !Arc.BrokenHeader) + CheckPwd.Set(Arc.FileHead.Salt,Arc.FileHead.InitV,Arc.FileHead.Lg2Count,Arc.FileHead.PswCheck); + + while (true) // Repeat the password prompt for wrong and empty passwords. { // Stop archive extracting if user cancelled a password prompt. #ifdef RARDLL @@ -465,9 +593,9 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) return false; } #else - if (!ExtrGetPassword(Arc,ArcFileName)) + if (!ExtrGetPassword(Arc,ArcFileName,CheckPwd.IsSet() ? &CheckPwd:NULL)) { - PasswordCancelled=true; + SuppressNoFilesMessage=true; return false; } #endif @@ -478,78 +606,101 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) // and cancelled passwords differently sometimes. if (!Cmd->Password.IsSet()) { - ErrHandler.SetErrorCode(RARX_WARNING); - Cmd->DllError=ERAR_MISSING_PASSWORD; - ExtrFile=false; - } - } + ErrHandler.SetErrorCode(RARX_WARNING); + Cmd->DllError=ERAR_MISSING_PASSWORD; + ExtrFile=false; + } - // Set a password before creating the file, so we can skip creating - // in case of wrong password. - SecPassword FilePassword=Cmd->Password; + // Set a password before creating the file, so we can skip creating + // in case of wrong password. + SecPassword FilePassword=Cmd->Password; #if defined(_WIN_ALL) && !defined(SFX_MODULE) - ConvertDosPassword(Arc,FilePassword); + ConvertDosPassword(Arc,FilePassword); #endif - byte PswCheck[SIZE_PSWCHECK]; - DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, - Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, - Arc.FileHead.InitV,Arc.FileHead.Lg2Count, - Arc.FileHead.HashKey,PswCheck); - - // If header is damaged, we cannot rely on password check value, - // because it can be damaged too. - if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck && - memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 && - !Arc.BrokenHeader) - { - if (GlobalPassword) // For -p or Ctrl+P to avoid the infinite loop. - { - // This message is used by Android GUI to reset cached passwords. - // Update appropriate code if changed. - uiMsg(UIERROR_BADPSW,Arc.FileName,ArcFileName); - } - else // For passwords entered manually. + byte PswCheck[SIZE_PSWCHECK]; + bool EncSet=DataIO.SetEncryption(false,Arc.FileHead.CryptMethod, + &FilePassword,Arc.FileHead.SaltSet ? Arc.FileHead.Salt:nullptr, + Arc.FileHead.InitV,Arc.FileHead.Lg2Count, + Arc.FileHead.HashKey,PswCheck); + + // If header is damaged, we cannot rely on password check value, + // because it can be damaged too. + if (EncSet && Arc.FileHead.UsePswCheck && !Arc.BrokenHeader && + memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0) { - // This message is used by Android GUI and Windows GUI and SFX to - // reset cached passwords. Update appropriate code if changed. - uiMsg(UIWAIT_BADPSW,Arc.FileName,ArcFileName); - Cmd->Password.Clean(); + if (GlobalPassword) // For -p or Ctrl+P to avoid the infinite loop. + { + // This message is used by Android GUI to reset cached passwords. + // Update appropriate code if changed. + uiMsg(UIERROR_BADPSW,Arc.FileName,ArcFileName); + } + else // For passwords entered manually. + { + // This message is used by Android GUI and Windows GUI and SFX to + // reset cached passwords. Update appropriate code if changed. + uiMsg(UIWAIT_BADPSW,Arc.FileName,ArcFileName); + Cmd->Password.Clean(); - // Avoid new requests for unrar.dll to prevent the infinite loop - // if app always returns the same password. + // Avoid new requests for unrar.dll to prevent the infinite loop + // if app always returns the same password. #ifndef RARDLL - continue; // Request a password again. + continue; // Request a password again. #endif - } + } #ifdef RARDLL - // If we already have ERAR_EOPEN as result of missing volume, - // we should not replace it with less precise ERAR_BAD_PASSWORD. - if (Cmd->DllError!=ERAR_EOPEN) - Cmd->DllError=ERAR_BAD_PASSWORD; + // If we already have ERAR_EOPEN as result of missing volume, + // we should not replace it with less precise ERAR_BAD_PASSWORD. + if (Cmd->DllError!=ERAR_EOPEN) + Cmd->DllError=ERAR_BAD_PASSWORD; #endif - ErrHandler.SetErrorCode(RARX_BADPWD); - ExtrFile=false; + ErrHandler.SetErrorCode(RARX_BADPWD); + ExtrFile=false; + } + break; } - break; } + else + DataIO.SetEncryption(false,CRYPT_NONE,NULL,NULL,NULL,0,NULL,NULL); +#endif // RAR_NOCRYPT + + // Per file symlink conversion flag. Can be turned off in unrar.dll. + bool CurConvertSymlinkPaths=ConvertSymlinkPaths; #ifdef RARDLL - if (*Cmd->DllDestName!=0) - wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); + if (!Cmd->DllDestName.empty()) + { + DestFileName=Cmd->DllDestName; + + // If unrar.dll sets the entire destination pathname, there is no + // destination path and we can't convert symlinks, because we would + // risk converting important user or system symlinks in this case. + // If DllDestName is set, it turns off our path processing and app + // invoking the library cares about everything including safety. + CurConvertSymlinkPaths=false; + } #endif + if (ExtrFile && Command!='P' && !Cmd->Test && !Cmd->AbsoluteLinks && + CurConvertSymlinkPaths) + ExtrFile=LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); + File CurFile; bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; - if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY) + if (LinkEntry && (Arc.FileHead.RedirType!=FSREDIR_FILECOPY)) { + if (Cmd->SkipSymLinks && (Arc.FileHead.RedirType==FSREDIR_UNIXSYMLINK || + Arc.FileHead.RedirType==FSREDIR_WINSYMLINK || Arc.FileHead.RedirType==FSREDIR_JUNCTION)) + ExtrFile=false; + if (ExtrFile && Command!='P' && !Cmd->Test) { - // Overwrite prompt for symbolic and hard links. + // Overwrite prompt for symbolic and hard links and when we move + // a temporary file to the file reference instead of copying it. bool UserReject=false; - if (FileExist(DestFileName) && !UserReject) - FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + if (FileExist(DestFileName)) + FileCreate(Cmd,NULL,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); if (UserReject) ExtrFile=false; } @@ -568,7 +719,36 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) } else if (ExtrFile) // Create files and file copies (FSREDIR_FILECOPY). - ExtrFile=ExtrCreateFile(Arc,CurFile); + { + // Check the dictionary size before creating a file and issuing + // any overwrite prompts. + if (!CheckWinLimit(Arc,ArcFileName)) + return false; + + // 2025.09.03: OpenIndiana info is likely outdated, see https://www.illumos.org/issues/2000 + // Read+write mode is required to set "Compressed" attribute. + // Other than that prefer the write only mode to avoid + // OpenIndiana NAS problem with SetFileTime and read+write files. +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + bool Compressed=Cmd->SetCompressedAttr && + (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0; + bool WriteOnly=!Compressed; +#else + bool WriteOnly=true; +#endif + + ExtrFile=ExtrCreateFile(Arc,CurFile,WriteOnly); + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + // 2024.03.12: Set early to compress written data immediately. + // For 10 GB text file it was ~1.5x faster than when set after close. + + if (ExtrFile && Compressed) + SetFileCompression(CurFile.GetHandle(),true); + +#endif + + } if (!ExtrFile && Arc.Solid) { @@ -582,6 +762,9 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) // a solid archive. if (!uiStartFileExtract(ArcFileName,false,false,true)) return false; + // Check the dictionary size also for skipping files. + if (!CheckWinLimit(Arc,ArcFileName)) + return false; } if (ExtrFile) { @@ -604,21 +787,21 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) FileCount++; if (Command!='I' && !Cmd->DisableNames) if (SkipSolid) - mprintf(St(MExtrSkipFile),ArcFileName); + mprintf(St(MExtrSkipFile),ArcFileName.c_str()); else switch(Cmd->Test ? 'T':Command) // "Test" can be also enabled by -t switch. { case 'T': - mprintf(St(MExtrTestFile),ArcFileName); + mprintf(St(MExtrTestFile),ArcFileName.c_str()); break; #ifndef SFX_MODULE case 'P': - mprintf(St(MExtrPrinting),ArcFileName); + mprintf(St(MExtrPrinting),ArcFileName.c_str()); break; #endif case 'X': case 'E': - mprintf(St(MExtrFile),DestFileName); + mprintf(St(MExtrFile),DestFileName.c_str()); break; } if (!Cmd->DisablePercentage && !Cmd->DisableNames) @@ -648,7 +831,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) uint64 Preallocated=0; if (!TestMode && !Arc.BrokenHeader && Arc.FileHead.UnpSize>1000000 && - Arc.FileHead.PackSize*1024>Arc.FileHead.UnpSize && + Arc.FileHead.PackSize>Arc.FileHead.UnpSize/1024 && Arc.IsSeekable() && (Arc.FileHead.UnpSize<100000000 || Arc.FileLength()>Arc.FileHead.PackSize)) { CurFile.Prealloc(Arc.FileHead.UnpSize); @@ -666,23 +849,53 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) { - wchar NameExisting[NM]; - ExtrPrepareName(Arc,Arc.FileHead.RedirName,NameExisting,ASIZE(NameExisting)); - if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch. + std::wstring RedirName; + + // 2022.11.15: Might be needed when unpacking WinRAR 5.0 links with + // Unix RAR. WinRAR 5.0 used \ path separators here, when beginning + // from 5.10 even Windows version uses / internally and converts + // them to \ when reading FHEXTRA_REDIR. + // We must perform this conversion before ConvertPath call, + // so paths mixing different slashes like \dir1/dir2\file are + // processed correctly. + SlashToNative(Arc.FileHead.RedirName,RedirName); + + // Ensure that target is inside of destination folder. + ConvertPath(&RedirName,&RedirName); + + std::wstring NameExisting; + ExtrPrepareName(Arc,RedirName,NameExisting); + if (FileCreateMode && !NameExisting.empty()) // *NameExisting can be empty in case of excessive -ap switch. if (Type==FSREDIR_HARDLINK) - LinkSuccess=ExtractHardlink(DestFileName,NameExisting,ASIZE(NameExisting)); + LinkSuccess=ExtractHardlink(Cmd,DestFileName,NameExisting); else - LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting)); + LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,RedirName,DestFileName,NameExisting,Arc.FileHead.UnpSize); } else if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) { if (FileCreateMode) - LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName); + { + bool UpLink; + LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName,UpLink); + + ConvertSymlinkPaths|=LinkSuccess && UpLink; + + // We do not actually need to reset the cache here if we cache + // only the single last checked path, because at this point + // it will always contain the link own path and link can't + // overwrite its parent folder. But if we ever decide to cache + // several already checked paths, we'll need to reset them here. + // Otherwise if no files were created in one of such paths, + // let's say because of file create error, it might be possible + // to overwrite the path with link and avoid checks. We keep this + // code here as a reminder in case of possible modifications. + LastCheckedSymlink.clear(); // Reset cache for safety reason. + } } else { - uiMsg(UIERROR_UNKNOWNEXTRA, Arc.FileName, DestFileName); + uiMsg(UIERROR_UNKNOWNEXTRA,Arc.FileName,ArcFileName); LinkSuccess=false; } @@ -703,9 +916,20 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) UnstoreFile(DataIO,Arc.FileHead.UnpSize); else { - Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); + try + { + Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); + } + catch (std::bad_alloc) + { + if (Arc.FileHead.WinSize>=0x40000000) + uiMsg(UIERROR_EXTRDICTOUTMEM,Arc.FileName,uint(Arc.FileHead.WinSize/0x40000000+(Arc.FileHead.WinSize%0x40000000!=0 ? 1 : 0))); + throw; + } + Unp->SetDestSize(Arc.FileHead.UnpSize); #ifndef SFX_MODULE + // RAR 1.3 - 1.5 archives do not set per file solid flag. if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) Unp->DoUnpack(15,FileCount>1 && Arc.Solid); else @@ -763,59 +987,80 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) } } else - mprintf(L"\b\b\b\b\b "); - - // If we successfully unpacked a hard link, we wish to set its file - // attributes. Hard link shares file metadata with link target, - // so we do not need to set link time or owner. But when we overwrite - // an existing link, we can call PrepareToDelete(), which affects - // link target attributes as well. So we set link attributes to restore - // both target and link attributes if PrepareToDelete() changed them. - bool SetAttrOnly=LinkEntry && Arc.FileHead.RedirType==FSREDIR_HARDLINK && LinkSuccess; - + { + // We check SkipSolid to remove percent for skipped solid files only. + // We must not apply these \b to links with ShowChecksum==false + // and their possible error messages. + if (SkipSolid) + mprintf(L"\b\b\b\b\b "); + } if (!TestMode && (Command=='X' || Command=='E') && - (!LinkEntry || SetAttrOnly || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && - (!BrokenFile || Cmd->KeepBroken)) + (!LinkEntry || LinkSuccess) && (!BrokenFile || Cmd->KeepBroken)) { + // Set everything for usual files and file references. + bool SetAll=!LinkEntry || Arc.FileHead.RedirType==FSREDIR_FILECOPY; + + // Set time and adjust size for usual files and references. + // Symlink time requires the special treatment and it is set directly + // after creating a symlink. + bool SetTimeAndSize=SetAll; + + // Set file attributes for usual files, references and hard links. + // Hard link shares the file metadata with link target, so we do not + // need to set link time or owner. But when we overwrite an existing + // link, we can call PrepareToDelete(), which affects link target + // attributes too. So we set link attributes to restore both target + // and link attributes if PrepareToDelete() has changed them. + bool SetAttr=SetAll || Arc.FileHead.RedirType==FSREDIR_HARDLINK; + + // Call SetFileHeaderExtra to set Unix user and group for usual files, + // references and symlinks. Unix symlink can have its own owner data. + bool SetExtra=SetAll || Arc.FileHead.RedirType==FSREDIR_UNIXSYMLINK; + // Below we use DestFileName instead of CurFile.FileName, // so we can set file attributes also for hard links, which do not // have the open CurFile. These strings are the same for other items. - if (!SetAttrOnly) + if (SetTimeAndSize) { - // We could preallocate more space that really written to broken file + // We could preallocate more space than really written to broken file // or file with crafted header. if (Preallocated>0 && (BrokenFile || DataIO.CurUnpWrite!=Preallocated)) CurFile.Truncate(); +#ifdef PROPAGATE_MOTW + Arc.Motw.CreateZoneIdStream(DestFileName,Cmd->MotwList); +#endif CurFile.SetOpenFileTime( Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); CurFile.Close(); + } + if (SetExtra) SetFileHeaderExtra(Cmd,Arc,DestFileName); + if (SetTimeAndSize) CurFile.SetCloseFileTime( Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); - } + if (SetAttr) + { #if defined(_WIN_ALL) && !defined(SFX_MODULE) - if (Cmd->SetCompressedAttr && - (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0) - SetFileCompression(DestFileName,true); - if (Cmd->ClearArc) - Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; + if (Cmd->ClearArc) + Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; #endif - if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(DestFileName,Arc.FileHead.FileAttr)) - { - uiMsg(UIERROR_FILEATTR,Arc.FileName,DestFileName); - // Android cannot set file attributes and while UIERROR_FILEATTR - // above is handled by Android RAR silently, this call would cause - // "Operation not permitted" message for every unpacked file. - ErrHandler.SysErrMsg(); + if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(DestFileName,Arc.FileHead.FileAttr)) + { + uiMsg(UIERROR_FILEATTR,Arc.FileName,DestFileName); + // Android cannot set file attributes and while UIERROR_FILEATTR + // above is handled by Android RAR silently, this call would cause + // "Operation not permitted" message for every unpacked file. + ErrHandler.SysErrMsg(); + } } PrevProcessed=true; @@ -841,47 +1086,94 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize) { - Array Buffer(File::CopyBufferSize()); + std::vector Buffer(File::CopyBufferSize()); while (true) { - int ReadSize=DataIO.UnpRead(&Buffer[0],Buffer.Size()); + int ReadSize=DataIO.UnpRead(Buffer.data(),Buffer.size()); if (ReadSize<=0) break; int WriteSize=ReadSize0) { - DataIO.UnpWrite(&Buffer[0],WriteSize); + DataIO.UnpWrite(Buffer.data(),WriteSize); DestUnpSize-=WriteSize; } } } -bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +bool CmdExtract::ExtractFileCopy(File &New,const std::wstring &ArcName,const std::wstring &RedirName,const std::wstring &NameNew,const std::wstring &NameExisting,int64 UnpSize) { - SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. - File Existing; - if (!Existing.WOpen(NameExisting)) + if (!Existing.Open(NameExisting)) { - uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); - uiMsg(UIERROR_FILECOPYHINT,ArcName); + std::wstring TmpExisting=NameExisting; // NameExisting is 'const', so copy it here. + + bool OpenFailed=true; + // If we couldn't find the existing file, check if match is present + // in temporary reference sources list. + for (size_t I=0;IDllError=ERAR_EREFERENCE; + Cmd->DllError=ERAR_EREFERENCE; #endif - return false; + return false; + } } - Array Buffer(0x100000); + std::vector Buffer(0x100000); int64 CopySize=0; while (true) { Wait(); - int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); + int ReadSize=Existing.Read(Buffer.data(),Buffer.size()); if (ReadSize==0) break; - New.Write(&Buffer[0],ReadSize); + // Update only the current file progress in WinRAR, set the total to 0 + // to keep it as is. It looks better for WinRAR. + uiExtractProgress(CopySize,UnpSize,0,0); + + New.Write(Buffer.data(),ReadSize); CopySize+=ReadSize; } @@ -889,20 +1181,32 @@ bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar * } -void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) +void CmdExtract::ExtrPrepareName(Archive &Arc,const std::wstring &ArcFileName,std::wstring &DestName) { - wcsncpyz(DestName,Cmd->ExtrPath,DestSize); + if (Cmd->Test) + { + // Destination name conversion isn't needed for simple archive test. + // This check also allows to avoid issuing "Attempting to correct... + // Renaming..." messages in MakeNameCompatible() below for problematic + // names like aux.txt when testing an archive. + DestName=ArcFileName; + return; + } + + DestName=Cmd->ExtrPath; - if (*Cmd->ExtrPath!=0) + if (!Cmd->ExtrPath.empty()) { - wchar LastChar=*PointToLastChar(Cmd->ExtrPath); + wchar LastChar=GetLastChar(Cmd->ExtrPath); // We need IsPathDiv check here to correctly handle Unix forward slash // in the end of destination path in Windows: rar x arc dest/ + // so we call IsPathDiv first instead of just calling AddEndSlash, + // which checks for only one type of path separator. // IsDriveDiv is needed for current drive dir: rar x arc d: if (!IsPathDiv(LastChar) && !IsDriveDiv(LastChar)) { // Destination path can be without trailing slash if it come from GUI shell. - AddEndSlash(DestName,DestSize); + AddEndSlash(DestName); } } @@ -912,44 +1216,40 @@ void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *De switch(Cmd->AppendArcNameToPath) { case APPENDARCNAME_DESTPATH: // To subdir of destination path. - wcsncatz(DestName,PointToName(Arc.FirstVolumeName),DestSize); - SetExt(DestName,NULL,DestSize); + DestName+=PointToName(Arc.FirstVolumeName); + RemoveExt(DestName); break; case APPENDARCNAME_OWNSUBDIR: // To subdir of archive own dir. - wcsncpyz(DestName,Arc.FirstVolumeName,DestSize); - SetExt(DestName,NULL,DestSize); + DestName=Arc.FirstVolumeName; + RemoveExt(DestName); break; case APPENDARCNAME_OWNDIR: // To archive own dir. - wcsncpyz(DestName,Arc.FirstVolumeName,DestSize); + DestName=Arc.FirstVolumeName; RemoveNameFromPath(DestName); break; } - AddEndSlash(DestName,DestSize); + AddEndSlash(DestName); } #endif - + // We need to modify the name below and ArcFileName is const. + std::wstring CurName=ArcFileName; #ifndef SFX_MODULE - size_t ArcPathLength=wcslen(Cmd->ArcPath); + std::wstring &ArcPath=!Cmd->ExclArcPath.empty() ? Cmd->ExclArcPath:Cmd->ArcPath; + size_t ArcPathLength=ArcPath.size(); if (ArcPathLength>0) { - size_t NameLength=wcslen(ArcFileName); - - // Earlier we compared lengths only here, but then noticed a cosmetic bug - // in WinRAR. When extracting a file reference from subfolder with - // "Extract relative paths", so WinRAR sets ArcPath, if reference target - // is missing, error message removed ArcPath both from reference and target - // names. If target was stored in another folder, its name looked wrong. - if (NameLength>=ArcPathLength && - wcsnicompc(Cmd->ArcPath,ArcFileName,ArcPathLength)==0 && - (IsPathDiv(Cmd->ArcPath[ArcPathLength-1]) || - IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0)) + size_t NameLength=CurName.size(); + if (NameLength>=ArcPathLength && wcsnicompc(ArcPath,CurName,ArcPathLength)==0 && + (IsPathDiv(ArcPath[ArcPathLength-1]) || + IsPathDiv(CurName[ArcPathLength]) || CurName[ArcPathLength]==0)) { - ArcFileName+=Min(ArcPathLength,NameLength); - while (IsPathDiv(*ArcFileName)) - ArcFileName++; - if (*ArcFileName==0) // Excessive -ap switch. + size_t Pos=Min(ArcPathLength,NameLength); + while (PosExclPath==EXCL_ABSPATH && Command=='X' && IsDriveDiv(':'); - // We do not use any user specified destination paths when extracting - // absolute paths in -ep3 mode. if (AbsPaths) - *DestName=0; + { + // We do not use a user specified destination path when extracting + // absolute paths in -ep3 mode. + wchar DiskLetter=toupperw(CurName[0]); + if (CurName[1]=='_' && IsPathDiv(CurName[2]) && DiskLetter>='A' && DiskLetter<='Z') + DestName=CurName.substr(0,1) + L':' + CurName.substr(2); + else + if (CurName[0]=='_' && CurName[1]=='_') + DestName=std::wstring(2,CPATHDIVIDER) + CurName.substr(2); + else + AbsPaths=false; // Apply the destination path even with -ep3 for not absolute path. + } if (Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) - wcsncatz(DestName,PointToName(ArcFileName),DestSize); - else - wcsncatz(DestName,ArcFileName,DestSize); + CurName=PointToName(CurName); + if (!AbsPaths) + DestName+=CurName; #ifdef _WIN_ALL // Must do after Cmd->ArcPath processing above, so file name and arc path @@ -976,22 +1285,6 @@ void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *De if (!Cmd->AllowIncompatNames) MakeNameCompatible(DestName); #endif - - wchar DiskLetter=toupperw(DestName[0]); - - if (AbsPaths) - { - if (DestName[1]=='_' && IsPathDiv(DestName[2]) && - DiskLetter>='A' && DiskLetter<='Z') - DestName[1]=':'; - else - if (DestName[0]=='_' && DestName[1]=='_') - { - // Convert __server\share to \\server\share. - DestName[0]=CPATHDIVIDER; - DestName[1]=CPATHDIVIDER; - } - } } @@ -1012,7 +1305,7 @@ bool CmdExtract::ExtrDllGetPassword() *PasswordA=0; if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) *PasswordA=0; - GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); + CharToWide(PasswordA,PasswordW,ASIZE(PasswordW)); cleandata(PasswordA,sizeof(PasswordA)); } Cmd->Password.Set(PasswordW); @@ -1028,18 +1321,14 @@ bool CmdExtract::ExtrDllGetPassword() #ifndef RARDLL -bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) +bool CmdExtract::ExtrGetPassword(Archive &Arc,const std::wstring &ArcFileName,RarCheckPassword *CheckPwd) { if (!Cmd->Password.IsSet()) { - if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)/* || !Cmd->Password.IsSet()*/) + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password,CheckPwd)/* || !Cmd->Password.IsSet()*/) { // Suppress "test is ok" message if user cancelled the password prompt. -// 2019.03.23: If some archives are tested ok and prompt is cancelled for others, -// do we really need to suppress "test is ok"? Also if we set an empty password -// and "Use for all archives" in WinRAR Ctrl+P and skip some encrypted archives. -// We commented out this UIERROR_INCERRCOUNT for now. -// uiMsg(UIERROR_INCERRCOUNT); + uiMsg(UIERROR_INCERRCOUNT); return false; } Cmd->ManualPassword=true; @@ -1048,13 +1337,13 @@ bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) else if (!GlobalPassword && !Arc.FileHead.Solid) { - eprintf(St(MUseCurPsw),ArcFileName); + eprintf(St(MUseCurPsw),ArcFileName.c_str()); switch(Cmd->AllYes ? 1 : Ask(St(MYesNoAll))) { case -1: ErrHandler.Exit(RARX_USERBREAK); case 2: - if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)) + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password,CheckPwd)) return false; break; case 3: @@ -1089,14 +1378,14 @@ void CmdExtract::ConvertDosPassword(Archive &Arc,SecPassword &DestPwd) #endif -void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) +void CmdExtract::ExtrCreateDir(Archive &Arc,const std::wstring &ArcFileName) { if (Cmd->Test) { if (!Cmd->DisableNames) { - mprintf(St(MExtrTestFile),ArcFileName); - mprintf(L" %s",St(MOk)); + mprintf(St(MExtrTestFile),ArcFileName.c_str()); + mprintf(L" %s",St(MOk)); } return; } @@ -1111,24 +1400,28 @@ void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) // File with name same as this directory exists. Propose user // to overwrite it. bool UserReject; - FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + FileCreate(Cmd,NULL,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); DirExist=false; } if (!DirExist) { - CreatePath(DestFileName,true); + CreatePath(DestFileName,true,Cmd->DisableNames); MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); - if (MDCode!=MKDIR_SUCCESS) + if (MDCode!=MKDIR_SUCCESS && !IsNameUsable(DestFileName)) { - wchar OrigName[ASIZE(DestFileName)]; - wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); + uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName); + std::wstring OrigName=DestFileName; MakeNameUsable(DestFileName,true); - CreatePath(DestFileName,true); - MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); #ifndef SFX_MODULE - if (MDCode==MKDIR_SUCCESS) - uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); + uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); #endif + DirExist=FileExist(DestFileName) && IsDir(GetFileAttr(DestFileName)); + if (!DirExist && (Cmd->AbsoluteLinks || !ConvertSymlinkPaths || + LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink))) + { + CreatePath(DestFileName,true,Cmd->DisableNames); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + } } } } @@ -1136,8 +1429,8 @@ void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) { if (!Cmd->DisableNames) { - mprintf(St(MCreatDir),DestFileName); - mprintf(L" %s",St(MOk)); + mprintf(St(MCreatDir),DestFileName.c_str()); + mprintf(L" %s",St(MOk)); } PrevProcessed=true; } @@ -1173,7 +1466,7 @@ void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) } -bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) +bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile,bool WriteOnly) { bool Success=true; wchar Command=Cmd->Command[0]; @@ -1184,9 +1477,7 @@ bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) if ((Command=='E' || Command=='X') && !Cmd->Test) { bool UserReject; - // Specify "write only" mode to avoid OpenIndiana NAS problems - // with SetFileTime and read+write files. - if (!FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + if (!FileCreate(Cmd,&CurFile,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,WriteOnly)) { Success=false; if (!UserReject) @@ -1202,21 +1493,24 @@ bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) { uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName); - wchar OrigName[ASIZE(DestFileName)]; - wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); + std::wstring OrigName=DestFileName; MakeNameUsable(DestFileName,true); - CreatePath(DestFileName,true); - if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + if (Cmd->AbsoluteLinks || !ConvertSymlinkPaths || + LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink)) { + CreatePath(DestFileName,true,Cmd->DisableNames); + if (FileCreate(Cmd,&CurFile,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + { #ifndef SFX_MODULE - uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); + uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); #endif - Success=true; + Success=true; + } + else + ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); } - else - ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); } } } @@ -1225,11 +1519,11 @@ bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) } -bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) +bool CmdExtract::CheckUnpVer(Archive &Arc,const std::wstring &ArcFileName) { bool WrongVer; - if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 archives. - WrongVer=Arc.FileHead.UnpVer>VER_UNPACK5; + if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 and 7.0 archives. + WrongVer=Arc.FileHead.UnpVer>VER_UNPACK7; else { #ifdef SFX_MODULE // SFX can unpack only RAR 2.9 archives. @@ -1243,41 +1537,75 @@ bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) if (Arc.FileHead.Method==0) WrongVer=false; + // Can't unpack the unknown encryption even for stored files. + if (Arc.FileHead.CryptMethod==CRYPT_UNKNOWN) + WrongVer=true; + if (WrongVer) { ErrHandler.UnknownMethodMsg(Arc.FileName,ArcFileName); - uiMsg(UIERROR_NEWERRAR,Arc.FileName); + // No need to suggest a new version if it is just a broken archive. + if (!Arc.BrokenHeader) + uiMsg(UIERROR_NEWERRAR,Arc.FileName); } return !WrongVer; } #ifndef SFX_MODULE -// To speed up solid volumes extraction, try to find a non-first start volume, -// which still allows to unpack all files. It is possible for independent -// solid volumes with solid statistics reset in the beginning. -bool CmdExtract::DetectStartVolume(const wchar *VolName,bool NewNumbering) +// Find non-matched reference sources in solid and non-solid archives. +// Detect the optimal start position for semi-solid archives +// and optimal start volume for independent solid volumes. +// +// Alternatively we could collect references while extracting an archive +// and perform the second extraction pass for references only. +// But it would be slower for solid archives than scaning headers +// in first pass and extracting everything in second, as implemented now. +// +void CmdExtract::AnalyzeArchive(const std::wstring &ArcName,bool Volume,bool NewNumbering) { + FreeAnalyzeData(); // If processing non-first archive in multiple archives set. + wchar *ArgName=Cmd->FileArgs.GetString(); Cmd->FileArgs.Rewind(); if (ArgName!=NULL && (wcscmp(ArgName,L"*")==0 || wcscmp(ArgName,L"*.*")==0)) - return false; // No need to check further for * and *.* masks. + return; // No need to check further for * and *.* masks. - wchar StartName[NM]; - *StartName=0; - // Start search from first volume if all volumes preceding current are available. - wchar NextName[NM]; - GetFirstVolIfFullSet(VolName,NewNumbering,NextName,ASIZE(NextName)); + std::wstring NextName; + if (Volume) + GetFirstVolIfFullSet(ArcName,NewNumbering,NextName); + else + NextName=ArcName; + + bool MatchFound=false; + bool PrevMatched=false; + bool OpenNext=false; - bool Matched=false; - while (!Matched) + bool FirstVolume=true; + + // We shall set FirstFile once for all volumes and not for each volume. + // So we do not reuse the outdated Analyze.StartPos from previous volume + // if extracted file resides completely in the beginning of current one. + bool FirstFile=true; + + while (true) { Archive Arc(Cmd); - if (!Arc.Open(NextName) || !Arc.IsArchive(false) || !Arc.Volume) + if (!Arc.Open(NextName) || !Arc.IsArchive(false)) + { + if (OpenNext) + { + // If we couldn't open trailing volumes, we can't set early exit + // parameters. It is possible that some volume are on removable media + // and will be provided by user when extracting. + Analyze.EndName.clear(); + Analyze.EndPos=0; + } break; + } - bool OpenNext=false; + OpenNext=false; while (Arc.ReadHeader()>0) { Wait(); @@ -1290,17 +1618,90 @@ bool CmdExtract::DetectStartVolume(const wchar *VolName,bool NewNumbering) } if (HeaderType==HEAD_FILE) { + if ((Arc.Format==RARFMT14 || Arc.Format==RARFMT15) && Arc.FileHead.UnpVer<=15) + { + // RAR versions earlier than 2.0 do not set per file solid flag. + // They have only the global archive solid flag, so we can't + // reliably analyze them here. + OpenNext=false; + break; + } + if (!Arc.FileHead.SplitBefore) { - if (!Arc.FileHead.Solid) // Can start extraction from here. - wcsncpyz(StartName,NextName,ASIZE(StartName)); + if (!MatchFound && !Arc.FileHead.Solid && !Arc.FileHead.Dir && + Arc.FileHead.RedirType==FSREDIR_NONE && Arc.FileHead.Method!=0) + { + // Can start extraction from here. + // We would gain nothing and unnecessarily complicate extraction + // if we set StartName for first volume or StartPos for first + // archived file. + if (!FirstVolume) + Analyze.StartName=NextName; + + // We shall set FirstFile once for all volumes for this code + // to work properly. Alternatively we could append + // "|| Analyze.StartPos!=0" to the condition, so we do not reuse + // the outdated Analyze.StartPos value from previous volume. + if (!FirstFile) + Analyze.StartPos=Arc.CurBlockPos; + } - if (Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL,0)!=0) + if (Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0) { - Matched=true; // First matched file found, must stop further scan. - break; + MatchFound = true; + PrevMatched = true; + + // Reset the previously set early exit position, if any, because + // we found a new matched file. + Analyze.EndPos=0; + + // Matched file reference pointing at maybe non-matched source file. + // Even though we know RedirName, we can't check if source file + // is certainly non-matched, because it can be filtered out by + // date or attributes, which we do not know here. + if (Arc.FileHead.RedirType==FSREDIR_FILECOPY) + { + bool AlreadyAdded=false; + for (size_t I=0;IWinSizeLimit || Arc.FileHead.WinSize<=Cmd->WinSize) + return true; + if (uiDictLimit(Cmd,ArcFileName,Arc.FileHead.WinSize,Max(Cmd->WinSizeLimit,Cmd->WinSize))) + { + // No more prompts when extracting other files. Important for GUI versions, + // where we might not have [Max]WinSize set permanently when extracting. + Cmd->WinSizeLimit=Arc.FileHead.WinSize; + } + else + { + ErrHandler.SetErrorCode(RARX_FATAL); +#ifdef RARDLL + Cmd->DllError=ERAR_LARGE_DICT; +#endif + Arc.SeekToNext(); + return false; + } + return true; +} diff --git a/unrar/extract.hpp b/unrar/extract.hpp index f42a7c76..adf0f0db 100644 --- a/unrar/extract.hpp +++ b/unrar/extract.hpp @@ -6,24 +6,44 @@ enum EXTRACT_ARC_CODE {EXTRACT_ARC_NEXT,EXTRACT_ARC_REPEAT}; class CmdExtract { private: + struct ExtractRef + { + std::wstring RefName; + std::wstring TmpName; + uint64 RefCount; + }; + std::vector RefList; + + struct AnalyzeData + { + std::wstring StartName; + uint64 StartPos; + std::wstring EndName; + uint64 EndPos; + } Analyze; + + bool ArcAnalyzed; + + void FreeAnalyzeData(); EXTRACT_ARC_CODE ExtractArchive(); - bool ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); - void ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize); + bool ExtractFileCopy(File &New,const std::wstring &ArcName,const std::wstring &RedirName,const std::wstring &NameNew,const std::wstring &NameExisting,int64 UnpSize); + void ExtrPrepareName(Archive &Arc,const std::wstring &ArcFileName,std::wstring &DestName); #ifdef RARDLL bool ExtrDllGetPassword(); #else - bool ExtrGetPassword(Archive &Arc,const wchar *ArcFileName); + bool ExtrGetPassword(Archive &Arc,const std::wstring &ArcFileName,RarCheckPassword *CheckPwd); #endif #if defined(_WIN_ALL) && !defined(SFX_MODULE) void ConvertDosPassword(Archive &Arc,SecPassword &DestPwd); #endif - void ExtrCreateDir(Archive &Arc,const wchar *ArcFileName); - bool ExtrCreateFile(Archive &Arc,File &CurFile); - bool CheckUnpVer(Archive &Arc,const wchar *ArcFileName); + void ExtrCreateDir(Archive &Arc,const std::wstring &ArcFileName); + bool ExtrCreateFile(Archive &Arc,File &CurFile,bool WriteOnly); + bool CheckUnpVer(Archive &Arc,const std::wstring &ArcFileName); #ifndef SFX_MODULE - bool DetectStartVolume(const wchar *VolName,bool NewNumbering); - void GetFirstVolIfFullSet(const wchar *SrcName,bool NewNumbering,wchar *DestName,size_t DestSize); + void AnalyzeArchive(const std::wstring &ArcName,bool Volume,bool NewNumbering); + void GetFirstVolIfFullSet(const std::wstring &SrcName,bool NewNumbering,std::wstring &DestName); #endif + bool CheckWinLimit(Archive &Arc,std::wstring &ArcFileName); RarTime StartTime; // Time when extraction started. @@ -46,12 +66,21 @@ class CmdExtract // any wrong password hints. bool AnySolidDataUnpackedWell; - wchar ArcName[NM]; + std::wstring ArcName; bool GlobalPassword; bool PrevProcessed; // If previous file was successfully extracted or tested. - wchar DestFileName[NM]; - bool PasswordCancelled; + std::wstring DestFileName; + bool SuppressNoFilesMessage; + + // In Windows it is set to true if at least one symlink with ".." + // in target was extracted. + bool ConvertSymlinkPaths; + + // Last path checked for symlinks. We use it to improve the performance, + // so we do not check recently checked folders again. + std::wstring LastCheckedSymlink; + #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) bool Fat32,NotFat32; #endif diff --git a/unrar/extractchunk.cpp b/unrar/extractchunk.cpp index bbc00e58..1e6d31a5 100644 --- a/unrar/extractchunk.cpp +++ b/unrar/extractchunk.cpp @@ -40,22 +40,21 @@ bool CmdExtract::ExtractCurrentFileChunkInit(Archive &Arc, if (Arc.FileHead.SplitBefore && FirstFile) { - wchar CurVolName[NM]; - wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName)); - VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),Arc.NewNumbering); + std::wstring CurVolName=ArcName; + VolNameToFirstName(ArcName,ArcName,Arc.NewNumbering); if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName)) { // If first volume name does not match the current name and if such // volume name really exists, let's unpack from this first volume. - *ArcName=0; + ArcName.clear(); Repeat=true; ErrHandler.SetErrorCode(RARX_WARNING); /* Actually known. The problem is that the file doesn't start on this volume. */ Cmd->DllError = ERAR_UNKNOWN; return false; } - wcsncpyz(ArcName,CurVolName,ASIZE(ArcName)); + ArcName=CurVolName; } DataIO.UnpVolume=Arc.FileHead.SplitAfter; @@ -73,16 +72,16 @@ bool CmdExtract::ExtractCurrentFileChunkInit(Archive &Arc, } } - if (*Cmd->DllDestName!=0) + if (!Cmd->DllDestName.empty()) { - wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); + DestFileName=Cmd->DllDestName; // Do we need this code? // if (Cmd->DllOpMode!=RAR_EXTRACT) // ExtrFile=false; } - wchar ArcFileName[NM]; - ConvertPath(Arc.FileHead.FileName,ArcFileName,ASIZE(ArcFileName)); + std::wstring ArcFileName; + ConvertPath(&Arc.FileHead.FileName,&ArcFileName); if (!CheckUnpVer(Arc,ArcFileName)) { ErrHandler.SetErrorCode(RARX_FATAL); diff --git a/unrar/filcreat.cpp b/unrar/filcreat.cpp index a64a7d4d..6b21aa26 100644 --- a/unrar/filcreat.cpp +++ b/unrar/filcreat.cpp @@ -1,9 +1,9 @@ #include "rar.hpp" // If NewFile==NULL, we delete created file after user confirmation. -// It is useful we we need to overwrite an existing folder or file, +// It is useful if we need to overwrite an existing folder or file, // but need user confirmation for that. -bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, +bool FileCreate(CommandData *Cmd,File *NewFile,std::wstring &Name, bool *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly) { if (UserReject!=NULL) @@ -29,7 +29,7 @@ bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, // autorename below can change the name, so we need to check it again. ShortNameChanged=false; #endif - UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,MaxNameSize,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0)); + UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0)); if (Choice==UIASKREP_R_REPLACE) break; @@ -44,95 +44,82 @@ bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, } // Try to truncate the existing file first instead of delete, - // so we preserve existing file permissions such as NTFS permissions. + // so we preserve existing file permissions, such as NTFS permissions, + // also as "Compressed" attribute and hard links. In GUI version we avoid + // deleting an existing file for non-.rar archive formats as well. uint FileMode=WriteOnly ? FMF_WRITE|FMF_SHAREREAD:FMF_UPDATE|FMF_SHAREREAD; if (NewFile!=NULL && NewFile->Create(Name,FileMode)) return true; - CreatePath(Name,true); + CreatePath(Name,true,Cmd->DisableNames); return NewFile!=NULL ? NewFile->Create(Name,FileMode):DelFile(Name); } -bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize) -{ - wchar NewName[NM]; - size_t NameLength=wcslen(Name); - wchar *Ext=GetExt(Name); - if (Ext==NULL) - Ext=Name+NameLength; - for (uint FileVer=1;;FileVer++) - { - swprintf(NewName,ASIZE(NewName),L"%.*ls(%u)%ls",uint(Ext-Name),Name,FileVer,Ext); - if (!FileExist(NewName)) - { - wcsncpyz(Name,NewName,MaxNameSize); - break; - } - if (FileVer>=1000000) - return false; - } - return true; -} - - #if defined(_WIN_ALL) // If we find a file, which short name is equal to 'Name', we try to change // its short name, while preserving the long name. It helps when unpacking // an archived file, which long name is equal to short name of already // existing file. Otherwise we would overwrite the already existing file, // even though its long name does not match the name of unpacking file. -bool UpdateExistingShortName(const wchar *Name) +bool UpdateExistingShortName(const std::wstring &Name) { - wchar LongPathName[NM]; - DWORD Res=GetLongPathName(Name,LongPathName,ASIZE(LongPathName)); - if (Res==0 || Res>=ASIZE(LongPathName)) + DWORD Res=GetLongPathName(Name.c_str(),NULL,0); + if (Res==0) return false; - wchar ShortPathName[NM]; - Res=GetShortPathName(Name,ShortPathName,ASIZE(ShortPathName)); - if (Res==0 || Res>=ASIZE(ShortPathName)) + std::vector LongPathBuf(Res); + Res=GetLongPathName(Name.c_str(),LongPathBuf.data(),(DWORD)LongPathBuf.size()); + if (Res==0 || Res>=LongPathBuf.size()) return false; - wchar *LongName=PointToName(LongPathName); - wchar *ShortName=PointToName(ShortPathName); + Res=GetShortPathName(Name.c_str(),NULL,0); + if (Res==0) + return false; + std::vector ShortPathBuf(Res); + Res=GetShortPathName(Name.c_str(),ShortPathBuf.data(),(DWORD)ShortPathBuf.size()); + if (Res==0 || Res>=ShortPathBuf.size()) + return false; + std::wstring LongPathName=LongPathBuf.data(); + std::wstring ShortPathName=ShortPathBuf.data(); + + std::wstring LongName=PointToName(LongPathName); + std::wstring ShortName=PointToName(ShortPathName); // We continue only if file has a short name, which does not match its // long name, and this short name is equal to name of file which we need // to create. - if (*ShortName==0 || wcsicomp(LongName,ShortName)==0 || + if (ShortName.empty() || wcsicomp(LongName,ShortName)==0 || wcsicomp(PointToName(Name),ShortName)!=0) return false; // Generate the temporary new name for existing file. - wchar NewName[NM]; - *NewName=0; - for (int I=0;I<10000 && *NewName==0;I+=123) + std::wstring NewName; + for (uint I=0;I<10000 && NewName.empty();I+=123) { // Here we copy the path part of file to create. We'll make the temporary // file in the same folder. - wcsncpyz(NewName,Name,ASIZE(NewName)); + NewName=Name; // Here we set the random name part. - swprintf(PointToName(NewName),ASIZE(NewName),L"rtmp%d",I); + SetName(NewName,std::wstring(L"rtmp") + std::to_wstring(I)); // If such file is already exist, try next random name. if (FileExist(NewName)) - *NewName=0; + NewName.clear(); } // If we could not generate the name not used by any other file, we return. - if (*NewName==0) + if (NewName.empty()) return false; // FastFind returns the name without path, but we need the fully qualified // name for renaming, so we use the path from file to create and long name // from existing file. - wchar FullName[NM]; - wcsncpyz(FullName,Name,ASIZE(FullName)); - SetName(FullName,LongName,ASIZE(FullName)); + std::wstring FullName=Name; + SetName(FullName,LongName); // Rename the existing file to randomly generated name. Normally it changes // the short name too. - if (!MoveFile(FullName,NewName)) + if (!MoveFile(FullName.c_str(),NewName.c_str())) return false; // Now we need to create the temporary empty file with same name as @@ -147,7 +134,7 @@ bool UpdateExistingShortName(const wchar *Name) // Now we rename the existing file from temporary name to original long name. // Since its previous short name is occupied by another file, it should // get another short name. - MoveFile(NewName,FullName); + MoveFile(NewName.c_str(),FullName.c_str()); if (Created) { @@ -155,9 +142,9 @@ bool UpdateExistingShortName(const wchar *Name) KeepShortFile.Close(); KeepShortFile.Delete(); } - // We successfully changed the short name. Maybe sometimes we'll simplify - // this function by use of SetFileShortName Windows API call. - // But SetFileShortName is not available in older Windows. + // We successfully changed the short name. We do not use the simpler + // SetFileShortName Windows API call, because it requires SE_RESTORE_NAME + // privilege. return true; } #endif diff --git a/unrar/filcreat.hpp b/unrar/filcreat.hpp index 44f801d4..ad95feef 100644 --- a/unrar/filcreat.hpp +++ b/unrar/filcreat.hpp @@ -1,14 +1,12 @@ #ifndef _RAR_FILECREATE_ #define _RAR_FILECREATE_ -bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, +bool FileCreate(CommandData *Cmd,File *NewFile,std::wstring &Name, bool *UserReject,int64 FileSize=INT64NDF, RarTime *FileTime=NULL,bool WriteOnly=false); -bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize); - #if defined(_WIN_ALL) -bool UpdateExistingShortName(const wchar *Name); +bool UpdateExistingShortName(const std::wstring &Name); #endif #endif diff --git a/unrar/file.cpp b/unrar/file.cpp index 5a8099ec..c6fab173 100644 --- a/unrar/file.cpp +++ b/unrar/file.cpp @@ -3,10 +3,10 @@ File::File() { hFile=FILE_BAD_HANDLE; - *FileName=0; NewFile=false; LastWrite=false; HandleType=FILE_HANDLENORMAL; + LineInput=false; SkipClose=false; ErrorType=FILE_SUCCESS; OpenShared=false; @@ -14,11 +14,11 @@ File::File() AllowExceptions=true; PreserveAtime=false; #ifdef _WIN_ALL - NoSequentialRead=false; - CreateMode=FMF_UNDEFINED; + // CreateMode=FMF_UNDEFINED; #endif ReadErrorMode=FREM_ASK; TruncatedAfterReadError=false; + CurFilePos=0; } @@ -39,12 +39,12 @@ void File::operator = (File &SrcFile) LastWrite=SrcFile.LastWrite; HandleType=SrcFile.HandleType; TruncatedAfterReadError=SrcFile.TruncatedAfterReadError; - wcsncpyz(FileName,SrcFile.FileName,ASIZE(FileName)); + FileName=SrcFile.FileName; SrcFile.SkipClose=true; } -bool File::Open(const wchar *Name,uint Mode) +bool File::Open(const std::wstring &Name,uint Mode) { ErrorType=FILE_SUCCESS; FileHandle hNewFile; @@ -58,21 +58,21 @@ bool File::Open(const wchar *Name,uint Mode) uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ; if (OpenShared) ShareMode|=FILE_SHARE_WRITE; - uint Flags=NoSequentialRead ? 0:FILE_FLAG_SEQUENTIAL_SCAN; + uint Flags=FILE_FLAG_SEQUENTIAL_SCAN; FindData FD; if (PreserveAtime) Access|=FILE_WRITE_ATTRIBUTES; // Needed to preserve atime. - hNewFile=CreateFile(Name,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); + hNewFile=CreateFile(Name.c_str(),Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); DWORD LastError; if (hNewFile==FILE_BAD_HANDLE) { LastError=GetLastError(); - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) { - hNewFile=CreateFile(LongName,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); + hNewFile=CreateFile(LongName.c_str(),Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); // For archive names longer than 260 characters first CreateFile // (without \\?\) fails and sets LastError to 3 (access denied). @@ -100,6 +100,12 @@ bool File::Open(const wchar *Name,uint Mode) #else int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY); + + // 2025.06.09: We can't just set O_DIRECT for Unix like we set + // FILE_FLAG_SEQUENTIAL_SCAN for Windows to minimize disk caching. + // O_DIRECT might impose alignment requirements for data size, data address + // and file offset. Also it might not be supported by some file systems + // and fail with an error. #ifdef O_BINARY flags|=O_BINARY; #if defined(_AIX) && defined(_LARGE_FILE_API) @@ -111,10 +117,10 @@ bool File::Open(const wchar *Name,uint Mode) if (PreserveAtime) flags|=O_NOATIME; #endif - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); + std::string NameA; + WideToChar(Name,NameA); - int handle=open(NameA,flags); + int handle=open(NameA.c_str(),flags); #ifdef LOCK_EX #ifdef _OSF_SOURCE @@ -147,7 +153,7 @@ bool File::Open(const wchar *Name,uint Mode) if (Success) { hFile=hNewFile; - wcsncpyz(FileName,Name,ASIZE(FileName)); + FileName=Name; TruncatedAfterReadError=false; } return Success; @@ -155,7 +161,7 @@ bool File::Open(const wchar *Name,uint Mode) #if !defined(SFX_MODULE) -void File::TOpen(const wchar *Name) +void File::TOpen(const std::wstring &Name) { if (!WOpen(Name)) ErrHandler.Exit(RARX_OPEN); @@ -163,7 +169,7 @@ void File::TOpen(const wchar *Name) #endif -bool File::WOpen(const wchar *Name) +bool File::WOpen(const std::wstring &Name) { if (Open(Name)) return true; @@ -172,8 +178,9 @@ bool File::WOpen(const wchar *Name) } -bool File::Create(const wchar *Name,uint Mode) +bool File::Create(const std::wstring &Name,uint Mode) { + // 2025.09.03: Likely outdated info, see https://www.illumos.org/issues/2000 // OpenIndiana based NAS and CIFS shares fail to set the file time if file // was created in read+write mode and some data was written and not flushed // before SetFileTime call. So we should use the write only mode if we plan @@ -181,46 +188,46 @@ bool File::Create(const wchar *Name,uint Mode) bool WriteMode=(Mode & FMF_WRITE)!=0; bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared; #ifdef _WIN_ALL - CreateMode=Mode; + // CreateMode=Mode; uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE; DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0; // Windows automatically removes dots and spaces in the end of file name, // So we detect such names and process them with \\?\ prefix. - wchar *LastChar=PointToLastChar(Name); - bool Special=*LastChar=='.' || *LastChar==' '; + wchar LastChar=GetLastChar(Name); + bool Special=LastChar=='.' || LastChar==' '; if (Special && (Mode & FMF_STANDARDNAMES)==0) hFile=FILE_BAD_HANDLE; else - hFile=CreateFile(Name,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); + hFile=CreateFile(Name.c_str(),Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); if (hFile==FILE_BAD_HANDLE) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - hFile=CreateFile(LongName,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + hFile=CreateFile(LongName.c_str(),Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); } #else - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); #ifdef FILE_USE_OPEN - hFile=open(NameA,(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666); + std::string NameA; + WideToChar(Name,NameA); + hFile=open(NameA.c_str(),(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666); #else - hFile=fopen(NameA,WriteMode ? WRITEBINARY:CREATEBINARY); + hFile=fopen(NameA.c_str(),WriteMode ? WRITEBINARY:CREATEBINARY); #endif #endif NewFile=true; HandleType=FILE_HANDLENORMAL; SkipClose=false; - wcsncpyz(FileName,Name,ASIZE(FileName)); + FileName=Name; return hFile!=FILE_BAD_HANDLE; } #if !defined(SFX_MODULE) -void File::TCreate(const wchar *Name,uint Mode) +void File::TCreate(const std::wstring &Name,uint Mode) { if (!WCreate(Name,Mode)) ErrHandler.Exit(RARX_FATAL); @@ -228,7 +235,7 @@ void File::TCreate(const wchar *Name,uint Mode) #endif -bool File::WCreate(const wchar *Name,uint Mode) +bool File::WCreate(const std::wstring &Name,uint Mode) { if (Create(Name,Mode)) return true; @@ -249,7 +256,7 @@ bool File::Close() // We use the standard system handle for stdout in Windows // and it must not be closed here. if (HandleType==FILE_HANDLENORMAL) - Success=CloseHandle(hFile)==TRUE; + Success=CloseHandle(hFile)!=FALSE; #else #ifdef FILE_USE_OPEN Success=close(hFile)!=-1; @@ -279,16 +286,16 @@ bool File::Delete() } -bool File::Rename(const wchar *NewName) +bool File::Rename(const std::wstring &NewName) { // No need to rename if names are already same. - bool Success=wcscmp(FileName,NewName)==0; + bool Success=(NewName==FileName); if (!Success) Success=RenameFile(FileName,NewName); if (Success) - wcsncpyz(FileName,NewName,ASIZE(FileName)); + FileName=NewName; return Success; } @@ -326,13 +333,13 @@ bool File::Write(const void *Data,size_t Size) const size_t MaxSize=0x4000; for (size_t I=0;I0 && (uint)ReadSize0) // Can be -1 for error and AllowExceptions disabled. + CurFilePos+=TotalRead; + return TotalRead; // It can return -1 only if AllowExceptions is disabled. } @@ -499,6 +528,36 @@ bool File::RawSeek(int64 Offset,int Method) { if (hFile==FILE_BAD_HANDLE) return true; + if (!IsSeekable()) // To extract archives from stdin with -si. + { + // We tried to dynamically allocate 32 KB buffer here, but it improved + // speed in Windows 10 by mere ~1.5%. + byte Buf[4096]; + if (Method==SEEK_CUR || Method==SEEK_SET && Offset>=CurFilePos) + { + uint64 SkipSize=Method==SEEK_CUR ? Offset:Offset-CurFilePos; + while (SkipSize>0) // Reading to emulate seek forward. + { + int ReadSize=Read(Buf,(size_t)Min(SkipSize,ASIZE(Buf))); + if (ReadSize<=0) + return false; + SkipSize-=ReadSize; + CurFilePos+=ReadSize; + } + return true; + } + // May need it in FileLength() in Archive::UnexpEndArcMsg() when unpacking + // RAR 4.x archives without the end of archive block created with -en. + if (Method==SEEK_END) + { + int ReadSize; + while ((ReadSize=Read(Buf,ASIZE(Buf)))>0) + CurFilePos+=ReadSize; + return true; + } + + return false; // Backward seek on unseekable file. + } if (Offset<0 && Method!=SEEK_SET) { Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset; @@ -533,6 +592,8 @@ int64 File::Tell() ErrHandler.SeekError(FileName); else return -1; + if (!IsSeekable()) + return CurFilePos; #ifdef _WIN_ALL LONG HighDist=0; uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT); @@ -591,7 +652,7 @@ void File::PutByte(byte Byte) bool File::Truncate() { #ifdef _WIN_ALL - return SetEndOfFile(hFile)==TRUE; + return SetEndOfFile(hFile)!=FALSE; #else return ftruncate(GetFD(),(off_t)Tell())==0; #endif @@ -617,8 +678,10 @@ void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta) // Workaround for OpenIndiana NAS time bug. If we cannot create a file // in write only mode, we need to flush the write buffer before calling // SetFileTime or file time will not be changed. - if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0) - FlushFileBuffers(hFile); + // 2025.09.03: Removed this code as likely redundant now, + // see https://www.illumos.org/issues/2000 + // if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0) + // FlushFileBuffers(hFile); bool sm=ftm!=NULL && ftm->IsSet(); bool sc=ftc!=NULL && ftc->IsSet(); @@ -649,15 +712,15 @@ void File::SetCloseFileTime(RarTime *ftm,RarTime *fta) } -void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) +void File::SetCloseFileTimeByName(const std::wstring &Name,RarTime *ftm,RarTime *fta) { #ifdef _UNIX bool setm=ftm!=NULL && ftm->IsSet(); bool seta=fta!=NULL && fta->IsSet(); if (setm || seta) { - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); + std::string NameA; + WideToChar(Name,NameA); #ifdef UNIX_TIME_NS timespec times[2]; @@ -665,7 +728,7 @@ void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) times[0].tv_nsec=seta ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW; times[1].tv_sec=setm ? ftm->GetUnix() : 0; times[1].tv_nsec=setm ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW; - utimensat(AT_FDCWD,NameA,times,0); + utimensat(AT_FDCWD,NameA.c_str(),times,0); #else utimbuf ut; if (setm) @@ -676,24 +739,47 @@ void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) ut.actime=fta->GetUnix(); else ut.actime=ut.modtime; // Need to set something, cannot left it 0. - utime(NameA,&ut); + utime(NameA.c_str(),&ut); #endif } #endif } -void File::GetOpenFileTime(RarTime *ft) +#ifdef _UNIX +void File::StatToRarTime(struct stat &st,RarTime *ftm,RarTime *ftc,RarTime *fta) +{ +#ifdef UNIX_TIME_NS +#if defined(_APPLE) + if (ftm!=NULL) ftm->SetUnixNS(st.st_mtimespec.tv_sec*(uint64)1000000000+st.st_mtimespec.tv_nsec); + if (ftc!=NULL) ftc->SetUnixNS(st.st_ctimespec.tv_sec*(uint64)1000000000+st.st_ctimespec.tv_nsec); + if (fta!=NULL) fta->SetUnixNS(st.st_atimespec.tv_sec*(uint64)1000000000+st.st_atimespec.tv_nsec); +#else + if (ftm!=NULL) ftm->SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec); + if (ftc!=NULL) ftc->SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec); + if (fta!=NULL) fta->SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec); +#endif +#else + if (ftm!=NULL) ftm->SetUnix(st.st_mtime); + if (ftc!=NULL) ftc->SetUnix(st.st_ctime); + if (fta!=NULL) fta->SetUnix(st.st_atime); +#endif +} +#endif + + +void File::GetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta) { #ifdef _WIN_ALL - FILETIME FileTime; - GetFileTime(hFile,NULL,NULL,&FileTime); - ft->SetWinFT(&FileTime); -#endif -#if defined(_UNIX) || defined(_EMX) + FILETIME ctime,atime,mtime; + GetFileTime(hFile,&ctime,&atime,&mtime); + if (ftm!=NULL) ftm->SetWinFT(&mtime); + if (ftc!=NULL) ftc->SetWinFT(&ctime); + if (fta!=NULL) fta->SetWinFT(&atime); +#elif defined(_UNIX) struct stat st; fstat(GetFD(),&st); - ft->SetUnix(st.st_mtime); + StatToRarTime(st,ftm,ftc,fta); #endif } @@ -724,15 +810,23 @@ bool File::IsDevice() #ifndef SFX_MODULE int64 File::Copy(File &Dest,int64 Length) { - Array Buffer(File::CopyBufferSize()); - int64 CopySize=0; bool CopyAll=(Length==INT64NDF); + // Adjust the buffer size to data size. So we do not waste too much time + // to vector initialization when copying many small data blocks like + // when updating an archive with many small files. + size_t BufSize=File::CopyBufferSize(); + if (!CopyAll && Length<(int64)BufSize) + BufSize=(size_t)Length; + + std::vector Buffer(BufSize); + int64 CopySize=0; + while (CopyAll || Length>0) { Wait(); - size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.Size()) ? (size_t)Length:Buffer.Size(); - byte *Buf=&Buffer[0]; + size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.size()) ? (size_t)Length:Buffer.size(); + byte *Buf=Buffer.data(); int ReadSize=Read(Buf,SizeToRead); if (ReadSize==0) break; diff --git a/unrar/file.hpp b/unrar/file.hpp index 1c436d4e..67c17549 100644 --- a/unrar/file.hpp +++ b/unrar/file.hpp @@ -14,8 +14,6 @@ #define FILE_BAD_HANDLE NULL #endif -class RAROptions; - enum FILE_HANDLETYPE {FILE_HANDLENORMAL,FILE_HANDLESTD}; enum FILE_ERRORTYPE {FILE_SUCCESS,FILE_NOTFOUND,FILE_READERROR}; @@ -43,7 +41,7 @@ enum FILE_MODE_FLAGS { FMF_STANDARDNAMES=32, // Mode flags are not defined yet. - FMF_UNDEFINED=256 + // FMF_UNDEFINED=256 }; enum FILE_READ_ERROR_MODE { @@ -59,21 +57,32 @@ class File FileHandle hFile; bool LastWrite; FILE_HANDLETYPE HandleType; + + // If we read the user input in console prompts from stdin, we shall + // process the available line immediately, not waiting for rest of data. + // Otherwise apps piping user responses to multiple Ask() prompts can + // hang if no more data is available yet and pipe isn't closed. + // If we read RAR archive or other file data from stdin, we shall collect + // the entire requested block as long as pipe isn't closed, so we get + // complete archive headers, not split between different reads. + bool LineInput; + bool SkipClose; FILE_READ_ERROR_MODE ReadErrorMode; bool NewFile; bool AllowDelete; bool AllowExceptions; #ifdef _WIN_ALL - bool NoSequentialRead; - uint CreateMode; + // uint CreateMode; #endif bool PreserveAtime; bool TruncatedAfterReadError; + + int64 CurFilePos; // Used for forward seeks in stdin files. protected: bool OpenShared; // Set by 'Archive' class. public: - wchar FileName[NM]; + std::wstring FileName; FILE_ERRORTYPE ErrorType; public: @@ -83,15 +92,15 @@ class File // Several functions below are 'virtual', because they are redefined // by Archive for QOpen and by MultiFile for split files in WinRAR. - virtual bool Open(const wchar *Name,uint Mode=FMF_READ); - void TOpen(const wchar *Name); - bool WOpen(const wchar *Name); - bool Create(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); - void TCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); - bool WCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + virtual bool Open(const std::wstring &Name,uint Mode=FMF_READ); + void TOpen(const std::wstring &Name); + bool WOpen(const std::wstring &Name); + bool Create(const std::wstring &Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + void TCreate(const std::wstring &Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + bool WCreate(const std::wstring &Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); virtual bool Close(); // 'virtual' for MultiFile class. bool Delete(); - bool Rename(const wchar *NewName); + bool Rename(const std::wstring &NewName); bool Write(const void *Data,size_t Size); virtual int Read(void *Data,size_t Size); int DirectRead(void *Data,size_t Size); @@ -105,12 +114,17 @@ class File void Flush(); void SetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL); void SetCloseFileTime(RarTime *ftm,RarTime *fta=NULL); - static void SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta); - void GetOpenFileTime(RarTime *ft); + static void SetCloseFileTimeByName(const std::wstring &Name,RarTime *ftm,RarTime *fta); +#ifdef _UNIX + static void StatToRarTime(struct stat &st,RarTime *ftm,RarTime *ftc,RarTime *fta); +#endif + void GetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL); virtual bool IsOpened() {return hFile!=FILE_BAD_HANDLE;} // 'virtual' for MultiFile class. - int64 FileLength(); + virtual int64 FileLength(); // 'virtual' for MultiFile class. void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;} + void SetLineInputMode(bool Mode) {LineInput=Mode;} FILE_HANDLETYPE GetHandleType() {return HandleType;} + bool IsSeekable() {return HandleType!=FILE_HANDLESTD;} bool IsDevice(); static bool RemoveCreated(); FileHandle GetHandle() {return hFile;} @@ -119,9 +133,6 @@ class File int64 Copy(File &Dest,int64 Length=INT64NDF); void SetAllowDelete(bool Allow) {AllowDelete=Allow;} void SetExceptions(bool Allow) {AllowExceptions=Allow;} -#ifdef _WIN_ALL - void RemoveSequentialFlag() {NoSequentialRead=true;} -#endif void SetPreserveAtime(bool Preserve) {PreserveAtime=Preserve;} bool IsTruncatedAfterReadError() {return TruncatedAfterReadError;} #ifdef _UNIX @@ -136,14 +147,9 @@ class File #endif static size_t CopyBufferSize() { -#ifdef _WIN_ALL - // USB flash performance is poor with 64 KB buffer, 256+ KB resolved it. - // For copying from HDD to same HDD the best performance was with 256 KB - // buffer in XP and with 1 MB buffer in Win10. - return WinNT()==WNT_WXP ? 0x40000:0x100000; -#else - return 0x100000; -#endif + // Values in 0x100000 - 0x400000 range are ok, but multithreaded CRC32 + // seems to benefit from 0x400000, especially on ARM CPUs. + return 0x400000; } }; diff --git a/unrar/filefn.cpp b/unrar/filefn.cpp index 6cc922f2..437c5a03 100644 --- a/unrar/filefn.cpp +++ b/unrar/filefn.cpp @@ -1,18 +1,18 @@ #include "rar.hpp" -MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr) +MKDIR_CODE MakeDir(const std::wstring &Name,bool SetAttr,uint Attr) { #ifdef _WIN_ALL // Windows automatically removes dots and spaces in the end of directory // name. So we detect such names and process them with \\?\ prefix. - wchar *LastChar=PointToLastChar(Name); - bool Special=*LastChar=='.' || *LastChar==' '; - BOOL RetCode=Special ? FALSE : CreateDirectory(Name,NULL); + wchar LastChar=GetLastChar(Name); + bool Special=LastChar=='.' || LastChar==' '; + BOOL RetCode=Special ? FALSE : CreateDirectory(Name.c_str(),NULL); if (RetCode==0 && !FileExist(Name)) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - RetCode=CreateDirectory(LongName,NULL); + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + RetCode=CreateDirectory(LongName.c_str(),NULL); } if (RetCode!=0) // Non-zero return code means success for CreateDirectory. { @@ -25,10 +25,10 @@ MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr) return MKDIR_BADPATH; return MKDIR_ERROR; #elif defined(_UNIX) - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); + std::string NameA; + WideToChar(Name,NameA); mode_t uattr=SetAttr ? (mode_t)Attr:0777; - int ErrCode=mkdir(NameA,uattr); + int ErrCode=mkdir(NameA.c_str(),uattr); if (ErrCode==-1) return errno==ENOENT ? MKDIR_BADPATH:MKDIR_ERROR; return MKDIR_SUCCESS; @@ -38,12 +38,19 @@ MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr) } -bool CreatePath(const wchar *Path,bool SkipLastName) +// Simplified version of MakeDir(). +bool CreateDir(const std::wstring &Name) { - if (Path==NULL || *Path==0) + return MakeDir(Name,false,0)==MKDIR_SUCCESS; +} + + +bool CreatePath(const std::wstring &Path,bool SkipLastName,bool Silent) +{ + if (Path.empty()) return false; -#if defined(_WIN_ALL) || defined(_EMX) +#ifdef _WIN_ALL uint DirAttr=0; #else uint DirAttr=0777; @@ -51,42 +58,36 @@ bool CreatePath(const wchar *Path,bool SkipLastName) bool Success=true; - for (const wchar *s=Path;*s!=0;s++) + for (size_t I=0;I=ASIZE(DirName)) - break; - // Process all kinds of path separators, so user can enter Unix style - // path in Windows or Windows in Unix. s>Path check avoids attempting + // path in Windows or Windows in Unix. I>0 check avoids attempting // creating an empty directory for paths starting from path separator. - if (IsPathDiv(*s) && s>Path) + if (IsPathDiv(Path[I]) && I>0) { #ifdef _WIN_ALL // We must not attempt to create "D:" directory, because first // CreateDirectory will fail, so we'll use \\?\D:, which forces Wine // to create "D:" directory. - if (s==Path+2 && Path[1]==':') + if (I==2 && Path[1]==':') continue; #endif - wcsncpy(DirName,Path,s-Path); - DirName[s-Path]=0; - + std::wstring DirName=Path.substr(0,I); Success=MakeDir(DirName,true,DirAttr)==MKDIR_SUCCESS; - if (Success) + if (Success && !Silent) { - mprintf(St(MCreatDir),DirName); + mprintf(St(MCreatDir),DirName.c_str()); mprintf(L" %s",St(MOk)); } } } - if (!SkipLastName && !IsPathDiv(*PointToLastChar(Path))) + if (!SkipLastName && !IsPathDiv(GetLastChar(Path))) Success=MakeDir(Path,true,DirAttr)==MKDIR_SUCCESS; return Success; } -void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta) +void SetDirTime(const std::wstring &Name,RarTime *ftm,RarTime *ftc,RarTime *fta) { #if defined(_WIN_ALL) bool sm=ftm!=NULL && ftm->IsSet(); @@ -98,13 +99,13 @@ void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta) if (ResetAttr) SetFileAttr(Name,0); - HANDLE hFile=CreateFile(Name,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, + HANDLE hFile=CreateFile(Name.c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); if (hFile==INVALID_HANDLE_VALUE) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - hFile=CreateFile(LongName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + hFile=CreateFile(LongName.c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); } @@ -122,18 +123,20 @@ void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta) if (ResetAttr) SetFileAttr(Name,DirAttr); #endif -#if defined(_UNIX) || defined(_EMX) +#ifdef _UNIX File::SetCloseFileTimeByName(Name,ftm,fta); #endif } -bool IsRemovable(const wchar *Name) + + +bool IsRemovable(const std::wstring &Name) { #if defined(_WIN_ALL) - wchar Root[NM]; - GetPathRoot(Name,Root,ASIZE(Root)); - int Type=GetDriveType(*Root!=0 ? Root:NULL); + std::wstring Root; + GetPathRoot(Name,Root); + int Type=GetDriveType(Root.empty() ? nullptr : Root.c_str()); return Type==DRIVE_REMOVABLE || Type==DRIVE_CDROM; #else return false; @@ -142,25 +145,25 @@ bool IsRemovable(const wchar *Name) #ifndef SFX_MODULE -int64 GetFreeDisk(const wchar *Name) +int64 GetFreeDisk(const std::wstring &Name) { #ifdef _WIN_ALL - wchar Root[NM]; - GetFilePath(Name,Root,ASIZE(Root)); + std::wstring Root; + GetPathWithSep(Name,Root); ULARGE_INTEGER uiTotalSize,uiTotalFree,uiUserFree; uiUserFree.u.LowPart=uiUserFree.u.HighPart=0; - if (GetDiskFreeSpaceEx(*Root!=0 ? Root:NULL,&uiUserFree,&uiTotalSize,&uiTotalFree) && + if (GetDiskFreeSpaceEx(Root.empty() ? NULL:Root.c_str(),&uiUserFree,&uiTotalSize,&uiTotalFree) && uiUserFree.u.HighPart<=uiTotalFree.u.HighPart) return INT32TO64(uiUserFree.u.HighPart,uiUserFree.u.LowPart); return 0; #elif defined(_UNIX) - wchar Root[NM]; - GetFilePath(Name,Root,ASIZE(Root)); - char RootA[NM]; - WideToChar(Root,RootA,ASIZE(RootA)); + std::wstring Root; + GetPathWithSep(Name,Root); + std::string RootA; + WideToChar(Root,RootA); struct statvfs sfs; - if (statvfs(*RootA!=0 ? RootA:".",&sfs)!=0) + if (statvfs(RootA.empty() ? ".":RootA.c_str(),&sfs)!=0) return 0; int64 FreeSize=sfs.f_bsize; FreeSize=FreeSize*sfs.f_bavail; @@ -175,26 +178,27 @@ int64 GetFreeDisk(const wchar *Name) #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) // Return 'true' for FAT and FAT32, so we can adjust the maximum supported // file size to 4 GB for these file systems. -bool IsFAT(const wchar *Name) +bool IsFAT(const std::wstring &Name) { - wchar Root[NM]; - GetPathRoot(Name,Root,ASIZE(Root)); + std::wstring Root; + GetPathRoot(Name,Root); wchar FileSystem[MAX_PATH+1]; - if (GetVolumeInformation(Root,NULL,0,NULL,NULL,NULL,FileSystem,ASIZE(FileSystem))) + // Root can be empty, when we create volumes with -v in the current folder. + if (GetVolumeInformation(Root.empty() ? NULL:Root.c_str(),NULL,0,NULL,NULL,NULL,FileSystem,ASIZE(FileSystem))) return wcscmp(FileSystem,L"FAT")==0 || wcscmp(FileSystem,L"FAT32")==0; return false; } #endif -bool FileExist(const wchar *Name) +bool FileExist(const std::wstring &Name) { #ifdef _WIN_ALL return GetFileAttr(Name)!=0xffffffff; #elif defined(ENABLE_ACCESS) - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); - return access(NameA,0)==0; + std::string NameA; + WideToChar(Name,NameA); + return access(NameA.c_str(),0)==0; #else FindData FD; return FindFile::FastFind(Name,&FD); @@ -202,7 +206,7 @@ bool FileExist(const wchar *Name) } -bool WildFileExist(const wchar *Name) +bool WildFileExist(const std::wstring &Name) { if (IsWildcard(Name)) { @@ -230,8 +234,9 @@ bool IsUnreadable(uint Attr) { #if defined(_UNIX) && defined(S_ISFIFO) && defined(S_ISSOCK) && defined(S_ISCHR) return S_ISFIFO(Attr) || S_ISSOCK(Attr) || S_ISCHR(Attr); -#endif +#else return false; +#endif } @@ -261,70 +266,66 @@ bool IsDeleteAllowed(uint FileAttr) } -void PrepareToDelete(const wchar *Name) +void PrepareToDelete(const std::wstring &Name) { -#if defined(_WIN_ALL) || defined(_EMX) +#ifdef _WIN_ALL SetFileAttr(Name,0); #endif #ifdef _UNIX - if (Name!=NULL) - { - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); - chmod(NameA,S_IRUSR|S_IWUSR|S_IXUSR); - } + std::string NameA; + WideToChar(Name,NameA); + chmod(NameA.c_str(),S_IRUSR|S_IWUSR|S_IXUSR); #endif } -uint GetFileAttr(const wchar *Name) +uint GetFileAttr(const std::wstring &Name) { #ifdef _WIN_ALL - DWORD Attr=GetFileAttributes(Name); + DWORD Attr=GetFileAttributes(Name.c_str()); if (Attr==0xffffffff) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - Attr=GetFileAttributes(LongName); + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + Attr=GetFileAttributes(LongName.c_str()); } return Attr; #else - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); + std::string NameA; + WideToChar(Name,NameA); struct stat st; - if (stat(NameA,&st)!=0) + if (stat(NameA.c_str(),&st)!=0) return 0; return st.st_mode; #endif } -bool SetFileAttr(const wchar *Name,uint Attr) +bool SetFileAttr(const std::wstring &Name,uint Attr) { #ifdef _WIN_ALL - bool Success=SetFileAttributes(Name,Attr)!=0; + bool Success=SetFileAttributes(Name.c_str(),Attr)!=0; if (!Success) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - Success=SetFileAttributes(LongName,Attr)!=0; + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + Success=SetFileAttributes(LongName.c_str(),Attr)!=0; } return Success; #elif defined(_UNIX) - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); - return chmod(NameA,(mode_t)Attr)==0; + std::string NameA; + WideToChar(Name,NameA); + return chmod(NameA.c_str(),(mode_t)Attr)==0; #else return false; #endif } -#if 0 -wchar *MkTemp(wchar *Name,size_t MaxSize) +// Ext is the extension with the leading dot, like L".bat", or nullptr to use +// the default extension. +bool MkTemp(std::wstring &Name,const wchar *Ext) { - size_t Length=wcslen(Name); - RarTime CurTime; CurTime.SetCurrentTime(); @@ -343,18 +344,28 @@ wchar *MkTemp(wchar *Name,size_t MaxSize) for (uint Attempt=0;;Attempt++) { - uint Ext=Random%50000+Attempt; - wchar RndText[50]; - swprintf(RndText,ASIZE(RndText),L"%u.%03u",PID,Ext); - if (Length+wcslen(RndText)>=MaxSize || Attempt==1000) - return NULL; - wcsncpyz(Name+Length,RndText,MaxSize-Length); - if (!FileExist(Name)) + uint RandomExt=Random%50000+Attempt; + if (Attempt==1000) + return false; + + // User asked to specify the single extension for all temporary files, + // so it can be added to server ransomware protection exceptions. + // He wrote, this protection blocks temporary files when adding + // a file to RAR archive with drag and drop. So unless a calling code + // requires a specific extension, like .bat file when uninstalling, + // we set the uniform extension here. + if (Ext==nullptr) + Ext=L".rartemp"; + + std::wstring NewName=Name + std::to_wstring(PID) + L"." + std::to_wstring(RandomExt) + Ext; + if (!FileExist(NewName)) + { + Name=NewName; break; + } } - return Name; + return true; } -#endif #if !defined(SFX_MODULE) @@ -372,8 +383,7 @@ void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size, SrcFile->Seek(0,SEEK_SET); const size_t BufSize=0x100000; - Array Data(BufSize); - + std::vector Data(BufSize); DataHash HashCRC,HashBlake2; HashCRC.Init(HASH_CRC32,Threads); @@ -388,7 +398,7 @@ void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size, SizeToRead=BufSize; // Then always attempt to read the entire buffer. else SizeToRead=(size_t)Min((int64)BufSize,Size); - int ReadSize=SrcFile->Read(&Data[0],SizeToRead); + int ReadSize=SrcFile->Read(Data.data(),SizeToRead); if (ReadSize==0) break; TotalRead+=ReadSize; @@ -397,7 +407,11 @@ void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size, { #ifndef SILENT if ((Flags & CALCFSUM_SHOWPROGRESS)!=0) - uiExtractProgress(TotalRead,FileLength,TotalRead,FileLength); + { + // Update only the current file progress in WinRAR, set the total to 0 + // to keep it as is. It looks better for WinRAR. + uiExtractProgress(TotalRead,FileLength,0,0); + } else { if ((Flags & CALCFSUM_SHOWPERCENT)!=0) @@ -408,9 +422,9 @@ void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size, } if (CRC32!=NULL) - HashCRC.Update(&Data[0],ReadSize); + HashCRC.Update(Data.data(),ReadSize); if (Blake2!=NULL) - HashBlake2.Update(&Data[0],ReadSize); + HashBlake2.Update(Data.data(),ReadSize); if (Size!=INT64NDF) Size-=ReadSize; @@ -432,73 +446,109 @@ void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size, #endif -bool RenameFile(const wchar *SrcName,const wchar *DestName) +bool RenameFile(const std::wstring &SrcName,const std::wstring &DestName) { #ifdef _WIN_ALL - bool Success=MoveFile(SrcName,DestName)!=0; + bool Success=MoveFile(SrcName.c_str(),DestName.c_str())!=0; if (!Success) { - wchar LongName1[NM],LongName2[NM]; - if (GetWinLongPath(SrcName,LongName1,ASIZE(LongName1)) && - GetWinLongPath(DestName,LongName2,ASIZE(LongName2))) - Success=MoveFile(LongName1,LongName2)!=0; + std::wstring LongName1,LongName2; + if (GetWinLongPath(SrcName,LongName1) && GetWinLongPath(DestName,LongName2)) + Success=MoveFile(LongName1.c_str(),LongName2.c_str())!=0; } return Success; #else - char SrcNameA[NM],DestNameA[NM]; - WideToChar(SrcName,SrcNameA,ASIZE(SrcNameA)); - WideToChar(DestName,DestNameA,ASIZE(DestNameA)); - bool Success=rename(SrcNameA,DestNameA)==0; + std::string SrcNameA,DestNameA; + WideToChar(SrcName,SrcNameA); + WideToChar(DestName,DestNameA); + bool Success=rename(SrcNameA.c_str(),DestNameA.c_str())==0; return Success; #endif } -bool DelFile(const wchar *Name) +bool DelFile(const std::wstring &Name) { #ifdef _WIN_ALL - bool Success=DeleteFile(Name)!=0; + bool Success=DeleteFile(Name.c_str())!=0; if (!Success) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - Success=DeleteFile(LongName)!=0; + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + Success=DeleteFile(LongName.c_str())!=0; } return Success; #else - char NameA[NM]; - WideToChar(Name,NameA,ASIZE(NameA)); - bool Success=remove(NameA)==0; + std::string NameA; + WideToChar(Name,NameA); + bool Success=remove(NameA.c_str())==0; return Success; #endif } +bool DelDir(const std::wstring &Name) +{ +#ifdef _WIN_ALL + bool Success=RemoveDirectory(Name.c_str())!=0; + if (!Success) + { + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + Success=RemoveDirectory(LongName.c_str())!=0; + } + return Success; +#else + std::string NameA; + WideToChar(Name,NameA); + bool Success=rmdir(NameA.c_str())==0; + return Success; +#endif +} #if defined(_WIN_ALL) && !defined(SFX_MODULE) -bool SetFileCompression(const wchar *Name,bool State) +bool SetFileCompression(const std::wstring &Name,bool State) { - HANDLE hFile=CreateFile(Name,FILE_READ_DATA|FILE_WRITE_DATA, + HANDLE hFile=CreateFile(Name.c_str(),FILE_READ_DATA|FILE_WRITE_DATA, FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL); if (hFile==INVALID_HANDLE_VALUE) { - wchar LongName[NM]; - if (GetWinLongPath(Name,LongName,ASIZE(LongName))) - hFile=CreateFile(LongName,FILE_READ_DATA|FILE_WRITE_DATA, + std::wstring LongName; + if (GetWinLongPath(Name,LongName)) + hFile=CreateFile(LongName.c_str(),FILE_READ_DATA|FILE_WRITE_DATA, FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL); + if (hFile==INVALID_HANDLE_VALUE) + return false; } - if (hFile==INVALID_HANDLE_VALUE) - return false; + bool Success=SetFileCompression(hFile,State); + CloseHandle(hFile); + return Success; +} + + +bool SetFileCompression(HANDLE hFile,bool State) +{ SHORT NewState=State ? COMPRESSION_FORMAT_DEFAULT:COMPRESSION_FORMAT_NONE; DWORD Result; int RetCode=DeviceIoControl(hFile,FSCTL_SET_COMPRESSION,&NewState, sizeof(NewState),NULL,0,&Result,NULL); - CloseHandle(hFile); return RetCode!=0; } + + +void ResetFileCache(const std::wstring &Name) +{ + // To reset file cache in Windows it is enough to open it with + // FILE_FLAG_NO_BUFFERING and then close it. + HANDLE hSrc=CreateFile(Name.c_str(),GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL,OPEN_EXISTING,FILE_FLAG_NO_BUFFERING,NULL); + if (hSrc!=INVALID_HANDLE_VALUE) + CloseHandle(hSrc); +} #endif @@ -509,3 +559,73 @@ bool SetFileCompression(const wchar *Name,bool State) + + + + + + +// Delete symbolic links in file path, if any, and replace them by directories. +// Prevents extracting files outside of destination folder with symlink chains. +bool LinksToDirs(const std::wstring &SrcName,const std::wstring &SkipPart,std::wstring &LastChecked) +{ + // Unlike Unix, Windows doesn't expand lnk1 in symlink targets like + // "lnk1/../dir", but converts the path to "dir". In Unix we need to call + // this function to prevent placing unpacked files outside of destination + // folder if previously we unpacked "dir/lnk1" -> "..", + // "dir/lnk2" -> "lnk1/.." and "dir/lnk2/anypath/poc.txt". + // We may still need this function to prevent abusing symlink chains + // in link source path if we remove detection of such chains + // in IsRelativeSymlinkSafe. This function seems to make other symlink + // related safety checks redundant, but for now we prefer to keep them too. + // + // 2022.12.01: the performance impact is minimized after adding the check + // against the previous path and enabling this verification only after + // extracting a symlink with ".." in target. So we enabled it for Windows + // as well for extra safety. +//#ifdef _UNIX + std::wstring Path=SrcName; + + size_t SkipLength=SkipPart.size(); + + if (SkipLength>0 && !starts_with(Path,SkipPart)) + SkipLength=0; // Parameter validation, not really needed now. + + // Do not check parts already checked in previous path to improve performance. + for (size_t I=0;ISkipLength) + SkipLength=I; + + // Avoid converting symlinks in destination path part specified by user. + while (SkipLength0) + for (size_t I=Path.size()-1;I>SkipLength;I--) + if (IsPathDiv(Path[I])) + { + Path.erase(I); + FindData FD; + if (FindFile::FastFind(Path,&FD,true) && FD.IsLink) + { +#ifdef _WIN_ALL + // Normally Windows symlinks to directory look like a directory + // and are deleted with DelDir(). It is possible to create + // a file-like symlink pointing at directory, which can be deleted + // only with && DelFile, but such symlink isn't really functional. + // Here we prefer to fail deleting such symlink and skip extracting + // a file. + if (!DelDir(Path)) +#else + if (!DelFile(Path)) +#endif + { + ErrHandler.CreateErrorMsg(SrcName); // Extraction command will skip this file or directory. + return false; // Couldn't delete the symlink to replace it with directory. + } + } + } + LastChecked=SrcName; +//#endif + return true; +} diff --git a/unrar/filefn.hpp b/unrar/filefn.hpp index 1bda9b58..8ac7c10f 100644 --- a/unrar/filefn.hpp +++ b/unrar/filefn.hpp @@ -3,48 +3,55 @@ enum MKDIR_CODE {MKDIR_SUCCESS,MKDIR_ERROR,MKDIR_BADPATH}; -MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr); -bool CreatePath(const wchar *Path,bool SkipLastName); -void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta); -bool IsRemovable(const wchar *Name); +MKDIR_CODE MakeDir(const std::wstring &Name,bool SetAttr,uint Attr); +bool CreateDir(const std::wstring &Name); +bool CreatePath(const std::wstring &Path,bool SkipLastName,bool Silent); + +void SetDirTime(const std::wstring &Name,RarTime *ftm,RarTime *ftc,RarTime *fta); + + +bool IsRemovable(const std::wstring &Name); #ifndef SFX_MODULE -int64 GetFreeDisk(const wchar *Name); +int64 GetFreeDisk(const std::wstring &Name); #endif #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) -bool IsFAT(const wchar *Root); +bool IsFAT(const std::wstring &Root); #endif -bool FileExist(const wchar *Name); -bool WildFileExist(const wchar *Name); +bool FileExist(const std::wstring &Name); +bool WildFileExist(const std::wstring &Name); bool IsDir(uint Attr); bool IsUnreadable(uint Attr); bool IsLink(uint Attr); -void SetSFXMode(const wchar *FileName); -void EraseDiskContents(const wchar *FileName); +void SetSFXMode(const std::wstring &FileName); +void EraseDiskContents(const std::wstring &FileName); bool IsDeleteAllowed(uint FileAttr); -void PrepareToDelete(const wchar *Name); -uint GetFileAttr(const wchar *Name); -bool SetFileAttr(const wchar *Name,uint Attr); -#if 0 -wchar* MkTemp(wchar *Name,size_t MaxSize); -#endif +void PrepareToDelete(const std::wstring &Name); +uint GetFileAttr(const std::wstring &Name); +bool SetFileAttr(const std::wstring &Name,uint Attr); +bool MkTemp(std::wstring &Name,const wchar *Ext); enum CALCFSUM_FLAGS {CALCFSUM_SHOWTEXT=1,CALCFSUM_SHOWPERCENT=2,CALCFSUM_SHOWPROGRESS=4,CALCFSUM_CURPOS=8}; void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size=INT64NDF,uint Flags=0); -bool RenameFile(const wchar *SrcName,const wchar *DestName); -bool DelFile(const wchar *Name); -bool DelDir(const wchar *Name); +bool RenameFile(const std::wstring &SrcName,const std::wstring &DestName); +bool DelFile(const std::wstring &Name); +bool DelDir(const std::wstring &Name); #if defined(_WIN_ALL) && !defined(SFX_MODULE) -bool SetFileCompression(const wchar *Name,bool State); +bool SetFileCompression(const std::wstring &Name,bool State); +bool SetFileCompression(HANDLE hFile,bool State); +void ResetFileCache(const std::wstring &Name); #endif +// Keep it here and not in extinfo.cpp, because it is invoked from Zip.SFX too. +bool LinksToDirs(const std::wstring &SrcName,const std::wstring &SkipPart,std::wstring &LastChecked); + #endif diff --git a/unrar/filestr.cpp b/unrar/filestr.cpp index a5d29d74..ec45654d 100644 --- a/unrar/filestr.cpp +++ b/unrar/filestr.cpp @@ -1,7 +1,7 @@ #include "rar.hpp" bool ReadTextFile( - const wchar *Name, + const std::wstring &Name, StringList *List, bool Config, bool AbortOnError, @@ -10,17 +10,15 @@ bool ReadTextFile( bool SkipComments, bool ExpandEnvStr) { - wchar FileName[NM]; - *FileName=0; + std::wstring FileName; - if (Name!=NULL) - if (Config) - GetConfigName(Name,FileName,ASIZE(FileName),true,false); - else - wcsncpyz(FileName,Name,ASIZE(FileName)); + if (Config) + GetConfigName(Name,FileName,true,false); + else + FileName=Name; File SrcFile; - if (*FileName!=0) + if (!FileName.empty()) { bool OpenCode=AbortOnError ? SrcFile.WOpen(FileName):SrcFile.Open(FileName,0); @@ -34,36 +32,36 @@ bool ReadTextFile( else SrcFile.SetHandleType(FILE_HANDLESTD); - uint DataSize=0,ReadSize; + size_t DataSize=0,ReadSize; const int ReadBlock=4096; - Array Data(ReadBlock); + std::vector Data(ReadBlock); while ((ReadSize=SrcFile.Read(&Data[DataSize],ReadBlock))!=0) { DataSize+=ReadSize; - Data.Add(ReadSize); // Always have ReadBlock available for next data. + Data.resize(DataSize+ReadBlock); // Always have ReadBlock available for next data. } // Set to really read size, so we can zero terminate it correctly. - Data.Alloc(DataSize); + Data.resize(DataSize); int LittleEndian=DataSize>=2 && Data[0]==255 && Data[1]==254 ? 1:0; int BigEndian=DataSize>=2 && Data[0]==254 && Data[1]==255 ? 1:0; bool Utf8=DataSize>=3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf; if (SrcCharset==RCH_DEFAULT) - SrcCharset=DetectTextEncoding(&Data[0],DataSize); + SrcCharset=DetectTextEncoding(Data.data(),DataSize); - Array DataW; + std::vector DataW(ReadBlock); if (SrcCharset==RCH_DEFAULT || SrcCharset==RCH_OEM || SrcCharset==RCH_ANSI) { - Data.Push(0); // Zero terminate. + Data.push_back(0); // Zero terminate. #if defined(_WIN_ALL) if (SrcCharset==RCH_OEM) - OemToCharA((char *)&Data[0],(char *)&Data[0]); + OemToCharA((char *)Data.data(),(char *)Data.data()); #endif - DataW.Alloc(Data.Size()); - CharToWide((char *)&Data[0],&DataW[0],DataW.Size()); + DataW.resize(Data.size()); + CharToWide((char *)Data.data(),DataW.data(),DataW.size()); } if (SrcCharset==RCH_UNICODE) @@ -75,8 +73,8 @@ bool ReadTextFile( LittleEndian=1; } - DataW.Alloc(Data.Size()/2+1); - size_t End=Data.Size() & ~1; // We need even bytes number for UTF-16. + DataW.resize(Data.size()/2+1); + size_t End=Data.size() & ~1; // We need even bytes number for UTF-16. for (size_t I=Start;IAddString(ExpName); + Expanded=true; } #endif if (!Expanded && *CurStr!=0) diff --git a/unrar/filestr.hpp b/unrar/filestr.hpp index febd0a29..3c281304 100644 --- a/unrar/filestr.hpp +++ b/unrar/filestr.hpp @@ -2,7 +2,7 @@ #define _RAR_FILESTR_ bool ReadTextFile( - const wchar *Name, + const std::wstring &Name, StringList *List, bool Config, bool AbortOnError=false, diff --git a/unrar/find.cpp b/unrar/find.cpp index b22f82d8..64d933eb 100644 --- a/unrar/find.cpp +++ b/unrar/find.cpp @@ -2,7 +2,6 @@ FindFile::FindFile() { - *FindMask=0; FirstCall=true; #ifdef _WIN_ALL hFind=INVALID_HANDLE_VALUE; @@ -24,9 +23,9 @@ FindFile::~FindFile() } -void FindFile::SetMask(const wchar *Mask) +void FindFile::SetMask(const std::wstring &Mask) { - wcsncpyz(FindMask,Mask,ASIZE(FindMask)); + FindMask=Mask; FirstCall=true; } @@ -34,7 +33,7 @@ void FindFile::SetMask(const wchar *Mask) bool FindFile::Next(FindData *fd,bool GetSymLink) { fd->Error=false; - if (*FindMask==0) + if (FindMask.empty()) return false; #ifdef _WIN_ALL if (FirstCall) @@ -48,14 +47,14 @@ bool FindFile::Next(FindData *fd,bool GetSymLink) #else if (FirstCall) { - wchar DirName[NM]; - wcsncpyz(DirName,FindMask,ASIZE(DirName)); + std::wstring DirName; + DirName=FindMask; RemoveNameFromPath(DirName); - if (*DirName==0) - wcsncpyz(DirName,L".",ASIZE(DirName)); - char DirNameA[NM]; - WideToChar(DirName,DirNameA,ASIZE(DirNameA)); - if ((dirp=opendir(DirNameA))==NULL) + if (DirName.empty()) + DirName=L"."; + std::string DirNameA; + WideToChar(DirName,DirNameA); + if ((dirp=opendir(DirNameA.c_str()))==NULL) { fd->Error=(errno!=ENOENT); return false; @@ -63,32 +62,31 @@ bool FindFile::Next(FindData *fd,bool GetSymLink) } while (1) { - wchar Name[NM]; + std::wstring Name; struct dirent *ent=readdir(dirp); if (ent==NULL) return false; if (strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0) continue; - if (!CharToWide(ent->d_name,Name,ASIZE(Name))) - uiMsg(UIERROR_INVALIDNAME,UINULL,Name); + if (!CharToWide(std::string(ent->d_name),Name)) + uiMsg(UIERROR_INVALIDNAME,L"",Name); if (CmpName(FindMask,Name,MATCH_NAMES)) { - wchar FullName[NM]; - wcsncpyz(FullName,FindMask,ASIZE(FullName)); - *PointToName(FullName)=0; - if (wcslen(FullName)+wcslen(Name)>=ASIZE(FullName)-1) + std::wstring FullName=FindMask; + FullName.erase(GetNamePos(FullName)); + if (FullName.size()+Name.size()>=MAXPATHSIZE) { uiMsg(UIERROR_PATHTOOLONG,FullName,L"",Name); return false; } - wcsncatz(FullName,Name,ASIZE(FullName)); + FullName+=Name; if (!FastFind(FullName,fd,GetSymLink)) { ErrHandler.OpenErrorMsg(FullName); continue; } - wcsncpyz(fd->Name,FullName,ASIZE(fd->Name)); + fd->Name=FullName; break; } } @@ -98,14 +96,14 @@ bool FindFile::Next(FindData *fd,bool GetSymLink) fd->IsLink=IsLink(fd->FileAttr); FirstCall=false; - wchar *NameOnly=PointToName(fd->Name); - if (wcscmp(NameOnly,L".")==0 || wcscmp(NameOnly,L"..")==0) + std::wstring NameOnly=PointToName(fd->Name); + if (NameOnly==L"." || NameOnly==L"..") return Next(fd); return true; } -bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) +bool FindFile::FastFind(const std::wstring &FindMask,FindData *fd,bool GetSymLink) { fd->Error=false; #ifndef _UNIX @@ -117,17 +115,17 @@ bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) if (hFind==INVALID_HANDLE_VALUE) return false; FindClose(hFind); -#else - char FindMaskA[NM]; - WideToChar(FindMask,FindMaskA,ASIZE(FindMaskA)); +#elif defined(_UNIX) + std::string FindMaskA; + WideToChar(FindMask,FindMaskA); struct stat st; if (GetSymLink) { #ifdef SAVE_LINKS - if (lstat(FindMaskA,&st)!=0) + if (lstat(FindMaskA.c_str(),&st)!=0) #else - if (stat(FindMaskA,&st)!=0) + if (stat(FindMaskA.c_str(),&st)!=0) #endif { fd->Error=(errno!=ENOENT); @@ -135,7 +133,7 @@ bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) } } else - if (stat(FindMaskA,&st)!=0) + if (stat(FindMaskA.c_str(),&st)!=0) { fd->Error=(errno!=ENOENT); return false; @@ -143,17 +141,9 @@ bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) fd->FileAttr=st.st_mode; fd->Size=st.st_size; -#ifdef UNIX_TIME_NS - fd->mtime.SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec); - fd->atime.SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec); - fd->ctime.SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec); -#else - fd->mtime.SetUnix(st.st_mtime); - fd->atime.SetUnix(st.st_atime); - fd->ctime.SetUnix(st.st_ctime); -#endif + File::StatToRarTime(st,&fd->mtime,&fd->ctime,&fd->atime); - wcsncpyz(fd->Name,FindMask,ASIZE(fd->Name)); + fd->Name=FindMask; #endif fd->Flags=0; fd->IsDir=IsDir(fd->FileAttr); @@ -164,17 +154,17 @@ bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) #ifdef _WIN_ALL -HANDLE FindFile::Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd) +HANDLE FindFile::Win32Find(HANDLE hFind,const std::wstring &Mask,FindData *fd) { WIN32_FIND_DATA FindData; if (hFind==INVALID_HANDLE_VALUE) { - hFind=FindFirstFile(Mask,&FindData); + hFind=FindFirstFile(Mask.c_str(),&FindData); if (hFind==INVALID_HANDLE_VALUE) { - wchar LongMask[NM]; - if (GetWinLongPath(Mask,LongMask,ASIZE(LongMask))) - hFind=FindFirstFile(LongMask,&FindData); + std::wstring LongMask; + if (GetWinLongPath(Mask,LongMask)) + hFind=FindFirstFile(LongMask.c_str(),&FindData); } if (hFind==INVALID_HANDLE_VALUE) { @@ -198,8 +188,8 @@ HANDLE FindFile::Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd) if (hFind!=INVALID_HANDLE_VALUE) { - wcsncpyz(fd->Name,Mask,ASIZE(fd->Name)); - SetName(fd->Name,FindData.cFileName,ASIZE(fd->Name)); + fd->Name=Mask; + SetName(fd->Name,FindData.cFileName); fd->Size=INT32TO64(FindData.nFileSizeHigh,FindData.nFileSizeLow); fd->FileAttr=FindData.dwFileAttributes; fd->ftCreationTime=FindData.ftCreationTime; diff --git a/unrar/find.hpp b/unrar/find.hpp index 250637f8..6812def7 100644 --- a/unrar/find.hpp +++ b/unrar/find.hpp @@ -7,7 +7,7 @@ enum FINDDATA_FLAGS { struct FindData { - wchar Name[NM]; + std::wstring Name; uint64 Size; uint FileAttr; bool IsDir; @@ -28,10 +28,10 @@ class FindFile { private: #ifdef _WIN_ALL - static HANDLE Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd); + static HANDLE Win32Find(HANDLE hFind,const std::wstring &Mask,FindData *fd); #endif - wchar FindMask[NM]; + std::wstring FindMask; bool FirstCall; #ifdef _WIN_ALL HANDLE hFind; @@ -41,9 +41,9 @@ class FindFile public: FindFile(); ~FindFile(); - void SetMask(const wchar *Mask); + void SetMask(const std::wstring &Mask); bool Next(FindData *fd,bool GetSymLink=false); - static bool FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink=false); + static bool FastFind(const std::wstring &FindMask,FindData *fd,bool GetSymLink=false); }; #endif diff --git a/unrar/getbits.cpp b/unrar/getbits.cpp index e4db2695..c414548a 100644 --- a/unrar/getbits.cpp +++ b/unrar/getbits.cpp @@ -5,11 +5,11 @@ BitInput::BitInput(bool AllocBuffer) ExternalBuffer=false; if (AllocBuffer) { - // getbits32 attempts to read data from InAddr, ... InAddr+3 positions. - // So let's allocate 3 additional bytes for situation, when we need to + // getbits*() attempt to read data from InAddr, ... InAddr+8 positions. + // So let's allocate 8 additional bytes for situation, when we need to // read only 1 byte from the last position of buffer and avoid a crash - // from access to next 3 bytes, which contents we do not need. - size_t BufSize=MAX_SIZE+3; + // from access to next 8 bytes, which contents we do not need. + size_t BufSize=MAX_SIZE+8; InBuf=new byte[BufSize]; // Ensure that we get predictable results when accessing bytes in area @@ -17,7 +17,7 @@ BitInput::BitInput(bool AllocBuffer) memset(InBuf,0,BufSize); } else - InBuf=NULL; + InBuf=nullptr; } @@ -30,21 +30,21 @@ BitInput::~BitInput() void BitInput::faddbits(uint Bits) { - // Function wrapped version of inline addbits to save code size. + // Function wrapped version of inline addbits to reduce the code size. addbits(Bits); } uint BitInput::fgetbits() { - // Function wrapped version of inline getbits to save code size. + // Function wrapped version of inline getbits to reduce the code size. return getbits(); } void BitInput::SetExternalBuffer(byte *Buf) { - if (InBuf!=NULL && !ExternalBuffer) + if (InBuf!=nullptr && !ExternalBuffer) delete[] InBuf; InBuf=Buf; ExternalBuffer=true; diff --git a/unrar/getbits.hpp b/unrar/getbits.hpp index 2e151da9..65fb25a1 100644 --- a/unrar/getbits.hpp +++ b/unrar/getbits.hpp @@ -28,30 +28,43 @@ class BitInput InAddr+=Bits>>3; InBit=Bits&7; } - + // Return 16 bits from current position in the buffer. // Bit at (InAddr,InBit) has the highest position in returning data. uint getbits() { +#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) + uint32 BitField=RawGetBE4(InBuf+InAddr); + BitField >>= (16-InBit); +#else uint BitField=(uint)InBuf[InAddr] << 16; BitField|=(uint)InBuf[InAddr+1] << 8; BitField|=(uint)InBuf[InAddr+2]; BitField >>= (8-InBit); +#endif return BitField & 0xffff; } + // Return 32 bits from current position in the buffer. // Bit at (InAddr,InBit) has the highest position in returning data. uint getbits32() { - uint BitField=(uint)InBuf[InAddr] << 24; - BitField|=(uint)InBuf[InAddr+1] << 16; - BitField|=(uint)InBuf[InAddr+2] << 8; - BitField|=(uint)InBuf[InAddr+3]; + uint BitField=RawGetBE4(InBuf+InAddr); BitField <<= InBit; BitField|=(uint)InBuf[InAddr+4] >> (8-InBit); return BitField & 0xffffffff; } + + // Return 64 bits from current position in the buffer. + // Bit at (InAddr,InBit) has the highest position in returning data. + uint64 getbits64() + { + uint64 BitField=RawGetBE8(InBuf+InAddr); + BitField <<= InBit; + BitField|=(uint)InBuf[InAddr+8] >> (8-InBit); + return BitField; + } void faddbits(uint Bits); uint fgetbits(); diff --git a/unrar/global.cpp b/unrar/global.cpp index 3975813a..771f0001 100644 --- a/unrar/global.cpp +++ b/unrar/global.cpp @@ -1,6 +1,6 @@ #define INCLUDEGLOBAL -#if defined(__BORLANDC__) || defined(_MSC_VER) +#ifdef _MSC_VER #pragma hdrstop #endif diff --git a/unrar/hardlinks.cpp b/unrar/hardlinks.cpp index 946a3952..5080da05 100644 --- a/unrar/hardlinks.cpp +++ b/unrar/hardlinks.cpp @@ -1,7 +1,5 @@ -bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +bool ExtractHardlink(CommandData *Cmd,const std::wstring &NameNew,const std::wstring &NameExisting) { - SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. - if (!FileExist(NameExisting)) { uiMsg(UIERROR_HLINKCREATE,NameNew); @@ -9,10 +7,10 @@ bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) ErrHandler.SetErrorCode(RARX_CREATE); return false; } - CreatePath(NameNew,true); + CreatePath(NameNew,true,Cmd->DisableNames); #ifdef _WIN_ALL - bool Success=CreateHardLink(NameNew,NameExisting,NULL)!=0; + bool Success=CreateHardLink(NameNew.c_str(),NameExisting.c_str(),NULL)!=0; if (!Success) { uiMsg(UIERROR_HLINKCREATE,NameNew); @@ -21,10 +19,10 @@ bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) } return Success; #elif defined(_UNIX) - char NameExistingA[NM],NameNewA[NM]; - WideToChar(NameExisting,NameExistingA,ASIZE(NameExistingA)); - WideToChar(NameNew,NameNewA,ASIZE(NameNewA)); - bool Success=link(NameExistingA,NameNewA)==0; + std::string NameExistingA,NameNewA; + WideToChar(NameExisting,NameExistingA); + WideToChar(NameNew,NameNewA); + bool Success=link(NameExistingA.c_str(),NameNewA.c_str())==0; if (!Success) { uiMsg(UIERROR_HLINKCREATE,NameNew); diff --git a/unrar/hash.cpp b/unrar/hash.cpp index a4559e05..d07b2d24 100644 --- a/unrar/hash.cpp +++ b/unrar/hash.cpp @@ -26,7 +26,7 @@ void HashValue::Init(HASH_TYPE Type) } -bool HashValue::operator == (const HashValue &cmp) +bool HashValue::operator == (const HashValue &cmp) const { if (Type==HASH_NONE || cmp.Type==HASH_NONE) return true; @@ -76,7 +76,7 @@ void DataHash::Init(HASH_TYPE Type,uint MaxThreads) if (Type==HASH_BLAKE2) blake2sp_init(blake2ctx); #ifdef RAR_SMP - DataHash::MaxThreads=Min(MaxThreads,MaxHashThreads); + DataHash::MaxThreads=Min(MaxThreads,HASH_POOL_THREADS); #endif } @@ -88,13 +88,19 @@ void DataHash::Update(const void *Data,size_t DataSize) CurCRC32=Checksum14((ushort)CurCRC32,Data,DataSize); #endif if (HashType==HASH_CRC32) + { +#ifdef RAR_SMP + UpdateCRC32MT(Data,DataSize); +#else CurCRC32=CRC32(CurCRC32,Data,DataSize); +#endif + } if (HashType==HASH_BLAKE2) { #ifdef RAR_SMP - if (MaxThreads>1 && ThPool==NULL) - ThPool=new ThreadPool(BLAKE2_THREADS_NUMBER); + if (MaxThreads>1 && ThPool==nullptr) + ThPool=new ThreadPool(HASH_POOL_THREADS); blake2ctx->ThPool=ThPool; blake2ctx->MaxThreads=MaxThreads; #endif @@ -103,6 +109,146 @@ void DataHash::Update(const void *Data,size_t DataSize) } +#ifdef RAR_SMP +THREAD_PROC(BuildCRC32Thread) +{ + DataHash::CRC32ThreadData *td=(DataHash::CRC32ThreadData *)Data; + + // Use 0 initial value to simplify combining the result with existing CRC32. + // It doesn't affect the first initial 0xffffffff in the data beginning. + // If we used 0xffffffff here, we would need to shift 0xffffffff left to + // block width and XOR it with block CRC32 to reset its initial value to 0. + td->DataCRC=CRC32(0,td->Data,td->DataSize); +} + + +// CRC is linear and distributive over addition, so CRC(a+b)=CRC(a)+CRC(b). +// Since addition in finite field is XOR, we have CRC(a^b)=CRC(a)^CRC(b). +// So CRC(aaabbb) = CRC(aaa000) ^ CRC(000bbb) = CRC(aaa000) ^ CRC(bbb), +// because CRC ignores leading zeroes. Thus to split CRC calculations +// to "aaa" and "bbb" blocks and then to threads we need to be able to +// find CRC(aaa000) knowing "aaa" quickly. We use Galois finite field to +// calculate the power of 2 to get "1000" and multiply it by "aaa". +void DataHash::UpdateCRC32MT(const void *Data,size_t DataSize) +{ + const size_t MinBlock=0x4000; + if (DataSize<2*MinBlock || MaxThreads<2) + { + CurCRC32=CRC32(CurCRC32,Data,DataSize); + return; + } + + if (ThPool==nullptr) + ThPool=new ThreadPool(HASH_POOL_THREADS); + + size_t Threads=MaxThreads; + size_t BlockSize=DataSize/Threads; + if (BlockSizeAddTask(BuildCRC32Thread,(void*)&td[I]); +#else + BuildCRC32Thread((void*)&td[I]); +#endif + } + +#ifdef USE_THREADS + ThPool->WaitDone(); +#endif // USE_THREADS + + uint StdShift=gfExpCRC(uint(8*td[0].DataSize)); + for (size_t I=0;I>=1) + Reversed|=(N & 1)<<(31-I); + return Reversed; +} + + +// Galois field multiplication modulo POLY. +uint DataHash::gfMulCRC(uint A, uint B) +{ + // For reversed 0xEDB88320 polynomial we bit reverse CRC32 before passing + // to this function, so we must use the normal polynomial here. + // We set the highest polynomial bit 33 for proper multiplication + // in case uint is larger than 32-bit. + const uint POLY=uint(0x104c11db7); + + uint R = 0 ; // Multiplication result. + while (A != 0 && B != 0) // If any of multipliers becomes 0, quit early. + { + // For non-zero lowest B bit, add A to result. + R ^= (B & 1)!=0 ? A : 0; + + // Make A twice larger before the next iteration. + // Subtract POLY to keep it modulo POLY if high bit is set. + A = (A << 1) ^ ((A & 0x80000000)!=0 ? POLY : 0); + + B >>= 1; // Move next B bit to lowest position. + } + return R; +} + + +// Calculate 2 power N with square-and-multiply algorithm. +uint DataHash::gfExpCRC(uint N) +{ + uint S = 2; // Starts from base value and contains the current square. + uint R = 1; // Exponentiation result. + while (N > 1) + { + if ((N & 1)!=0) // If N is odd. + R = gfMulCRC(R, S); + S = gfMulCRC(S, S); // Next square. + N >>= 1; + } + // We could change the loop condition to N > 0 and return R at expense + // of one additional gfMulCRC(S, S). + return gfMulCRC(R, S); +} + + void DataHash::Result(HashValue *Result) { Result->Type=HashType; @@ -129,7 +275,9 @@ bool DataHash::Cmp(HashValue *CmpValue,byte *Key) { HashValue Final; Result(&Final); - if (Key!=NULL) +#ifndef RAR_NOCRYPT + if (Key!=nullptr) ConvertHashToMAC(&Final,Key); +#endif return Final==*CmpValue; } diff --git a/unrar/hash.hpp b/unrar/hash.hpp index b7d879f6..99488e6a 100644 --- a/unrar/hash.hpp +++ b/unrar/hash.hpp @@ -6,8 +6,16 @@ enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2}; struct HashValue { void Init(HASH_TYPE Type); - bool operator == (const HashValue &cmp); - bool operator != (const HashValue &cmp) {return !(*this==cmp);} + + // Use the const member, so types on both sides of "==" match. + // Otherwise clang -std=c++20 issues "ambiguity is between a regular call + // to this operator and a call with the argument order reversed" warning. + bool operator == (const HashValue &cmp) const; + + // Not actually used now. Const member for same reason as operator == above. + // Can be removed after switching to C++20, which automatically provides "!=" + // if operator == is defined. + bool operator != (const HashValue &cmp) const {return !(*this==cmp);} HASH_TYPE Type; union @@ -26,7 +34,24 @@ class DataHash; class DataHash { + public: + struct CRC32ThreadData + { + void *Data; + size_t DataSize; + uint DataCRC; + }; private: + void UpdateCRC32MT(const void *Data,size_t DataSize); + uint BitReverse32(uint N); + uint gfMulCRC(uint A, uint B); + uint gfExpCRC(uint N); + + // Speed gain seems to vanish above 8 CRC32 threads. + static const uint CRC32_POOL_THREADS=8; + // Thread pool must allow at least BLAKE2_THREADS_NUMBER threads. + static const uint HASH_POOL_THREADS=Max(BLAKE2_THREADS_NUMBER,CRC32_POOL_THREADS); + HASH_TYPE HashType; uint CurCRC32; blake2sp_state *blake2ctx; @@ -35,8 +60,6 @@ class DataHash ThreadPool *ThPool; uint MaxThreads; - // Upper limit for maximum threads to prevent wasting threads in pool. - static const uint MaxHashThreads=8; #endif public: DataHash(); diff --git a/unrar/headers.cpp b/unrar/headers.cpp index b042dc39..b2d00d3b 100644 --- a/unrar/headers.cpp +++ b/unrar/headers.cpp @@ -2,7 +2,7 @@ void FileHeader::Reset(size_t SubDataSize) { - SubData.Alloc(SubDataSize); + SubData.resize(SubDataSize); BaseBlock::Reset(); FileHash.Init(HASH_NONE); mtime.Reset(); @@ -37,6 +37,7 @@ void FileHeader::Reset(size_t SubDataSize) } +/* FileHeader& FileHeader::operator = (FileHeader &hd) { SubData.Reset(); @@ -45,17 +46,10 @@ FileHeader& FileHeader::operator = (FileHeader &hd) SubData=hd.SubData; return *this; } +*/ void MainHeader::Reset() { - HighPosAV=0; - PosAV=0; - CommentInHeader=false; - PackComment=false; - Locator=false; - QOpenOffset=0; - QOpenMaxSize=0; - RROffset=0; - RRMaxSize=0; + *this={}; } diff --git a/unrar/headers.hpp b/unrar/headers.hpp index 67a63aec..87814ce1 100644 --- a/unrar/headers.hpp +++ b/unrar/headers.hpp @@ -6,18 +6,19 @@ #define SIZEOF_MAINHEAD3 13 // Size of RAR 4.x main archive header. #define SIZEOF_FILEHEAD14 21 // Size of RAR 1.4 file header. #define SIZEOF_FILEHEAD3 32 // Size of RAR 3.0 file header. -#define SIZEOF_SHORTBLOCKHEAD 7 +#define SIZEOF_SHORTBLOCKHEAD 7 // Smallest RAR 4.x block size. #define SIZEOF_LONGBLOCKHEAD 11 #define SIZEOF_SUBBLOCKHEAD 14 #define SIZEOF_COMMHEAD 13 #define SIZEOF_PROTECTHEAD 26 -#define SIZEOF_UOHEAD 18 #define SIZEOF_STREAMHEAD 26 #define VER_PACK 29U #define VER_PACK5 50U // It is stored as 0, but we subtract 50 when saving an archive. +#define VER_PACK7 70U // It is stored as 1, but we subtract 70 when saving an archive. #define VER_UNPACK 29U #define VER_UNPACK5 50U // It is stored as 0, but we add 50 when reading an archive. +#define VER_UNPACK7 70U // It is stored as 1, but we add 50 when reading an archive. #define VER_UNKNOWN 9999U // Just some large value. #define MHD_VOLUME 0x0001U @@ -81,7 +82,7 @@ enum HEADER_TYPE { }; -// RAR 2.9 and earlier. +// RAR 2.9 and earlier service haeders, mostly outdated and not supported. enum { EA_HEAD=0x100,UO_HEAD=0x101,MAC_HEAD=0x102,BEEA_HEAD=0x103, NTACL_HEAD=0x104,STREAM_HEAD=0x105 }; @@ -146,6 +147,14 @@ struct BaseBlock { SkipIfUnknown=false; } + + // We use it to assign this block data to inherited blocks. + // Such function seems to be cleaner than '(BaseBlock&)' cast or adding + // 'using BaseBlock::operator=;' to every inherited header. + void SetBaseBlock(BaseBlock &Src) + { + *this=Src; + } }; @@ -160,12 +169,16 @@ struct MainHeader:BaseBlock ushort HighPosAV; uint PosAV; bool CommentInHeader; - bool PackComment; // For RAR 1.4 archive format only. + bool PackComment; // For RAR 1.4 archive format only. bool Locator; - uint64 QOpenOffset; // Offset of quick list record. - uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field. - uint64 RROffset; // Offset of recovery record. - uint64 RRMaxSize; // Maximum size of RR offset in locator extra field. + uint64 QOpenOffset; // Offset of quick list record. + uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field. + uint64 RROffset; // Offset of recovery record. + uint64 RRMaxSize; // Maximum size of RR offset in locator extra field. + size_t MetaNameMaxSize; // Maximum size of archive name in metadata extra field. + std::wstring OrigName; // Original archive name. + RarTime OrigTime; // Original archive time. + void Reset(); }; @@ -179,9 +192,9 @@ struct FileHeader:BlockHeader uint FileAttr; uint SubFlags; }; - wchar FileName[NM]; + std::wstring FileName; - Array SubData; + std::vector SubData; RarTime mtime; RarTime ctime; @@ -221,20 +234,20 @@ struct FileHeader:BlockHeader bool Dir; bool CommentInHeader; // RAR 2.0 file comment. bool Version; // name.ext;ver file name containing the version number. - size_t WinSize; + uint64 WinSize; bool Inherited; // New file inherits a subblock when updating a host file (for subblocks only). // 'true' if file sizes use 8 bytes instead of 4. Not used in RAR 5.0. bool LargeFile; // 'true' for HEAD_SERVICE block, which is a child of preceding file block. - // RAR 4.x uses 'solid' flag to indicate child subheader blocks in archives. + // RAR 4.x uses 'solid' flag to indicate children subheader blocks in archives. bool SubBlock; HOST_SYSTEM_TYPE HSType; FILE_SYSTEM_REDIRECT RedirType; - wchar RedirName[NM]; + std::wstring RedirName; bool DirTarget; bool UnixOwnerSet,UnixOwnerNumeric,UnixGroupNumeric; @@ -251,10 +264,10 @@ struct FileHeader:BlockHeader bool CmpName(const wchar *Name) { - return(wcscmp(FileName,Name)==0); + return FileName==Name; } - FileHeader& operator = (FileHeader &hd); +// FileHeader& operator = (FileHeader &hd); }; @@ -319,16 +332,6 @@ struct ProtectHeader:BlockHeader }; -struct UnixOwnersHeader:SubBlockHeader -{ - ushort OwnerNameSize; - ushort GroupNameSize; -/* dummy */ - char OwnerName[256]; - char GroupName[256]; -}; - - struct EAHeader:SubBlockHeader { uint UnpSize; @@ -345,7 +348,7 @@ struct StreamHeader:SubBlockHeader byte Method; uint StreamCRC; ushort StreamNameSize; - char StreamName[260]; + std::string StreamName; }; diff --git a/unrar/headers5.hpp b/unrar/headers5.hpp index 9ea8d979..361e554d 100644 --- a/unrar/headers5.hpp +++ b/unrar/headers5.hpp @@ -42,28 +42,42 @@ // RAR 5.0 file compression flags. -#define FCI_ALGO_BIT0 0x0001 // Version of compression algorithm. -#define FCI_ALGO_BIT1 0x0002 // 0 .. 63. -#define FCI_ALGO_BIT2 0x0004 -#define FCI_ALGO_BIT3 0x0008 -#define FCI_ALGO_BIT4 0x0010 -#define FCI_ALGO_BIT5 0x0020 -#define FCI_SOLID 0x0040 // Solid flag. -#define FCI_METHOD_BIT0 0x0080 // Compression method. -#define FCI_METHOD_BIT1 0x0100 // 0 .. 5 (6 and 7 are not used). -#define FCI_METHOD_BIT2 0x0200 -#define FCI_DICT_BIT0 0x0400 // Dictionary size. -#define FCI_DICT_BIT1 0x0800 // 128 KB .. 4 GB. -#define FCI_DICT_BIT2 0x1000 -#define FCI_DICT_BIT3 0x2000 +#define FCI_ALGO_BIT0 0x00000001 // Version of compression algorithm. +#define FCI_ALGO_BIT1 0x00000002 // 0 .. 63. +#define FCI_ALGO_BIT2 0x00000004 +#define FCI_ALGO_BIT3 0x00000008 +#define FCI_ALGO_BIT4 0x00000010 +#define FCI_ALGO_BIT5 0x00000020 +#define FCI_SOLID 0x00000040 // Solid flag. +#define FCI_METHOD_BIT0 0x00000080 // Compression method. +#define FCI_METHOD_BIT1 0x00000100 // 0 .. 5 (6 and 7 are not used). +#define FCI_METHOD_BIT2 0x00000200 +#define FCI_DICT_BIT0 0x00000400 // Dictionary size. +#define FCI_DICT_BIT1 0x00000800 // 128 KB .. 1 TB. +#define FCI_DICT_BIT2 0x00001000 +#define FCI_DICT_BIT3 0x00002000 +#define FCI_DICT_BIT4 0x00004000 +#define FCI_DICT_FRACT0 0x00008000 // Dictionary fraction in 1/32 of size. +#define FCI_DICT_FRACT1 0x00010000 +#define FCI_DICT_FRACT2 0x00020000 +#define FCI_DICT_FRACT3 0x00040000 +#define FCI_DICT_FRACT4 0x00080000 +#define FCI_RAR5_COMPAT 0x00100000 // RAR7 compression flags and RAR5 compression algorithm. // Main header extra field values. #define MHEXTRA_LOCATOR 0x01 // Position of quick list and other blocks. +#define MHEXTRA_METADATA 0x02 // Archive metadata. // Flags for MHEXTRA_LOCATOR. #define MHEXTRA_LOCATOR_QLIST 0x01 // Quick open offset is present. #define MHEXTRA_LOCATOR_RR 0x02 // Recovery record offset is present. +// Flags for MHEXTRA_METADATA. +#define MHEXTRA_METADATA_NAME 0x01 // Archive name is present. +#define MHEXTRA_METADATA_CTIME 0x02 // Archive creation time is present. +#define MHEXTRA_METADATA_UNIXTIME 0x04 // Use Unix nanosecond time format. +#define MHEXTRA_METADATA_UNIX_NS 0x08 // Unix format with nanosecond precision. + // File and service header extra field values. #define FHEXTRA_CRYPT 0x01 // Encryption parameters. #define FHEXTRA_HASH 0x02 // File hash. diff --git a/unrar/isnt.cpp b/unrar/isnt.cpp index 6fadec04..f8dca884 100644 --- a/unrar/isnt.cpp +++ b/unrar/isnt.cpp @@ -13,6 +13,7 @@ DWORD WinNT() dwPlatformId=WinVer.dwPlatformId; dwMajorVersion=WinVer.dwMajorVersion; dwMinorVersion=WinVer.dwMinorVersion; + } DWORD Result=0; if (dwPlatformId==VER_PLATFORM_WIN32_NT) @@ -21,4 +22,92 @@ DWORD WinNT() return Result; } + +// Since Windows 10 development is stopped, we can assume that its build +// number never reaches 22000. So we do not need WMI anymore. +#if 0 +// Replace it with documented Windows 11 check when available. +#include +#include +#pragma comment(lib, "wbemuuid.lib") + +static bool WMI_IsWindows10() +{ + IWbemLocator *pLoc = NULL; + + HRESULT hres = CoCreateInstance(CLSID_WbemLocator,0,CLSCTX_INPROC_SERVER, + IID_IWbemLocator,(LPVOID *)&pLoc); + + if (FAILED(hres)) + return false; + + IWbemServices *pSvc = NULL; + + hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,NULL,0,NULL,NULL,&pSvc); + + if (FAILED(hres)) + { + pLoc->Release(); + return false; + } + + hres = CoSetProxyBlanket(pSvc,RPC_C_AUTHN_WINNT,RPC_C_AUTHZ_NONE,NULL, + RPC_C_AUTHN_LEVEL_CALL,RPC_C_IMP_LEVEL_IMPERSONATE,NULL,EOAC_NONE); + + if (FAILED(hres)) + { + pSvc->Release(); + pLoc->Release(); + return false; + } + + IEnumWbemClassObject *pEnumerator = NULL; + hres = pSvc->ExecQuery(bstr_t("WQL"), bstr_t("SELECT * FROM Win32_OperatingSystem"), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); + + if (FAILED(hres) || pEnumerator==NULL) + { + pSvc->Release(); + pLoc->Release(); + return false; + } + + bool Win10=false; + + IWbemClassObject *pclsObj = NULL; + ULONG uReturn = 0; + pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); + if (pclsObj!=NULL && uReturn>0) + { + VARIANT vtProp; + pclsObj->Get(L"Name", 0, &vtProp, 0, 0); + Win10|=wcsstr(vtProp.bstrVal,L"Windows 10")!=NULL; + VariantClear(&vtProp); + pclsObj->Release(); + } + + pSvc->Release(); + pLoc->Release(); + pEnumerator->Release(); + + return Win10; +} +#endif + + +// Replace it with actual check when available. +bool IsWindows11OrGreater() +{ + static bool IsSet=false,IsWin11=false; + if (!IsSet) + { + OSVERSIONINFO WinVer; + WinVer.dwOSVersionInfoSize=sizeof(WinVer); + GetVersionEx(&WinVer); + IsWin11=WinVer.dwMajorVersion>10 || + WinVer.dwMajorVersion==10 && WinVer.dwBuildNumber >= 22000/* && !WMI_IsWindows10()*/; + IsSet=true; + } + return IsWin11; +} #endif diff --git a/unrar/isnt.hpp b/unrar/isnt.hpp index 85790da4..fed0b517 100644 --- a/unrar/isnt.hpp +++ b/unrar/isnt.hpp @@ -10,4 +10,7 @@ enum WINNT_VERSION { DWORD WinNT(); +// Replace it with actual check when available. +bool IsWindows11OrGreater(); + #endif diff --git a/unrar/largepage.cpp b/unrar/largepage.cpp new file mode 100644 index 00000000..7b574c5f --- /dev/null +++ b/unrar/largepage.cpp @@ -0,0 +1,201 @@ +#include "rar.hpp" + +/* +To enable, disable or check Large Memory pages manually: +- open "Local Security Policy" from "Start Menu"; +- open "Lock Pages in Memory" in "Local Policies\User Rights Assignment"; +- add or remove the user and sign out and sign in or restart Windows. +*/ + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(RARDLL) +#define ALLOW_LARGE_PAGES +#endif + +LargePageAlloc::LargePageAlloc() +{ + UseLargePages=false; +#ifdef ALLOW_LARGE_PAGES + PageSize=0; +#endif +} + + +void LargePageAlloc::AllowLargePages(bool Allow) +{ +#ifdef ALLOW_LARGE_PAGES + if (Allow && PageSize==0) + { + HMODULE hKernel=GetModuleHandle(L"kernel32.dll"); + if (hKernel!=nullptr) + { + typedef SIZE_T (*GETLARGEPAGEMINIMUM)(); + GETLARGEPAGEMINIMUM pGetLargePageMinimum=(GETLARGEPAGEMINIMUM)GetProcAddress(hKernel, "GetLargePageMinimum"); + if (pGetLargePageMinimum!=nullptr) + PageSize=pGetLargePageMinimum(); + } + if (PageSize==0 || !SetPrivilege(SE_LOCK_MEMORY_NAME)) + { + UseLargePages=false; + return; + } + } + + UseLargePages=Allow; +#endif +} + + +bool LargePageAlloc::IsPrivilegeAssigned() +{ +#ifdef ALLOW_LARGE_PAGES + return SetPrivilege(SE_LOCK_MEMORY_NAME); +#else + return true; +#endif +} + + +bool LargePageAlloc::AssignPrivilege() +{ +#ifdef ALLOW_LARGE_PAGES + HANDLE hToken = NULL; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return false; + + // Get the required buffer size. + DWORD BufSize=0; + GetTokenInformation(hToken, TokenUser, NULL, 0, &BufSize); + if (BufSize==0 || BufSize>1000000) // Sanity check for returned value. + { + CloseHandle(hToken); + return false; + } + + TOKEN_USER *TokenInfo = (TOKEN_USER*)malloc(BufSize); + + // Get the current user token information. + if (GetTokenInformation(hToken,TokenUser,TokenInfo,BufSize,&BufSize)==0) + { + CloseHandle(hToken); + return false; + } + + // Get SID string for the current user. + LPWSTR ApiSidStr; + ConvertSidToStringSid(TokenInfo->User.Sid, &ApiSidStr); + + // Convert SID to C++ string and release API based buffer. + std::wstring SidStr=ApiSidStr; + LocalFree(ApiSidStr); + CloseHandle(hToken); + + if (IsUserAdmin()) + AssignPrivilegeBySid(SidStr); + else + { + // Define here, so they survive until ShellExecuteEx call. + std::wstring ExeName=GetModuleFileStr(); + std::wstring Param=std::wstring(L"-") + LOCKMEM_SWITCH + SidStr; + + SHELLEXECUTEINFO shExecInfo{}; + shExecInfo.cbSize = sizeof(shExecInfo); + + shExecInfo.hwnd = NULL; // Specifying WinRAR main window here does not work well in command line mode. + shExecInfo.lpVerb = L"runas"; + shExecInfo.lpFile = ExeName.c_str(); + shExecInfo.lpParameters = Param.c_str(); + shExecInfo.nShow = SW_SHOWNORMAL; + BOOL Result=ShellExecuteEx(&shExecInfo); + } +#endif + + return true; +} + + +bool LargePageAlloc::AssignPrivilegeBySid(const std::wstring &Sid) +{ +#ifdef ALLOW_LARGE_PAGES + LSA_HANDLE PolicyHandle; + LSA_OBJECT_ATTRIBUTES ObjectAttributes{}; // Docs require to zero initalize it. + +#ifndef STATUS_SUCCESS // Can be defined through WIL package in WinRAR. + // We define STATUS_SUCCESS here instead of including ntstatus.h to avoid + // macro redefinition warnings. We tried UMDF_USING_NTSTATUS define + // and other workarounds, but it didn't help. + const uint STATUS_SUCCESS=0; +#endif + + if (LsaOpenPolicy(NULL,&ObjectAttributes,POLICY_CREATE_ACCOUNT| + POLICY_LOOKUP_NAMES,&PolicyHandle)!=STATUS_SUCCESS) + return false; + + PSID UserSid; + ConvertStringSidToSid(Sid.c_str(),&UserSid); + + LSA_UNICODE_STRING LsaString; + LsaString.Buffer=(PWSTR)SE_LOCK_MEMORY_NAME; + // It must be in bytes, so multiple it to sizeof(wchar_t). + LsaString.Length=(USHORT)wcslen(LsaString.Buffer)*sizeof(LsaString.Buffer[0]); + LsaString.MaximumLength=LsaString.Length; + + bool Success=LsaAddAccountRights(PolicyHandle,UserSid,&LsaString,1)==STATUS_SUCCESS; + + LocalFree(UserSid); + LsaClose(PolicyHandle); + + mprintf(St(MPrivilegeAssigned)); + if (Ask(St(MYesNo)) == 1) + Shutdown(POWERMODE_RESTART); + + return Success; +#else + return true; +#endif +} + + +bool LargePageAlloc::AssignConfirmation() +{ +#ifdef ALLOW_LARGE_PAGES + mprintf(St(MLockInMemoryNeeded)); + return Ask(St(MYesNo)) == 1; +#else + return false; +#endif +} + + +void* LargePageAlloc::new_large(size_t Size) +{ + void *Allocated=nullptr; + +#ifdef ALLOW_LARGE_PAGES + if (UseLargePages && Size>=PageSize) + { + // VirtualAlloc fails if allocation size isn't multiple of page size. + SIZE_T AllocSize=Size%PageSize==0 ? Size:(Size/PageSize+1)*PageSize; + Allocated=VirtualAlloc(nullptr,AllocSize,MEM_COMMIT|MEM_RESERVE|MEM_LARGE_PAGES,PAGE_READWRITE); + if (Allocated!=nullptr) + LargeAlloc.push_back(Allocated); + } +#endif + return Allocated; +} + + +bool LargePageAlloc::delete_large(void *Addr) +{ +#ifdef ALLOW_LARGE_PAGES + if (Addr!=nullptr) + for (size_t I=0;I LargeAlloc; + SIZE_T PageSize; +#endif + bool UseLargePages; + public: + LargePageAlloc(); + void AllowLargePages(bool Allow); + static bool IsPrivilegeAssigned(); + static bool AssignPrivilege(); + static bool AssignPrivilegeBySid(const std::wstring &Sid); + static bool AssignConfirmation(); + + static bool ProcessSwitch(CommandData *Cmd,const wchar *Switch) + { + if (Switch[0]==LOCKMEM_SWITCH[0]) + { + size_t Length=wcslen(LOCKMEM_SWITCH); + if (wcsncmp(Switch,LOCKMEM_SWITCH,Length)==0) + { + LargePageAlloc::AssignPrivilegeBySid(Switch+Length); + return true; + } + } + return false; + } + + template T* new_l(size_t Size,bool Clear=false) + { + T *Allocated=(T*)new_large(Size*sizeof(T)); + if (Allocated==nullptr) + Allocated=Clear ? new T[Size]{} : new T[Size]; + return Allocated; + } + + template void delete_l(T *Addr) + { + if (!delete_large(Addr)) + delete[] Addr; + } +}; + + +#endif diff --git a/unrar/list.cpp b/unrar/list.cpp index 476fd3c6..a9af7777 100644 --- a/unrar/list.cpp +++ b/unrar/list.cpp @@ -1,7 +1,6 @@ #include "rar.hpp" -static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare); -static void ListSymLink(Archive &Arc); +static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare,bool DisableNames); static void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize); static void ListOldSubHeader(Archive &Arc); static void ListNewSubHeader(CommandData *Cmd,Archive &Arc); @@ -15,16 +14,13 @@ void ListArchive(CommandData *Cmd) bool Bare=(Cmd->Command[1]=='B'); bool Verbose=(Cmd->Command[0]=='V'); - wchar ArcName[NM]; - while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + std::wstring ArcName; + while (Cmd->GetArcName(ArcName)) { if (Cmd->ManualPassword) Cmd->Password.Clean(); // Clean user entered password before processing next archive. Archive Arc(Cmd); -#ifdef _WIN_ALL - Arc.RemoveSequentialFlag(); -#endif if (!Arc.WOpen(ArcName)) continue; bool FileMatched=true; @@ -38,32 +34,41 @@ void ListArchive(CommandData *Cmd) if (!Bare) { Arc.ViewComment(); - mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName); + mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName.c_str()); + mprintf(L"\n%s: ",St(MListDetails)); - uint SetCount=0; - const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 4":L"RAR 5"); - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", Fmt); + const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 1.5":L"RAR 5"); + mprintf(L"%s", Fmt); if (Arc.Solid) - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSolid)); + mprintf(L", %s", St(MListSolid)); if (Arc.SFXSize>0) - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSFX)); + mprintf(L", %s", St(MListSFX)); if (Arc.Volume) if (Arc.Format==RARFMT50) { // RAR 5.0 archives store the volume number in main header, // so it is already available now. - if (SetCount++ > 0) - mprintf(L", "); + mprintf(L", "); mprintf(St(MVolumeNumber),Arc.VolNumber+1); } else - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListVolume)); + mprintf(L", %s", St(MListVolume)); if (Arc.Protected) - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListRR)); + mprintf(L", %s", St(MListRR)); if (Arc.Locked) - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListLock)); + mprintf(L", %s", St(MListLock)); if (Arc.Encrypted) - mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListEncHead)); + mprintf(L", %s", St(MListEncHead)); + + if (!Arc.MainHead.OrigName.empty()) + mprintf(L"\n%s: %s",St(MOrigName),Arc.MainHead.OrigName.c_str()); + if (Arc.MainHead.OrigTime.IsSet()) + { + wchar DateStr[50]; + Arc.MainHead.OrigTime.GetText(DateStr,ASIZE(DateStr),Technical); + mprintf(L"\n%s: %s",St(MOriginalTime),DateStr); + } + mprintf(L"\n"); } @@ -92,10 +97,10 @@ void ListArchive(CommandData *Cmd) switch(HeaderType) { case HEAD_FILE: - FileMatched=Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL,0)!=0; + FileMatched=Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0; if (FileMatched) { - ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare); + ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare,Cmd->DisableNames); if (!Arc.FileHead.SplitBefore) { TotalUnpSize+=Arc.FileHead.UnpSize; @@ -105,10 +110,21 @@ void ListArchive(CommandData *Cmd) } break; case HEAD_SERVICE: + // For service blocks dependent on previous block, such as ACL + // or NTFS stream, we use "file matched" flag of host file. + // Independent blocks like RR are matched separately, + // so we can list them by their name. Also we match even + // dependent blocks separately if "vta -idn" are set. User may + // want to see service blocks only in this case. + if (!Arc.SubHead.SubBlock || Cmd->DisableNames) + FileMatched=Cmd->IsProcessFile(Arc.SubHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0; if (FileMatched && !Bare) { + // Here we set DisableNames parameter to true regardless of + // Cmd->DisableNames. If "vta -idn" are set together, user + // wants to see service blocks like RR only. if (Technical && ShowService) - ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false); + ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false,false); } break; } @@ -125,15 +141,15 @@ void ListArchive(CommandData *Cmd) if (Verbose) { - mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----"); - mprintf(L"\n%21ls %9ls %3d%% %-27ls %u",UnpSizeText, + mprintf(L"\n----------- ---------- ---------- ----- ---------- ----- -------- ----"); + mprintf(L"\n%22ls %10ls %3d%% %-27ls %u",UnpSizeText, PackSizeText,ToPercentUnlim(TotalPackSize,TotalUnpSize), VolNumText,FileCount); } else { - mprintf(L"\n----------- --------- ---------- ----- ----"); - mprintf(L"\n%21ls %-16ls %u",UnpSizeText,VolNumText,FileCount); + mprintf(L"\n----------- ---------- ---------- ----- ----"); + mprintf(L"\n%22ls %-16ls %u",UnpSizeText,VolNumText,FileCount); } SumFileCount+=FileCount; @@ -147,7 +163,7 @@ void ListArchive(CommandData *Cmd) ArcCount++; #ifndef NOVOLUME - if (Cmd->VolSize!=0 && (Arc.FileHead.SplitAfter || + if (Cmd->VolSize==VOLSIZE_AUTO && (Arc.FileHead.SplitAfter || Arc.GetHeaderType()==HEAD_ENDARC && Arc.EndArcHead.NextVolume) && MergeArchive(Arc,NULL,false,Cmd->Command[0])) Arc.Seek(0,SEEK_SET); @@ -158,7 +174,7 @@ void ListArchive(CommandData *Cmd) else { if (Cmd->ArcNames.ItemsCount()<2 && !Bare) - mprintf(St(MNotRAR),Arc.FileName); + mprintf(St(MNotRAR),Arc.FileName.c_str()); break; } } @@ -188,31 +204,37 @@ enum LISTCOL_TYPE { }; -void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare) +void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare,bool DisableNames) { - wchar *Name=hd.FileName; - RARFORMAT Format=Arc.Format; - - if (Bare) - { - mprintf(L"%s\n",Name); - return; - } - - if (!TitleShown && !Technical) + if (!TitleShown && !Technical && !Bare) { if (Verbose) { mprintf(L"\n%ls",St(MListTitleV)); - mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----"); + if (!DisableNames) + mprintf(L"\n----------- ---------- ---------- ----- ---------- ----- -------- ----"); } else { mprintf(L"\n%ls",St(MListTitleL)); - mprintf(L"\n----------- --------- ---------- ----- ----"); + if (!DisableNames) + mprintf(L"\n----------- ---------- ---------- ----- ----"); } + // Must be set even in DisableNames mode to suppress "0 files" output + // unless no files are matched. TitleShown=true; } + if (DisableNames) + return; + + const wchar *Name=hd.FileName.c_str(); + RARFORMAT Format=Arc.Format; + + if (Bare) + { + mprintf(L"%s\n",Name); + return; + } wchar UnpSizeText[30],PackSizeText[30]; if (hd.UnpSize==INT64NDF) @@ -238,7 +260,7 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo if (hd.SplitAfter) wcsncpyz(RatioStr,L"-->",ASIZE(RatioStr)); else - swprintf(RatioStr,ASIZE(RatioStr),L"%d%%",ToPercentUnlim(hd.PackSize,hd.UnpSize)); + swprintf(RatioStr,ASIZE(RatioStr),L"%u%%",ToPercentUnlim(hd.PackSize,hd.UnpSize)); wchar DateStr[50]; hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical); @@ -252,9 +274,8 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) { mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream)); - wchar StreamName[NM]; - GetStreamNameNTFS(Arc,StreamName,ASIZE(StreamName)); - mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName); + std::wstring StreamName=GetStreamNameNTFS(Arc); + mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName.c_str()); } else { @@ -278,43 +299,57 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo if (hd.RedirType!=FSREDIR_NONE) if (Format==RARFMT15) { - char LinkTargetA[NM]; + std::string LinkTargetA; if (Arc.FileHead.Encrypted) { // Link data are encrypted. We would need to ask for password // and initialize decryption routine to display the link target. - strncpyz(LinkTargetA,"*<-?->",ASIZE(LinkTargetA)); + LinkTargetA="*<-?->"; } else { - int DataSize=(int)Min(hd.PackSize,ASIZE(LinkTargetA)-1); - Arc.Read(LinkTargetA,DataSize); - LinkTargetA[DataSize > 0 ? DataSize : 0] = 0; + size_t DataSize=(size_t)Min(hd.PackSize,MAXPATHSIZE); + std::vector Buf(DataSize+1); + Arc.Read(Buf.data(),DataSize); + Buf[DataSize] = 0; + LinkTargetA=Buf.data(); } - wchar LinkTarget[NM]; - CharToWide(LinkTargetA,LinkTarget,ASIZE(LinkTarget)); - mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget); + std::wstring LinkTarget; + CharToWide(LinkTargetA,LinkTarget); + mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget.c_str()); } else - mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName); + mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName.c_str()); } if (!hd.Dir) { mprintf(L"\n%12ls: %ls",St(MListSize),UnpSizeText); mprintf(L"\n%12ls: %ls",St(MListPacked),PackSizeText); mprintf(L"\n%12ls: %ls",St(MListRatio),RatioStr); + + if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_RR)) + { + // Display the original -rrN percent if available. + int RecoveryPercent=Arc.GetRecoveryPercent(); + if (RecoveryPercent>0) // It can be -1 if failed to detect. + mprintf(L"\n%12ls: %u%%",L"RR%", RecoveryPercent); + } } + bool WinTitles=false; +#ifdef _WIN_ALL + WinTitles=true; +#endif if (hd.mtime.IsSet()) - mprintf(L"\n%12ls: %ls",St(MListMtime),DateStr); + mprintf(L"\n%12ls: %ls",St(WinTitles ? MListModified:MListMtime),DateStr); if (hd.ctime.IsSet()) { hd.ctime.GetText(DateStr,ASIZE(DateStr),true); - mprintf(L"\n%12ls: %ls",St(MListCtime),DateStr); + mprintf(L"\n%12ls: %ls",St(WinTitles ? MListCreated:MListCtime),DateStr); } if (hd.atime.IsSet()) { hd.atime.GetText(DateStr,ASIZE(DateStr),true); - mprintf(L"\n%12ls: %ls",St(MListAtime),DateStr); + mprintf(L"\n%12ls: %ls",St(WinTitles ? MListAccessed:MListAtime),DateStr); } mprintf(L"\n%12ls: %ls",St(MListAttr),AttrStr); if (hd.FileHash.Type==HASH_CRC32) @@ -323,11 +358,11 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo hd.FileHash.CRC32); if (hd.FileHash.Type==HASH_BLAKE2) { - wchar BlakeStr[BLAKE2_DIGEST_SIZE*2+1]; - BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,NULL,BlakeStr,ASIZE(BlakeStr)); + std::wstring BlakeStr; + BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,BlakeStr); mprintf(L"\n%12ls: %ls", hd.UseHashKey ? L"BLAKE2 MAC":hd.SplitAfter ? L"Pack-BLAKE2":L"BLAKE2", - BlakeStr); + BlakeStr.c_str()); } const wchar *HostOS=L""; @@ -344,11 +379,22 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo if (*HostOS!=0) mprintf(L"\n%12ls: %ls",St(MListHostOS),HostOS); - mprintf(L"\n%12ls: RAR %ls(v%d) -m%d -md=%d%s",St(MListCompInfo), + std::wstring WinSize; + if (!hd.Dir) + if (hd.WinSize%1073741824==0) + WinSize=L" -md=" + std::to_wstring(hd.WinSize/1073741824) + L"g"; + else + if (hd.WinSize%1048576==0) + WinSize=L" -md=" + std::to_wstring(hd.WinSize/1048576) + L"m"; + else + if (hd.WinSize>=1024) + WinSize=L" -md=" + std::to_wstring(hd.WinSize/1024) + L"k"; + else + WinSize=L" -md=?"; + + mprintf(L"\n%12ls: RAR %ls(v%d) -m%d%s",St(MListCompInfo), Format==RARFMT15 ? L"1.5":L"5.0", - hd.UnpVer==VER_UNKNOWN ? 0 : hd.UnpVer,hd.Method, - hd.WinSize>=0x100000 ? hd.WinSize/0x100000:hd.WinSize/0x400, - hd.WinSize>=0x100000 ? L"M":L"K"); + hd.UnpVer==VER_UNKNOWN ? 0 : hd.UnpVer,hd.Method,WinSize.c_str()); if (hd.Solid || hd.Encrypted) { @@ -361,7 +407,7 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo if (hd.Version) { - uint Version=ParseVersionFileName(Name,false); + uint Version=ParseVersionFileName(hd.FileName,false); if (Version!=0) mprintf(L"\n%12ls: %u",St(MListFileVer),Version); } @@ -370,25 +416,27 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo { mprintf(L"\n%12ls: ",L"Unix owner"); if (*hd.UnixOwnerName!=0) - mprintf(L"%ls:",GetWide(hd.UnixOwnerName)); + mprintf(L"%ls",GetWide(hd.UnixOwnerName).c_str()); + else + if (hd.UnixOwnerNumeric) + mprintf(L"#%d",hd.UnixOwnerID); + mprintf(L":"); if (*hd.UnixGroupName!=0) - mprintf(L"%ls",GetWide(hd.UnixGroupName)); - if ((*hd.UnixOwnerName!=0 || *hd.UnixGroupName!=0) && (hd.UnixOwnerNumeric || hd.UnixGroupNumeric)) - mprintf(L" "); - if (hd.UnixOwnerNumeric) - mprintf(L"#%d:",hd.UnixOwnerID); - if (hd.UnixGroupNumeric) - mprintf(L"#%d:",hd.UnixGroupID); + mprintf(L"%ls",GetWide(hd.UnixGroupName).c_str()); + else + if (hd.UnixGroupNumeric) + mprintf(L"#%d",hd.UnixGroupID); } mprintf(L"\n"); return; } - mprintf(L"\n%c%10ls %9ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText); + // 2025.12.01: Size field width incremented to properly align 1+ GB sizes. + mprintf(L"\n%c%10ls %10ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText); if (Verbose) - mprintf(L"%9ls %4ls ",PackSizeText,RatioStr); + mprintf(L"%10ls %4ls ",PackSizeText,RatioStr); mprintf(L" %ls ",DateStr); @@ -403,31 +451,11 @@ void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bo mprintf(L"%02x%02x..%02x ",S[0],S[1],S[31]); } else - mprintf(L"???????? "); + mprintf(hd.Dir ? L" ":L"???????? "); // Missing checksum is ok for folder, not for file. } mprintf(L"%ls",Name); } -/* -void ListSymLink(Archive &Arc) -{ - if (Arc.FileHead.HSType==HSYS_UNIX && (Arc.FileHead.FileAttr & 0xF000)==0xA000) - if (Arc.FileHead.Encrypted) - { - // Link data are encrypted. We would need to ask for password - // and initialize decryption routine to display the link target. - mprintf(L"\n%22ls %ls",L"-->",L"*<-?->"); - } - else - { - char FileName[NM]; - uint DataSize=(uint)Min(Arc.FileHead.PackSize,sizeof(FileName)-1); - Arc.Read(FileName,DataSize); - FileName[DataSize]=0; - mprintf(L"\n%22ls %ls",L"-->",GetWide(FileName)); - } -} -*/ void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize) { diff --git a/unrar/loclang.hpp b/unrar/loclang.hpp index 99108c93..06837f0b 100644 --- a/unrar/loclang.hpp +++ b/unrar/loclang.hpp @@ -5,7 +5,7 @@ #define MContinueQuit L"_Continue_Quit" #define MRetryAbort L"_Retry_Abort" #define MIgnoreAllRetryQuit L"_Ignore_iGnore all_Retry_Quit" -#define MCopyright L"\nRAR %s Copyright (c) 1993-%d Alexander Roshal %d %s %d" +#define MCopyright L"\nRAR %s Copyright (c) 1993-%d Alexander Roshal %d %.3s %d" #define MRegTo L"\nRegistered to %s\n" #define MShare L"\nTrial version Type 'rar -?' for help\n" #define MRegKeyWarning L"\nAvailable license key is valid only for %s\n" @@ -13,21 +13,29 @@ #define MBeta L"beta" #define Mx86 L"x86" #define Mx64 L"x64" -#define MMonthJan L"Jan" -#define MMonthFeb L"Feb" -#define MMonthMar L"Mar" -#define MMonthApr L"Apr" +#define MMonthJan L"January" +#define MMonthFeb L"February" +#define MMonthMar L"March" +#define MMonthApr L"April" #define MMonthMay L"May" -#define MMonthJun L"Jun" -#define MMonthJul L"Jul" -#define MMonthAug L"Aug" -#define MMonthSep L"Sep" -#define MMonthOct L"Oct" -#define MMonthNov L"Nov" -#define MMonthDec L"Dec" +#define MMonthJun L"June" +#define MMonthJul L"July" +#define MMonthAug L"August" +#define MMonthSep L"September" +#define MMonthOct L"October" +#define MMonthNov L"November" +#define MMonthDec L"December" +#define MWeekDayMon L"Monday" +#define MWeekDayTue L"Tuesday" +#define MWeekDayWed L"Wednesday" +#define MWeekDayThu L"Thursday" +#define MWeekDayFri L"Friday" +#define MWeekDaySat L"Saturday" +#define MWeekDaySun L"Sunday" #define MRARTitle1 L"\nUsage: rar - - " #define MUNRARTitle1 L"\nUsage: unrar - - " #define MRARTitle2 L"\n <@listfiles...> " +#define MFwrSlTitle2 L"\n <@listfiles...> " #define MCHelpCmd L"\n\n" #define MCHelpCmdA L"\n a Add files to archive" #define MCHelpCmdC L"\n c Add archive comment" @@ -58,6 +66,7 @@ #define MCHelpSwAD L"\n ad[1,2] Alternate destination path" #define MCHelpSwAG L"\n ag[format] Generate archive name using the current date" #define MCHelpSwAI L"\n ai Ignore file attributes" +#define MCHelpSwAM L"\n am[s,r] Archive name and time [save, restore]" #define MCHelpSwAO L"\n ao Add files with Archive attribute set" #define MCHelpSwAP L"\n ap Set path inside archive" #define MCHelpSwAS L"\n as Synchronize archive contents" @@ -72,11 +81,11 @@ #define MCHelpSwDW L"\n dw Wipe files after archiving" #define MCHelpSwEa L"\n e[+] Set file exclude and include attributes" #define MCHelpSwED L"\n ed Do not add empty directories" -#define MCHelpSwEN L"\n en Do not put 'end of archive' block" #define MCHelpSwEP L"\n ep Exclude paths from names" #define MCHelpSwEP1 L"\n ep1 Exclude base directory from names" #define MCHelpSwEP2 L"\n ep2 Expand paths to full" #define MCHelpSwEP3 L"\n ep3 Expand paths to full including the drive letter" +#define MCHelpSwEP4 L"\n ep4 Exclude the path prefix from names" #define MCHelpSwF L"\n f Freshen files" #define MCHelpSwHP L"\n hp[password] Encrypt both file data and headers" #define MCHelpSwHT L"\n ht[b|c] Select hash type [BLAKE2,CRC32] for file checksum" @@ -92,9 +101,10 @@ #define MCHelpSwKB L"\n kb Keep broken extracted files" #define MCHelpSwLog L"\n log[f][=name] Write names to log file" #define MCHelpSwMn L"\n m<0..5> Set compression level (0-store...3-default...5-maximal)" -#define MCHelpSwMA L"\n ma[4|5] Specify a version of archiving format" #define MCHelpSwMC L"\n mc Set advanced compression parameters" -#define MCHelpSwMD L"\n md[k,m,g] Dictionary size in KB, MB or GB" +#define MCHelpSwMD L"\n md[x][kmg] Dictionary size in KB, MB or GB" +#define MCHelpSwME L"\n me[par] Set encryption parameters" +#define MCHelpSwMLP L"\n mlp Use large memory pages" #define MCHelpSwMS L"\n ms[ext;ext] Specify file types to store" #define MCHelpSwMT L"\n mt Set the number of threads" #define MCHelpSwN L"\n n Additionally filter included files" @@ -104,13 +114,14 @@ #define MCHelpSwOC L"\n oc Set NTFS Compressed attribute" #define MCHelpSwOH L"\n oh Save hard links as the link instead of the file" #define MCHelpSwOI L"\n oi[0-4][:min] Save identical files as references" -#define MCHelpSwOL L"\n ol[a] Process symbolic links as the link [absolute paths]" +#define MCHelpSwOL L"\n ol[a,-] Process symbolic links as the link [absolute paths, skip]" +#define MCHelpSwOM L"\n om[-|1][=lst] Propagate Mark of the Web" #define MCHelpSwONI L"\n oni Allow potentially incompatible names" +#define MCHelpSwOP L"\n op Set the output path for extracted files" #define MCHelpSwOR L"\n or Rename files automatically" #define MCHelpSwOS L"\n os Save NTFS streams" #define MCHelpSwOW L"\n ow Save or restore file owner and group" #define MCHelpSwP L"\n p[password] Set password" -#define MCHelpSwPm L"\n p- Do not query password" #define MCHelpSwQO L"\n qo[-|+] Add quick open information [none|force]" #define MCHelpSwR L"\n r Recurse subdirectories" #define MCHelpSwRm L"\n r- Disable recursion" @@ -118,15 +129,14 @@ #define MCHelpSwRI L"\n ri

[:] Set priority (0-default,1-min..15-max) and sleep time in ms" #define MCHelpSwRR L"\n rr[N] Add data recovery record" #define MCHelpSwRV L"\n rv[N] Create recovery volumes" -#define MCHelpSwS L"\n s[,v[-],e] Create solid archive" -#define MCHelpSwSm L"\n s- Disable solid archiving" +#define MCHelpSwS L"\n s[=] Create solid archive" #define MCHelpSwSC L"\n sc[obj] Specify the character set" #define MCHelpSwSFX L"\n sfx[name] Create SFX archive" #define MCHelpSwSI L"\n si[name] Read data from standard input (stdin)" -#define MCHelpSwSL L"\n sl Process files with size less than specified" -#define MCHelpSwSM L"\n sm Process files with size more than specified" +#define MCHelpSwSL L"\n sl[u] Process files with size less than specified" +#define MCHelpSwSM L"\n sm[u] Process files with size more than specified" #define MCHelpSwT L"\n t Test files after archiving" -#define MCHelpSwTK L"\n tk Keep original archive time" +#define MCHelpSwTK L"\n tk[] Keep the original or set the specified archive time" #define MCHelpSwTL L"\n tl Set archive time to latest file" #define MCHelpSwTN L"\n tn[mcao] Process files newer than time" #define MCHelpSwTO L"\n to[mcao] Process files older than time" @@ -136,10 +146,9 @@ #define MCHelpSwU L"\n u Update files" #define MCHelpSwV L"\n v Create volumes with size autodetection or list all volumes" #define MCHelpSwVUnr L"\n v List all volumes" -#define MCHelpSwVn L"\n v[k,b] Create volumes with size=*1000 [*1024, *1]" +#define MCHelpSwVn L"\n v[u] Create volumes with size in [bBkKmMgGtT] units" #define MCHelpSwVD L"\n vd Erase disk contents before creating volume" #define MCHelpSwVER L"\n ver[n] File version control" -#define MCHelpSwVN L"\n vn Use the old style volume naming scheme" #define MCHelpSwVP L"\n vp Pause before each volume" #define MCHelpSwW L"\n w Assign work directory" #define MCHelpSwX L"\n x Exclude specified file" @@ -162,7 +171,7 @@ #define MErrRename L"\nCannot rename %s to %s" #define MAbsNextVol L"\nCannot find volume %s" #define MBreak L"\nUser break\n" -#define MAskCreatVol L"\nCreate next volume ?" +#define MAskCreatVol L"\nCreate next volume?" #define MAskNextDisk L"\nDisk full. Insert next" #define MCreatVol L"\n\nCreating %sarchive %s\n" #define MAskNextVol L"\nInsert disk with %s" @@ -207,9 +216,9 @@ #define MAddAnalyze L"\nAnalyzing archived files: " #define MRepacking L"\nRepacking archived files: " #define MCRCFailed L"\n%-20s - checksum error" -#define MExtrTest L"\n\nTesting archive %s\n" -#define MExtracting L"\n\nExtracting from %s\n" -#define MUseCurPsw L"\n%s - use current password ?" +#define MExtrTest L"\nTesting archive %s\n" +#define MExtracting L"\nExtracting from %s\n" +#define MUseCurPsw L"\n%s - use current password?" #define MCreatDir L"\nCreating %-56s" #define MExtrSkipFile L"\nSkipping %-56s" #define MExtrTestFile L"\nTesting %-56s" @@ -222,18 +231,17 @@ #define MExtrAllOk L"\nAll OK" #define MExtrTotalErr L"\nTotal errors: %ld" #define MAskReplace L"\n\nWould you like to replace the existing file %s\n%6s bytes, modified on %s\nwith a new one\n%6s bytes, modified on %s\n" -#define MAskOverwrite L"\nOverwrite %s ?" +#define MAskOverwrite L"\nOverwrite %s?" #define MAskNewName L"\nEnter new name: " #define MHeaderBroken L"\nCorrupt header is found" #define MMainHeaderBroken L"\nMain archive header is corrupt" #define MLogFileHead L"\n%s - the file header is corrupt" #define MLogProtectHead L"The data recovery header is corrupt" +#define MArcComment L"\nArchive comment" #define MReadStdinCmt L"\nReading comment from stdin\n" #define MReadCommFrom L"\nReading comment from %s" -#define MDelComment L"\nDeleting comment from %s" -#define MAddComment L"\nAdding comment to %s" -#define MFCommAdd L"\nAdding file comments" -#define MAskFComm L"\n\nReading comment for %s : %s from stdin\n" +#define MDelComment L"\nDeleting a comment from %s" +#define MAddComment L"\nAdding a comment to %s" #define MLogCommBrk L"\nThe archive comment is corrupt" #define MCommAskCont L"\nPress 'Enter' to continue or 'Q' to quit:" #define MWriteCommTo L"\nWrite comment to %s" @@ -253,8 +261,8 @@ #define MListLock L"lock" #define MListEnc L"encrypted" #define MListEncHead L"encrypted headers" -#define MListTitleL L" Attributes Size Date Time Name" -#define MListTitleV L" Attributes Size Packed Ratio Date Time Checksum Name" +#define MListTitleL L" Attributes Size Date Time Name" +#define MListTitleV L" Attributes Size Packed Ratio Date Time Checksum Name" #define MListName L"Name" #define MListType L"Type" #define MListFile L"File" @@ -272,17 +280,18 @@ #define MListMtime L"mtime" #define MListCtime L"ctime" #define MListAtime L"atime" +#define MListModified L"Modified" +#define MListCreated L"Created" +#define MListAccessed L"Accessed" #define MListAttr L"Attributes" #define MListFlags L"Flags" #define MListCompInfo L"Compression" #define MListHostOS L"Host OS" #define MListFileVer L"File version" #define MListService L"Service" -#define MListUOHead L"\n Unix Owner/Group data: %-14s %-14s" #define MListNTACLHead L"\n NTFS security data" #define MListStrmHead L"\n NTFS stream: %s" #define MListUnkHead L"\n Unknown subheader type: 0x%04x" -#define MFileComment L"\nComment: " #define MYes L"Yes" #define MNo L"No" #define MListNoFiles L" 0 files\n" @@ -290,10 +299,10 @@ #define MRprBuild L"\nBuilding %s" #define MRprOldFormat L"\nCannot repair archive with old format" #define MRprFind L"\nFound %s" -#define MRprAskIsSol L"\nThe archive header is corrupt. Mark archive as solid ?" +#define MRprAskIsSol L"\nThe archive header is corrupt. Mark archive as solid?" #define MRprNoFiles L"\nNo files found" #define MLogUnexpEOF L"\nUnexpected end of archive" -#define MRepAskReconst L"\nReconstruct archive structure ?" +#define MRepAskReconst L"\nReconstruct archive structure?" #define MRRSearch L"\nSearching for recovery record" #define MAnalyzeFileData L"\nAnalyzing file data" #define MRecRNotFound L"\nData recovery record not found" @@ -301,7 +310,7 @@ #define MRecSecDamage L"\nSector %ld (offsets %lX...%lX) damaged" #define MRecCorrected L" - data recovered" #define MRecFailed L" - cannot recover data" -#define MAddRecRec L"\nAdding data recovery record" +#define MAddRecRec L"\nAdding the data recovery record" #define MEraseForVolume L"\n\nErasing contents of drive %c:\n" #define MGetOwnersError L"\nWARNING: Cannot get %s owner and group\n" #define MErrGetOwnerID L"\nWARNING: Cannot get owner %s ID\n" @@ -310,7 +319,7 @@ #define MSetOwnersError L"\nWARNING: Cannot set %s owner and group\n" #define MErrLnkRead L"\nWARNING: Cannot read symbolic link %s" #define MSymLinkExists L"\nWARNING: Symbolic link %s already exists" -#define MAskRetryCreate L"\nCannot create %s. Retry ?" +#define MAskRetryCreate L"\nCannot create %s. Retry?" #define MDataBadCRC L"\n%-20s : packed data checksum error in volume %s" #define MFileRO L"\n%s is read-only" #define MACLGetError L"\nWARNING: Cannot get %s security data\n" @@ -320,14 +329,14 @@ #define MStreamBroken L"\nERROR: %s stream data are corrupt\n" #define MStreamUnknown L"\nWARNING: Unknown format of %s stream data\n" #define MInvalidName L"\nERROR: Invalid file name %s" -#define MProcessArc L"\n\nProcessing archive %s" -#define MCorrectingName L"\nWARNING: Attempting to correct the invalid file name" +#define MProcessArc L"\nProcessing archive %s" +#define MCorrectingName L"\nWARNING: Attempting to correct the invalid file or directory name" #define MUnpCannotMerge L"\nWARNING: You need to start extraction from a previous volume to unpack %s" #define MUnknownOption L"\nERROR: Unknown option: %s" +#define MSwSyntaxError L"\nERROR: '-' is expected in the beginning of: %s" #define MSubHeadCorrupt L"\nERROR: Corrupt data header found, ignored" #define MSubHeadUnknown L"\nWARNING: Unknown data header format, ignored" #define MSubHeadDataCRC L"\nERROR: Corrupt %s data block" -#define MSubHeadType L"\nData header type: %s" #define MScanError L"\nCannot read contents of %s" #define MNotVolume L"\n%s is not volume" #define MRecVolDiffSets L"\nERROR: %s and %s belong to different sets" @@ -380,8 +389,28 @@ #define MNeedAdmin L"\nYou may need to run RAR as administrator" #define MDictOutMem L"\nNot enough memory for %d MB compression dictionary, changed to %d MB." #define MUseSmalllerDict L"\nPlease use a smaller compression dictionary." -#define MOpenErrAtime L"\nYou may need to remove -tsp switch to open this file." +#define MExtrDictOutMem L"\nNot enough memory to unpack the archive with %u GB compression dictionary." +#define MSuggest64bit L"\n64-bit RAR version is necessary." +#define MOpenErrAtime L"\nYou may need to remove -tsp switch or run RAR as administrator to open this file." #define MErrReadInfo L"\nChoose 'Ignore' to continue with the already read file part only, 'Ignore all' to do it for all read errors, 'Retry' to repeat read and 'Quit' to abort." #define MErrReadTrunc L"\n%s is archived incompletely because of read error.\n" #define MErrReadCount L"\n%u files are archived incompletely because of read errors." #define MDirNameExists L"\nDirectory with such name already exists" +#define MStdinNoInput L"\nKeyboard input is not allowed when reading data from stdin" +#define MTruncPsw L"\nPassword exceeds the maximum allowed length of %u characters and will be truncated." +#define MAdjustValue L"\nAdjusting %s value to %s." +#define MOpFailed L"\nOperation failed" +#define MSkipEncArc L"\nSkipping the encrypted archive %s" +#define MOrigName L"Original name" +#define MOriginalTime L"Original time" +#define MFileRenamed L"\n%s is renamed to %s" +#define MDictNotAllowed L"\n%u GB dictionary exceeds %u GB limit and needs more than %u GB memory to unpack." +#define MDictExtrAnyway L"\nUse -md%ug or -mdx%ug switches to extract anyway." +#define MDictComprLimit L"\n%u GB dictionary exceeds %u GB limit and not allowed when compressing data." +#define MNeedSFX64 L"\n64-bit self-extracting module is necessary for %u GB compression dictionary." +#define MSkipUnsafeLink L"\nSkipping the potentially unsafe %s -> %s link. For archives from a trustworthy source use -ola to extract it anyway." +#define MTruncService L"\nTruncated at the service block: %s" +#define MHeaderQO L"quick open information" +#define MHeaderRR L"recovery record" +#define MLockInMemoryNeeded L"-mlp switch requires ""Lock pages in memory"" privilege. Do you wish to assign it to the current user account?" +#define MPrivilegeAssigned L"User privilege has been successfully assigned and will be activated after Windows restart. Restart now?" diff --git a/unrar/log.cpp b/unrar/log.cpp index 8bbe8ee0..57f7648e 100644 --- a/unrar/log.cpp +++ b/unrar/log.cpp @@ -1,13 +1,14 @@ #include "rar.hpp" -static wchar LogName[NM]; -static RAR_CHARSET LogCharset=RCH_DEFAULT; -void InitLogOptions(const wchar *LogFileName,RAR_CHARSET CSet) +void InitLogOptions(const std::wstring &LogFileName,RAR_CHARSET CSet) +{ +} + + +void CloseLogOptions() { - wcsncpyz(LogName,LogFileName,ASIZE(LogName)); - LogCharset=CSet; } @@ -19,17 +20,15 @@ void Log(const wchar *ArcName,const wchar *fmt,...) uiAlarm(UIALARM_ERROR); - // This buffer is for format string only, not for entire output, - // so it can be short enough. - wchar fmtw[1024]; - PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw)); - - safebuf wchar Msg[2*NM+1024]; va_list arglist; va_start(arglist,fmt); - vswprintf(Msg,ASIZE(Msg),fmtw,arglist); + + std::wstring s=vwstrprintf(fmt,arglist); + + ReplaceEsc(s); + va_end(arglist); - eprintf(L"%ls",Msg); + eprintf(L"%ls",s.c_str()); ErrHandler.SetSystemErrorCode(Code); } #endif diff --git a/unrar/log.hpp b/unrar/log.hpp index 008ef11a..22eaa8e6 100644 --- a/unrar/log.hpp +++ b/unrar/log.hpp @@ -1,7 +1,8 @@ #ifndef _RAR_LOG_ #define _RAR_LOG_ -void InitLogOptions(const wchar *LogFileName,RAR_CHARSET CSet); +void InitLogOptions(const std::wstring &LogFileName,RAR_CHARSET CSet); +void CloseLogOptions(); #ifdef SILENT inline void Log(const wchar *ArcName,const wchar *fmt,...) {} diff --git a/unrar/makefile b/unrar/makefile index 214f87ef..f7fe61a3 100644 --- a/unrar/makefile +++ b/unrar/makefile @@ -2,8 +2,11 @@ # Makefile for UNIX - unrar # Linux using GCC +# 2024.08.19: -march=native isn't recognized on some platforms such as RISCV64. +# Thus we removed it. Clang ARM users can add -march=armv8-a+crypto to enable +# ARM NEON crypto. CXX=c++ -CXXFLAGS=-O2 -Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else +CXXFLAGS=-O2 -std=c++11 -Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else LIBFLAGS=-fPIC DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DRAR_SMP STRIP=strip @@ -11,107 +14,6 @@ AR=ar LDFLAGS=-pthread DESTDIR=/usr -# Linux using LCC -#CXX=lcc -#CXXFLAGS=-O2 -#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -#STRIP=strip -#AR=ar -#DESTDIR=/usr - -# CYGWIN using GCC -#CXX=c++ -#CXXFLAGS=-O2 -#LIBFLAGS= -#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DRAR_SMP -#STRIP=strip -#AR=ar -#LDFLAGS=-pthread -#DESTDIR=/usr - -# HP UX using aCC -#CXX=aCC -#CXXFLAGS=-AA +O2 +Onolimit -#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -#STRIP=strip -#AR=ar -#DESTDIR=/usr - -# IRIX using GCC -#CXX=g++ -#CXXFLAGS=-O2 -#DEFINES=-DBIG_ENDIAN -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_BSD_COMPAT -D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED=1 -#STRIP=strip -#AR=ar -#DESTDIR=/usr - -# IRIX using MIPSPro (experimental) -#CXX=CC -#CXXFLAGS=-O2 -mips3 -woff 1234,1156,3284 -LANG:std -#DEFINES=-DBIG_ENDIAN -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_BSD_COMPAT -Dint64=int64_t -#STRIP=strip -#AR=ar -#DESTDIR=/usr - -# AIX using xlC (IBM VisualAge C++ 5.0) -#CXX=xlC -#CXXFLAGS=-O -qinline -qro -qroconst -qmaxmem=16384 -qcpluscmt -#DEFINES=-D_LARGE_FILES -D_LARGE_FILE_API -#LIBS=-lbsd -#STRIP=strip -#AR=ar -#DESTDIR=/usr - -# Solaris using CC -#CXX=CC -#CXXFLAGS=-fast -erroff=wvarhidemem -#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -#STRIP=strip -#AR=ar -#DESTDIR=/usr - -# Solaris using GCC (optimized for UltraSPARC 1 CPU) -#CXX=g++ -#CXXFLAGS=-O3 -mcpu=v9 -mtune=ultrasparc -m32 -#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -#STRIP=/usr/ccs/bin/strip -#AR=/usr/ccs/bin/ar -#DESTDIR=/usr - -# Tru64 5.1B using GCC3 -#CXX=g++ -#CXXFLAGS=-O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_XOPEN_SOURCE=500 -#STRIP=strip -#AR=ar -#LDFLAGS=-rpath /usr/local/gcc/lib -#DESTDIR=/usr - -# Tru64 5.1B using DEC C++ -#CXX=cxx -#CXXFLAGS=-O4 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -Dint64=long -#STRIP=strip -#AR=ar -#LDFLAGS= -#DESTDIR=/usr - -# QNX 6.x using GCC -#CXX=g++ -#CXXFLAGS=-O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -fexceptions -#STRIP=strip -#AR=ar -#LDFLAGS=-fexceptions -#DESTDIR=/usr - -# Cross-compile -# Linux using arm-linux-g++ -#CXX=arm-linux-g++ -#CXXFLAGS=-O2 -#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -#STRIP=arm-linux-strip -#AR=arm-linux-ar -#LDFLAGS=-static -#DESTDIR=/usr - ########################## COMPILE=$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES) @@ -123,10 +25,10 @@ UNRAR_OBJ=filestr.o recvol.o rs.o scantree.o qopen.o LIB_OBJ=filestr.o scantree.o dll.o qopen.o OBJECTS=rar.o strlist.o strfn.o pathfn.o smallfn.o global.o file.o filefn.o filcreat.o \ - archive.o arcread.o unicode.o system.o isnt.o crypt.o crc.o rawread.o encname.o \ + archive.o arcread.o unicode.o system.o crypt.o crc.o rawread.o encname.o \ resource.o match.o timefn.o rdwrfn.o consio.o options.o errhnd.o rarvm.o secpassword.o \ rijndael.o getbits.o sha1.o sha256.o blake2s.o hash.o extinfo.o extract.o volume.o \ - list.o find.o unpack.o headers.o threadpool.o rs16.o cmddata.o ui.o + list.o find.o unpack.o headers.o threadpool.o rs16.o cmddata.o ui.o largepage.o .cpp.o: $(COMPILE) -D$(WHAT) -c $< @@ -142,20 +44,23 @@ clean: @rm -f $(OBJECTS) $(UNRAR_OBJ) $(LIB_OBJ) @rm -f unrar libunrar.* -unrar: clean $(OBJECTS) $(UNRAR_OBJ) +# We removed 'clean' from dependencies, because it prevented parallel +# 'make -Jn' builds. + +unrar: $(OBJECTS) $(UNRAR_OBJ) @rm -f unrar $(LINK) -o unrar $(LDFLAGS) $(OBJECTS) $(UNRAR_OBJ) $(LIBS) $(STRIP) unrar sfx: WHAT=SFX_MODULE -sfx: clean $(OBJECTS) +sfx: $(OBJECTS) @rm -f default.sfx $(LINK) -o default.sfx $(LDFLAGS) $(OBJECTS) $(STRIP) default.sfx lib: WHAT=RARDLL lib: CXXFLAGS+=$(LIBFLAGS) -lib: clean $(OBJECTS) $(LIB_OBJ) +lib: $(OBJECTS) $(LIB_OBJ) @rm -f libunrar.* $(LINK) -shared -o libunrar.so $(LDFLAGS) $(OBJECTS) $(LIB_OBJ) $(AR) rcs libunrar.a $(OBJECTS) $(LIB_OBJ) diff --git a/unrar/match.cpp b/unrar/match.cpp index ec88fa61..0a78d8dd 100644 --- a/unrar/match.cpp +++ b/unrar/match.cpp @@ -3,6 +3,7 @@ static bool match(const wchar *pattern,const wchar *string,bool ForceCase); static int mwcsicompc(const wchar *Str1,const wchar *Str2,bool ForceCase); static int mwcsnicompc(const wchar *Str1,const wchar *Str2,size_t N,bool ForceCase); +static bool IsWildcard(const wchar *Str,size_t CheckSize); inline uint touppercw(uint ch,bool ForceCase) { @@ -16,12 +17,15 @@ inline uint touppercw(uint ch,bool ForceCase) } -bool CmpName(const wchar *Wildcard,const wchar *Name,int CmpMode) +bool CmpName(const wchar *Wildcard,const wchar *Name,uint CmpMode) { bool ForceCase=(CmpMode&MATCH_FORCECASESENSITIVE)!=0; CmpMode&=MATCH_MODEMASK; + wchar *Name1=PointToName(Wildcard); + wchar *Name2=PointToName(Name); + if (CmpMode!=MATCH_NAMES) { size_t WildLength=wcslen(Wildcard); @@ -32,47 +36,48 @@ bool CmpName(const wchar *Wildcard,const wchar *Name,int CmpMode) // "path1" mask must match "path1\path2\filename.ext" and "path1" names. wchar NextCh=Name[WildLength]; if (NextCh==L'\\' || NextCh==L'/' || NextCh==0) - return(true); + return true; } // Nothing more to compare for MATCH_SUBPATHONLY. if (CmpMode==MATCH_SUBPATHONLY) - return(false); - - wchar Path1[NM],Path2[NM]; - GetFilePath(Wildcard,Path1,ASIZE(Path1)); - GetFilePath(Name,Path2,ASIZE(Path2)); - - if ((CmpMode==MATCH_EXACT || CmpMode==MATCH_EXACTPATH) && - mwcsicompc(Path1,Path2,ForceCase)!=0) - return(false); + return false; + + // 2023.08.29: We tried std::wstring Path1 and Path2 here, but performance + // impact for O(n^2) complexity loop in CmdExtract::AnalyzeArchive() + // was rather noticeable, 1.7s instead of 0.9s when extracting ~300 files + // with @listfile from archive with ~7000 files. + // This function can be invoked from other O(n^2) loops. So for now + // we prefer to avoid wstring and use pointers and path sizes here. + // Another option could be using std::wstring_view. + + size_t Path1Size=Name1-Wildcard; + size_t Path2Size=Name2-Name; + + if ((CmpMode==MATCH_EXACT || CmpMode==MATCH_EXACTPATH) && + (Path1Size!=Path2Size || + mwcsnicompc(Wildcard,Name,Path1Size,ForceCase)!=0)) + return false; if (CmpMode==MATCH_ALLWILD) return match(Wildcard,Name,ForceCase); if (CmpMode==MATCH_SUBPATH || CmpMode==MATCH_WILDSUBPATH) - if (IsWildcard(Path1)) - return(match(Wildcard,Name,ForceCase)); + if (IsWildcard(Wildcard,Path1Size)) + return match(Wildcard,Name,ForceCase); else if (CmpMode==MATCH_SUBPATH || IsWildcard(Wildcard)) { - if (*Path1 && mwcsnicompc(Path1,Path2,wcslen(Path1),ForceCase)!=0) - return(false); + if (Path1Size>0 && mwcsnicompc(Wildcard,Name,Path1Size,ForceCase)!=0) + return false; } else - if (mwcsicompc(Path1,Path2,ForceCase)!=0) - return(false); + if (Path1Size!=Path2Size || mwcsnicompc(Wildcard,Name,Path1Size,ForceCase)!=0) + return false; } - wchar *Name1=PointToName(Wildcard); - wchar *Name2=PointToName(Name); - - // Always return false for RAR temporary files to exclude them - // from archiving operations. -// if (mwcsnicompc(L"__rar_",Name2,6,false)==0) -// return(false); if (CmpMode==MATCH_EXACT) - return(mwcsicompc(Name1,Name2,ForceCase)==0); + return mwcsicompc(Name1,Name2,ForceCase)==0; - return(match(Name1,Name2,ForceCase)); + return match(Name1,Name2,ForceCase); } @@ -85,18 +90,18 @@ bool match(const wchar *pattern,const wchar *string,bool ForceCase) switch (patternc) { case 0: - return(stringc==0); + return stringc==0; case '?': if (stringc == 0) - return(false); + return false; break; case '*': if (*pattern==0) - return(true); + return true; if (*pattern=='.') { if (pattern[1]=='*' && pattern[2]==0) - return(true); + return true; const wchar *dot=wcschr(string,'.'); if (pattern[1]==0) return (dot==NULL || dot[1]==0); @@ -104,22 +109,22 @@ bool match(const wchar *pattern,const wchar *string,bool ForceCase) { string=dot; if (wcspbrk(pattern,L"*?")==NULL && wcschr(string+1,'.')==NULL) - return(mwcsicompc(pattern+1,string+1,ForceCase)==0); + return mwcsicompc(pattern+1,string+1,ForceCase)==0; } } while (*string) if (match(pattern,string++,ForceCase)) - return(true); - return(false); + return true; + return false; default: if (patternc != stringc) { // Allow "name." mask match "name" and "name.\" match "name\". if (patternc=='.' && (stringc==0 || stringc=='\\' || stringc=='.')) - return(match(pattern,string,ForceCase)); + return match(pattern,string,ForceCase); else - return(false); + return false; } break; } @@ -145,3 +150,18 @@ int mwcsnicompc(const wchar *Str1,const wchar *Str2,size_t N,bool ForceCase) return wcsnicomp(Str1,Str2,N); #endif } + + +bool IsWildcard(const wchar *Str,size_t CheckSize) +{ + size_t CheckPos=0; +#ifdef _WIN_ALL + // Not treat the special NTFS \\?\d: path prefix as a wildcard. + if (Str[0]=='\\' && Str[1]=='\\' && Str[2]=='?' && Str[3]=='\\') + CheckPos+=4; +#endif + for (size_t I=CheckPos;I= 9*sf)+(cf >= 12*sf)+(cf >= 15*sf); - pc->U.SummFreq += cf; + pc->U.SummFreq += (ushort)cf; } p=pc->U.Stats+ns1; p->Successor=Successor; p->Symbol = fs.Symbol; - p->Freq = cf; - pc->NumStats=++ns1; + p->Freq = (byte)cf; + pc->NumStats=(ushort)++ns1; } MaxContext=MinContext=fs.Successor; return; @@ -532,15 +532,17 @@ inline bool RARPPM_CONTEXT::decodeSymbol2(ModelPPM *Model) Model->Coder.SubRange.LowCount=HiCnt; Model->Coder.SubRange.HighCount=Model->Coder.SubRange.scale; i=NumStats-Model->NumMasked; - pps--; + + // 2022.12.02: we removed pps-- here and changed the code below to avoid + // "array subscript -1 is outside array bounds" warning in some compilers. do { - pps++; if (pps>=ps+ASIZE(ps)) // Extra safety check. return false; Model->CharMask[(*pps)->Symbol]=Model->EscCount; + pps++; } while ( --i ); - psee2c->Summ += Model->Coder.SubRange.scale; + psee2c->Summ += (ushort)Model->Coder.SubRange.scale; Model->NumMasked = NumStats; } return true; diff --git a/unrar/model.hpp b/unrar/model.hpp index 52abc89b..2c9fa404 100644 --- a/unrar/model.hpp +++ b/unrar/model.hpp @@ -25,7 +25,7 @@ struct RARPPM_SEE2_CONTEXT : RARPPM_DEF } uint getMean() { - uint RetVal=GET_SHORT16(Summ) >> Shift; + short RetVal=GET_SHORT16(Summ) >> Shift; Summ -= RetVal; return RetVal+(RetVal == 0); } diff --git a/unrar/motw.cpp b/unrar/motw.cpp new file mode 100644 index 00000000..51588d1a --- /dev/null +++ b/unrar/motw.cpp @@ -0,0 +1,134 @@ +#include "rar.hpp" + +/* Zone.Identifier stream can include the text like: + +[ZoneTransfer] +ZoneId=3 +HostUrl=https://site/path/file.ext +ReferrerUrl=d:\path\archive.ext + +Where ZoneId can be: + + 0 = My Computer + 1 = Local intranet + 2 = Trusted sites + 3 = Internet + 4 = Restricted sites +*/ + +MarkOfTheWeb::MarkOfTheWeb() +{ + ZoneIdValue=-1; // -1 indicates the missing MOTW. + AllFields=false; +} + + +void MarkOfTheWeb::Clear() +{ + ZoneIdValue=-1; +} + + +void MarkOfTheWeb::ReadZoneIdStream(const std::wstring &FileName,bool AllFields) +{ + MarkOfTheWeb::AllFields=AllFields; + ZoneIdValue=-1; + ZoneIdStream.clear(); + + std::wstring StreamName=FileName+MOTW_STREAM_NAME; + + File SrcFile; + if (SrcFile.Open(StreamName)) + { + ZoneIdStream.resize(MOTW_STREAM_MAX_SIZE); + int BufSize=SrcFile.Read(&ZoneIdStream[0],ZoneIdStream.size()); + ZoneIdStream.resize(BufSize<0 ? 0:BufSize); + + if (BufSize<=0) + return; + + ZoneIdValue=ParseZoneIdStream(ZoneIdStream); + } +} + + +// 'Stream' contains the raw "Zone.Identifier" NTFS stream data on input +// and either raw or cleaned stream data on output. +int MarkOfTheWeb::ParseZoneIdStream(std::string &Stream) +{ + if (!starts_with(Stream,"[ZoneTransfer]")) + return -1; // Not a valid Mark of the Web. Prefer the archive MOTW if any. + + std::string::size_type ZoneId=Stream.find("ZoneId=",0); + if (ZoneId==std::string::npos || !IsDigit(Stream[ZoneId+7])) + return -1; // Not a valid Mark of the Web. + int ZoneIdValue=atoi(&Stream[ZoneId+7]); + if (ZoneIdValue<0 || ZoneIdValue>4) + return -1; // Not a valid Mark of the Web. + + if (!AllFields) + Stream="[ZoneTransfer]\r\nZoneId=" + std::to_string(ZoneIdValue) + "\r\n"; + + return ZoneIdValue; +} + + +void MarkOfTheWeb::CreateZoneIdStream(const std::wstring &Name,StringList &MotwList) +{ + if (ZoneIdValue==-1) + return; + + size_t ExtPos=GetExtPos(Name); + const wchar *Ext=ExtPos==std::wstring::npos ? L"":&Name[ExtPos+1]; + + bool Matched=false; + const wchar *CurMask; + MotwList.Rewind(); + while ((CurMask=MotwList.GetString())!=nullptr) + { + // Perform the fast extension comparison for simple *.ext masks. + // Also we added the fast path to wcsicomp for English only strings. + // When extracting 100000 files with "Exe and office" masks set + // this loop spent 85ms with this optimization and wcsicomp optimized + // for English strings, 415ms with this optimization only, 475ms with + // wcsicomp optimized only and 795ms without both optimizations. + bool FastCmp=CurMask[0]=='*' && CurMask[1]=='.' && wcspbrk(CurMask+2,L"*?")==NULL; + if (FastCmp && wcsicomp(Ext,CurMask+2)==0 || !FastCmp && CmpName(CurMask,Name,MATCH_NAMES)) + { + Matched=true; + break; + } + } + + if (!Matched) + return; + + std::wstring StreamName=Name+MOTW_STREAM_NAME; + + File StreamFile; + if (StreamFile.Create(StreamName)) // Can fail on FAT. + { + // We got a report that write to stream failed on Synology 2411+ NAS drive. + // So we handle it silently instead of aborting. + StreamFile.SetExceptions(false); + if (StreamFile.Write(&ZoneIdStream[0],ZoneIdStream.size())) + StreamFile.Close(); + } +} + + +bool MarkOfTheWeb::IsNameConflicting(const std::wstring &StreamName) +{ + // We must use the case insensitive comparison for L":Zone.Identifier" + // to catch specially crafted archived streams like L":zone.identifier". + return wcsicomp(StreamName,MOTW_STREAM_NAME)==0 && ZoneIdValue!=-1; +} + + +// Return true and prepare the file stream to write if its ZoneId is stricter +// than archive ZoneId. If it is missing, less or equally strict, return false. +bool MarkOfTheWeb::IsFileStreamMoreSecure(std::string &FileStream) +{ + int StreamZone=ParseZoneIdStream(FileStream); + return StreamZone>ZoneIdValue; +} diff --git a/unrar/motw.hpp b/unrar/motw.hpp new file mode 100644 index 00000000..e24feeac --- /dev/null +++ b/unrar/motw.hpp @@ -0,0 +1,26 @@ +#ifndef _RAR_MOTW_ +#define _RAR_MOTW_ + +class MarkOfTheWeb +{ + private: + const size_t MOTW_STREAM_MAX_SIZE=1024; + const wchar* MOTW_STREAM_NAME=L":Zone.Identifier"; // Must start from ':'. + + int ParseZoneIdStream(std::string &Stream); + + std::string ZoneIdStream; // Store archive ":Zone.Identifier" NTFS stream data. + int ZoneIdValue; // -1 if missing. + bool AllFields; // Copy all MOTW fields or ZoneId only. + public: + MarkOfTheWeb(); + void Clear(); + void ReadZoneIdStream(const std::wstring &FileName,bool AllFields); + void CreateZoneIdStream(const std::wstring &Name,StringList &MotwList); + bool IsNameConflicting(const std::wstring &StreamName); + bool IsFileStreamMoreSecure(std::string &FileStream); +}; + +#endif + + diff --git a/unrar/options.cpp b/unrar/options.cpp index 40323be8..ab8cff5e 100644 --- a/unrar/options.cpp +++ b/unrar/options.cpp @@ -6,18 +6,11 @@ RAROptions::RAROptions() } -RAROptions::~RAROptions() -{ - // It is important for security reasons, so we do not have the unnecessary - // password data left in memory. - memset(this,0,sizeof(RAROptions)); -} - - void RAROptions::Init() { memset(this,0,sizeof(RAROptions)); WinSize=0x2000000; + WinSizeLimit=0x100000000; Overwrite=OVERWRITE_DEFAULT; Method=3; MsgStream=MSG_STDOUT; diff --git a/unrar/options.hpp b/unrar/options.hpp index 48e13d64..542c6350 100644 --- a/unrar/options.hpp +++ b/unrar/options.hpp @@ -1,7 +1,7 @@ #ifndef _RAR_OPTIONS_ #define _RAR_OPTIONS_ -#define DEFAULT_RECOVERY -3 +#define DEFAULT_RECOVERY 3 #define DEFAULT_RECVOLUMES -10 @@ -15,10 +15,18 @@ enum PATH_EXCL_MODE { EXCL_ABSPATH // -ep3 (the full path with the disk letter) }; -enum {SOLID_NONE=0,SOLID_NORMAL=1,SOLID_COUNT=2,SOLID_FILEEXT=4, - SOLID_VOLUME_DEPENDENT=8,SOLID_VOLUME_INDEPENDENT=16}; +enum { + SOLID_NONE=0, // Non-solid mode. + SOLID_NORMAL=1, // Standard solid mode. + SOLID_COUNT=2, // Reset the solid data after specified file count. + SOLID_FILEEXT=4, // Reset the solid data for every new file extension. + SOLID_VOLUME_DEPENDENT=8, // Preserve solid data in all volumes. + SOLID_VOLUME_INDEPENDENT=16, // Reset solid data in all volumes. + SOLID_RESET=32, // Reset solid data for newly added files. + SOLID_BLOCK_SIZE=64 // Reset solid data after the specified size. +}; -enum {ARCTIME_NONE=0,ARCTIME_KEEP,ARCTIME_LATEST}; +enum ARCTIME_MODE {ARCTIME_NONE=0,ARCTIME_KEEP,ARCTIME_LATEST,ARCTIME_SPECIFIED}; enum EXTTIME_MODE { EXTTIME_NONE=0,EXTTIME_1S,EXTTIME_MAX @@ -33,7 +41,7 @@ enum RECURSE_MODE RECURSE_NONE=0, // no recurse switches RECURSE_DISABLE, // switch -r- RECURSE_ALWAYS, // switch -r - RECURSE_WILDCARDS, // switch -r0 + RECURSE_WILDCARDS // switch -r0 }; enum OVERWRITE_MODE @@ -45,13 +53,25 @@ enum OVERWRITE_MODE OVERWRITE_FORCE_ASK }; +enum ARC_METADATA +{ + ARCMETA_NONE=0, + ARCMETA_SAVE, // -ams + ARCMETA_RESTORE // -amr +}; -enum QOPEN_MODE { QOPEN_NONE, QOPEN_AUTO, QOPEN_ALWAYS }; +enum QOPEN_MODE { QOPEN_NONE=0, QOPEN_AUTO, QOPEN_ALWAYS }; enum RAR_CHARSET { RCH_DEFAULT=0,RCH_ANSI,RCH_OEM,RCH_UNICODE,RCH_UTF8 }; #define MAX_FILTER_TYPES 16 -enum FilterState {FILTER_DEFAULT=0,FILTER_AUTO,FILTER_FORCE,FILTER_DISABLE}; + +enum FilterState { + FILTER_DEFAULT=0, // No -mc switch. + FILTER_AUTO, // -mc switch is present. + FILTER_FORCE, // -mc+ switch is present. + FILTER_DISABLE // -mc- switch is present. +}; enum SAVECOPY_MODE { @@ -84,13 +104,15 @@ struct FilterMode #define MAX_GENERATE_MASK 128 +// Here we store simple data types, which we can clear and move all together +// quickly. Rest of data types goes to CommandData. class RAROptions { public: RAROptions(); - ~RAROptions(); void Init(); + uint ExclFileAttr; uint InclFileAttr; @@ -100,43 +122,40 @@ class RAROptions bool InclDir; bool InclAttrSet; - size_t WinSize; - wchar TempPath[NM]; - wchar SFXModule[NM]; + uint64 WinSize; + uint64 WinSizeLimit; // Switch -mdx. #ifdef USE_QOPEN QOPEN_MODE QOpenMode; #endif bool ConfigDisabled; // Switch -cfg-. - wchar ExtrPath[NM]; - wchar CommentFile[NM]; RAR_CHARSET CommentCharset; RAR_CHARSET FilelistCharset; RAR_CHARSET ErrlogCharset; RAR_CHARSET RedirectCharset; - wchar ArcPath[NM]; - SecPassword Password; bool EncryptHeaders; + bool SkipEncrypted; bool ManualPassword; // Password entered manually during operation, might need to clean for next archive. - wchar LogName[NM]; MESSAGE_TYPE MsgStream; SOUND_NOTIFY_MODE Sound; OVERWRITE_MODE Overwrite; int Method; HASH_TYPE HashType; - int Recovery; + uint Recovery; int RecVolNumber; + ARC_METADATA ArcMetadata; bool DisablePercentage; bool DisableCopyright; bool DisableDone; bool DisableNames; bool PrintVersion; - int Solid; - int SolidCount; + uint Solid; + uint SolidCount; + uint64 SolidBlockSize; bool ClearArc; bool AddArcOnly; bool DisableComment; @@ -145,19 +164,26 @@ class RAROptions PATH_EXCL_MODE ExclPath; RECURSE_MODE Recurse; int64 VolSize; - Array NextVolSizes; uint CurVolNum; bool AllYes; bool VerboseOutput; // -iv, display verbose output, used only in "WinRAR t" now. bool DisableSortSolid; - int ArcTime; int ConvertNames; bool ProcessOwners; bool SaveSymLinks; bool SaveHardLinks; bool AbsoluteLinks; + bool SkipSymLinks; int Priority; int SleepTime; + + bool UseLargePages; + + // Quit after processing some system integration related switch, + // like enabling the large memory pages privilege. + // menu for non-admin user and quit. + bool SetupComplete; + bool KeepBroken; bool OpenShared; bool DeleteFiles; @@ -175,6 +201,9 @@ class RAROptions bool SyncFiles; bool ProcessEA; bool SaveStreams; +#ifdef PROPAGATE_MOTW + bool MotwAllFields; +#endif bool SetCompressedAttr; bool IgnoreGeneralAttr; RarTime FileMtimeBefore,FileCtimeBefore,FileAtimeBefore; @@ -187,7 +216,6 @@ class RAROptions bool Test; bool VolumePause; FilterMode FilterModes[MAX_FILTER_TYPES]; - wchar EmailTo[NM]; uint VersionControl; APPENDARCNAME_MODE AppendArcNameToPath; POWER_MODE Shutdown; @@ -195,14 +223,12 @@ class RAROptions EXTTIME_MODE xctime; EXTTIME_MODE xatime; bool PreserveAtime; - wchar CompressStdin[NM]; uint Threads; // We use it to init hash even if RAR_SMP is not defined. #ifdef RARDLL - wchar DllDestName[NM]; int DllOpMode; int DllError; LPARAM UserData; @@ -210,5 +236,6 @@ class RAROptions CHANGEVOLPROC ChangeVolProc; PROCESSDATAPROC ProcessDataProc; #endif + }; #endif diff --git a/unrar/os.hpp b/unrar/os.hpp index a4ef744a..a2c3faee 100644 --- a/unrar/os.hpp +++ b/unrar/os.hpp @@ -4,25 +4,24 @@ #define FALSE 0 #define TRUE 1 -#ifdef __EMX__ - #define INCL_BASE -#endif - #if defined(RARDLL) && !defined(SILENT) #define SILENT #endif #if defined(__cplusplus) #include +#include +#include +#include +#include // For automatic pointers. +#include #endif -#if defined(_WIN_ALL) || defined(_EMX) -#define LITTLE_ENDIAN -#define NM 2048 +#ifdef _WIN_ALL -#if defined(_WIN_ALL) && !defined(LEAN_RAR_INCLUDES) +#define LITTLE_ENDIAN // We got a report that just "#define STRICT" is incompatible with @@ -38,12 +37,11 @@ // re-definition warnings in third party projects. #ifndef UNICODE #define UNICODE +#define _UNICODE // Set _T() macro to convert from narrow to wide strings. #endif -#undef WINVER -#undef _WIN32_WINNT -#define WINVER 0x0501 -#define _WIN32_WINNT 0x0501 +#define WINVER _WIN32_WINNT_WINXP +#define _WIN32_WINNT _WIN32_WINNT_WINXP #if !defined(ZIPSFX) #define RAR_SMP @@ -57,35 +55,37 @@ #pragma comment(lib, "Shlwapi.lib") #include #pragma comment(lib, "PowrProf.lib") +#include +#pragma comment(lib, "Psapi.lib") // For GetProcessMemoryInfo(). #include #include #include #include #include #include +#include +#include -#endif // _WIN_ALL +// For WMI requests (C++ only). +#ifdef __cplusplus +#include +#include +#pragma comment(lib, "wbemuuid.lib") +#endif + #include #include #include +#include +#include -#if !defined(_EMX) && !defined(_MSC_VER) - #include -#endif -#ifdef _MSC_VER - #if _MSC_VER<1500 - #define for if (0) ; else for - #endif - #include - #include - +// Use SSE only for x86/x64, not ARM Windows. +#if defined(_M_IX86) || defined(_M_X64) #define USE_SSE #define SSE_ALIGNMENT 16 -#else - #include -#endif // _MSC_VER +#endif #undef _WSTDIO_DEFINED #include @@ -99,7 +99,6 @@ #include #include - #define SAVE_LINKS #define ENABLE_ACCESS @@ -109,7 +108,7 @@ #define SPATHDIVIDER L"\\" -#define CPATHDIVIDER '\\' +#define CPATHDIVIDER L'\\' #define MASKALL L"*" #define READBINARY "rb" @@ -119,25 +118,13 @@ #define WRITEBINARY "wb" #define APPENDTEXT "at" -#if defined(_WIN_ALL) - #ifdef _MSC_VER - #define _stdfunction __cdecl - #define _forceinline __forceinline - #else - #define _stdfunction _USERENTRY - #define _forceinline inline - #endif -#else - #define _stdfunction - #define _forceinline inline -#endif +#define _stdfunction __cdecl +#define _forceinline __forceinline -#endif // defined(_WIN_ALL) || defined(_EMX) +#endif // _WIN_ALL #ifdef _UNIX -#define NM 2048 - #include #include #include @@ -145,7 +132,7 @@ #if defined(__QNXNTO__) #include #endif -#if defined(RAR_SMP) && defined(__APPLE__) +#ifdef _APPLE #include #endif #ifndef SFX_MODULE @@ -158,6 +145,7 @@ #include #include #include +#include // Needed for ptrdiff_t in some UnRAR source builds. #include #include #include @@ -168,6 +156,28 @@ #include #include +#ifdef __GNUC__ + #if defined(__i386__) || defined(__x86_64__) + #include + + #define USE_SSE + #define SSE_ALIGNMENT 16 + #endif +#endif + +#if defined(__aarch64__) && (defined(__ARM_FEATURE_CRYPTO) || defined(__ARM_FEATURE_CRC32)) +#include +#ifndef _APPLE +#include +#include +#endif +#ifdef __ARM_FEATURE_CRYPTO +#define USE_NEON_AES +#endif +#ifdef __ARM_FEATURE_CRC32 +#define USE_NEON_CRC32 +#endif +#endif #ifdef S_IFLNK #define SAVE_LINKS @@ -185,7 +195,7 @@ #define SPATHDIVIDER L"/" -#define CPATHDIVIDER '/' +#define CPATHDIVIDER L'/' #define MASKALL L"*" #define READBINARY "r" @@ -215,25 +225,23 @@ #endif #endif -#if _POSIX_C_SOURCE >= 200809L +#ifdef __VMS +# define LITTLE_ENDIAN +#endif + +// Unlike Apple x64, utimensat shall be available in all Apple M1 systems. +#if _POSIX_C_SOURCE >= 200809L || defined(__APPLE__) && defined(__arm64__) #define UNIX_TIME_NS // Nanosecond time precision in Unix. #endif #endif // _UNIX -#if 0 - #define MSGID_INT - typedef int MSGID; -#else typedef const wchar* MSGID; -#endif #ifndef SSE_ALIGNMENT // No SSE use and no special data alignment is required. #define SSE_ALIGNMENT 1 #endif -#define safebuf static - // Solaris defines _LITTLE_ENDIAN or _BIG_ENDIAN. #if defined(_LITTLE_ENDIAN) && !defined(LITTLE_ENDIAN) #define LITTLE_ENDIAN @@ -264,8 +272,8 @@ #endif #endif -#if !defined(BIG_ENDIAN) && defined(_WIN_ALL) || defined(__i386__) || defined(__x86_64__) -// Allow not aligned integer access, increases speed in some operations. +#if !defined(BIG_ENDIAN) && defined(_WIN_ALL) || defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) +// Allow unaligned integer access, increases speed in some operations. #define ALLOW_MISALIGNED #endif diff --git a/unrar/pathfn.cpp b/unrar/pathfn.cpp index 41594bf9..24661666 100644 --- a/unrar/pathfn.cpp +++ b/unrar/pathfn.cpp @@ -5,7 +5,22 @@ wchar* PointToName(const wchar *Path) for (int I=(int)wcslen(Path)-1;I>=0;I--) if (IsPathDiv(Path[I])) return (wchar*)&Path[I+1]; - return (wchar*)((*Path && IsDriveDiv(Path[1])) ? Path+2:Path); + return (wchar*)((*Path!=0 && IsDriveDiv(Path[1])) ? Path+2:Path); +} + + +std::wstring PointToName(const std::wstring &Path) +{ + return std::wstring(Path.substr(GetNamePos(Path))); +} + + +size_t GetNamePos(const std::wstring &Path) +{ + for (int I=(int)Path.size()-1;I>=0;I--) + if (IsPathDiv(Path[I])) + return I+1; + return IsDriveLetter(Path) ? 2 : 0; } @@ -16,88 +31,95 @@ wchar* PointToLastChar(const wchar *Path) } -wchar* ConvertPath(const wchar *SrcPath,wchar *DestPath,size_t DestSize) +wchar GetLastChar(const std::wstring &Path) { - const wchar *DestPtr=SrcPath; + return Path.empty() ? 0:Path.back(); +} - // Prevent \..\ in any part of path string. - for (const wchar *s=DestPtr;*s!=0;s++) - if (IsPathDiv(s[0]) && s[1]=='.' && s[2]=='.' && IsPathDiv(s[3])) - DestPtr=s+4; + +size_t ConvertPath(const std::wstring *SrcPath,std::wstring *DestPath) +{ + const std::wstring &S=*SrcPath; // To avoid *SrcPath[] everywhere. + size_t DestPos=0; + + // Prevent \..\ in any part of path string and \.. at the end of string + for (size_t I=0;I:\ and any sequence of . and \ in the beginning of path string. - while (*DestPtr!=0) + while (DestPos='A' && Letter<='Z' && IsDriveDiv(Path[1]); } -int GetPathDisk(const wchar *Path) +int GetPathDisk(const std::wstring &Path) { if (IsDriveLetter(Path)) - return etoupperw(*Path)-'A'; + return etoupperw(Path[0])-'A'; else return -1; } -void AddEndSlash(wchar *Path,size_t MaxLength) +void AddEndSlash(std::wstring &Path) { - size_t Length=wcslen(Path); - if (Length>0 && Path[Length-1]!=CPATHDIVIDER && Length+12) + AddEndSlash(OutName); + OutName+=Name; + Pathname=OutName; } -// Returns file path including the trailing path separator symbol. -void GetFilePath(const wchar *FullName,wchar *Path,size_t MaxLength) +// Returns the file path including the trailing path separator symbol. +// It is allowed for both parameters to point to the same string. +void GetPathWithSep(const std::wstring &FullName,std::wstring &Path) { - if (MaxLength==0) - return; - size_t PathLength=Min(MaxLength-1,size_t(PointToName(FullName)-FullName)); - wcsncpy(Path,FullName,PathLength); - Path[PathLength]=0; + if (std::addressof(FullName)!=std::addressof(Path)) + Path=FullName; + Path.erase(GetNamePos(FullName)); } -// Removes name and returns file path without the trailing -// path separator symbol. -void RemoveNameFromPath(wchar *Path) +// Removes name and returns file path without the trailing path separator. +// But for names like d:\name return d:\ with trailing path separator. +void RemoveNameFromPath(std::wstring &Path) { - wchar *Name=PointToName(Path); - if (Name>=Path+2 && (!IsDriveDiv(Path[1]) || Name>=Path+4)) - Name--; - *Name=0; + auto NamePos=GetNamePos(Path); + if (NamePos>=2 && (!IsDriveDiv(Path[1]) || NamePos>=4)) + NamePos--; + Path.erase(NamePos); } #if defined(_WIN_ALL) && !defined(SFX_MODULE) -bool GetAppDataPath(wchar *Path,size_t MaxSize,bool Create) +bool GetAppDataPath(std::wstring &Path,bool Create) { LPMALLOC g_pMalloc; SHGetMalloc(&g_pMalloc); LPITEMIDLIST ppidl; - *Path=0; + Path.clear(); bool Success=false; if (SHGetSpecialFolderLocation(NULL,CSIDL_APPDATA,&ppidl)==NOERROR && - SHGetPathFromIDList(ppidl,Path) && *Path!=0) + SHGetPathStrFromIDList(ppidl,Path) && !Path.empty()) { - AddEndSlash(Path,MaxSize); - wcsncatz(Path,L"WinRAR",MaxSize); + AddEndSlash(Path); + Path+=L"WinRAR"; Success=FileExist(Path); if (!Success && Create) - Success=MakeDir(Path,false,0)==MKDIR_SUCCESS; + Success=CreateDir(Path); } g_pMalloc->Free(ppidl); return Success; @@ -235,24 +278,42 @@ bool GetAppDataPath(wchar *Path,size_t MaxSize,bool Create) #endif +#if defined(_WIN_ALL) +bool SHGetPathStrFromIDList(PCIDLIST_ABSOLUTE pidl,std::wstring &Path) +{ + std::vector Buf(MAX_PATH); + bool Success=SHGetPathFromIDList(pidl,Buf.data())!=FALSE; + Path=Buf.data(); + return Success; +} +#endif + + #if defined(_WIN_ALL) && !defined(SFX_MODULE) -void GetRarDataPath(wchar *Path,size_t MaxSize,bool Create) +void GetRarDataPath(std::wstring &Path,bool Create) { - *Path=0; + Path.clear(); + // This option to change %AppData% location is documented in wunrar.chm. HKEY hKey; if (RegOpenKeyEx(HKEY_CURRENT_USER,L"Software\\WinRAR\\Paths",0, KEY_QUERY_VALUE,&hKey)==ERROR_SUCCESS) { - DWORD DataSize=(DWORD)MaxSize,Type; - RegQueryValueEx(hKey,L"AppData",0,&Type,(BYTE *)Path,&DataSize); - RegCloseKey(hKey); + DWORD DataSize; + LSTATUS Code=RegQueryValueEx(hKey,L"AppData",NULL,NULL,NULL,&DataSize); + if (Code==ERROR_SUCCESS) + { + std::vector PathBuf(DataSize/sizeof(wchar)); + RegQueryValueEx(hKey,L"AppData",0,NULL,(BYTE *)PathBuf.data(),&DataSize); + Path=PathBuf.data(); + RegCloseKey(hKey); + } } - if (*Path==0 || !FileExist(Path)) - if (!GetAppDataPath(Path,MaxSize,Create)) + if (Path.empty() || !FileExist(Path)) + if (!GetAppDataPath(Path,Create)) { - GetModuleFileName(NULL,Path,(DWORD)MaxSize); + Path=GetModuleFileStr(); RemoveNameFromPath(Path); } } @@ -260,7 +321,7 @@ void GetRarDataPath(wchar *Path,size_t MaxSize,bool Create) #ifndef SFX_MODULE -bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create) +bool EnumConfigPaths(uint Number,std::wstring &Path,bool Create) { #ifdef _UNIX static const wchar *ConfPath[]={ @@ -268,26 +329,47 @@ bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create) }; if (Number==0) { - char *EnvStr=getenv("HOME"); - if (EnvStr!=NULL) - CharToWide(EnvStr,Path,MaxSize); + const char *EnvStr=getenv("HOME"); + if (EnvStr!=nullptr) + CharToWide(EnvStr,Path); else - wcsncpyz(Path,ConfPath[0],MaxSize); + Path=ConfPath[0]; + return true; + } + if (Number==1) // According to XDG Base Directory Specification. + { + const char *EnvStr=getenv("XDG_CONFIG_HOME"); + if (EnvStr!=nullptr && *EnvStr!=0) + { + CharToWide(EnvStr,Path); + MakeName(Path,L"rar",Path); + } + else + { + const char *EnvStr=getenv("HOME"); + if (EnvStr!=nullptr) + { + CharToWide(EnvStr,Path); + MakeName(Path,L".config/rar",Path); + } + else + Path=ConfPath[0]; + } return true; } - Number--; + Number-=2; if (Number>=ASIZE(ConfPath)) return false; - wcsncpyz(Path,ConfPath[Number], MaxSize); + Path=ConfPath[Number]; return true; #elif defined(_WIN_ALL) if (Number>1) return false; if (Number==0) - GetRarDataPath(Path,MaxSize,Create); + GetRarDataPath(Path,Create); else { - GetModuleFileName(NULL,Path,(DWORD)MaxSize); + Path=GetModuleFileStr(); RemoveNameFromPath(Path); } return true; @@ -299,13 +381,15 @@ bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create) #ifndef SFX_MODULE -void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckExist,bool Create) +void GetConfigName(const std::wstring &Name,std::wstring &FullName,bool CheckExist,bool Create) { - *FullName=0; - for (uint I=0;EnumConfigPaths(I,FullName,MaxSize,Create);I++) + FullName.clear(); + for (uint I=0;;I++) { - AddEndSlash(FullName,MaxSize); - wcsncatz(FullName,Name,MaxSize); + std::wstring ConfPath; + if (!EnumConfigPaths(I,ConfPath,Create)) + break; + MakeName(ConfPath,Name,FullName); if (!CheckExist || WildFileExist(FullName)) break; } @@ -313,171 +397,183 @@ void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckEx #endif -// Returns a pointer to rightmost digit of volume number or to beginning +// Returns the position to rightmost digit of volume number or beginning // of file name if numeric part is missing. -wchar* GetVolNumPart(const wchar *ArcName) +size_t GetVolNumPos(const std::wstring &ArcName) { - if (*ArcName==0) - return (wchar *)ArcName; + // We do not want to increment any characters in path component. + size_t NamePos=GetNamePos(ArcName); + + if (NamePos==ArcName.size()) + return NamePos; // Pointing to last name character. - const wchar *ChPtr=ArcName+wcslen(ArcName)-1; + size_t Pos=ArcName.size()-1; // Skipping the archive extension. - while (!IsDigit(*ChPtr) && ChPtr>ArcName) - ChPtr--; + while (!IsDigit(ArcName[Pos]) && Pos>NamePos) + Pos--; // Skipping the numeric part of name. - const wchar *NumPtr=ChPtr; - while (IsDigit(*NumPtr) && NumPtr>ArcName) - NumPtr--; + size_t NumPos=Pos; + while (IsDigit(ArcName[NumPos]) && NumPos>NamePos) + NumPos--; // Searching for first numeric part in names like name.part##of##.rar. // Stop search on the first dot. - while (NumPtr>ArcName && *NumPtr!='.') + while (NumPos>NamePos && ArcName[NumPos]!='.') { - if (IsDigit(*NumPtr)) + if (IsDigit(ArcName[NumPos])) { // Validate the first numeric part only if it has a dot somewhere // before it. - wchar *Dot=wcschr(PointToName(ArcName),'.'); - if (Dot!=NULL && Dot|\"")==NULL; + } + return !Name.empty() && Name.find_first_of(L"?*<>|\"")==std::wstring::npos; } -void MakeNameUsable(char *Name,bool Extended) +void MakeNameUsable(std::wstring &Name,bool Extended) { + size_t StartPos=0; #ifdef _WIN_ALL - // In Windows we also need to convert characters not defined in current - // code page. This double conversion changes them to '?', which is - // catched by code below. - size_t NameLength=strlen(Name); - wchar NameW[NM]; - CharToWide(Name,NameW,ASIZE(NameW)); - WideToChar(NameW,Name,NameLength+1); - Name[NameLength]=0; -#endif - for (char *s=Name;*s!=0;s=charnext(s)) - { - if (strchr(Extended ? "?*<>|\"":"?*",*s)!=NULL || Extended && (byte)*s<32) - *s='_'; -#ifdef _EMX - if (*s=='=') - *s='_'; + // 2025.07.03: Do not replace '?' and ':' in \\?\d: in the beginning of path + // in Windows. + if (Name.size()>5 && starts_with(Name,L"\\\\?\\") && IsDriveLetter(&Name[4])) + StartPos=6; #endif -#ifndef _UNIX - if (s-Name>1 && *s==':') - *s='_'; - // Remove ' ' and '.' before path separator, but allow .\ and ..\. - if ((*s==' ' || *s=='.' && s>Name && !IsPathDiv(s[-1]) && s[-1]!='.') && IsPathDiv(s[1])) - *s='_'; -#endif - } -} - -void MakeNameUsable(wchar *Name,bool Extended) -{ - for (wchar *s=Name;*s!=0;s++) + for (size_t I=StartPos;I|\"":L"?*",*s)!=NULL || Extended && (uint)*s<32) - *s='_'; -#ifndef _UNIX - if (s-Name>1 && *s==':') - *s='_'; -#if 0 // We already can create such files. - // Remove ' ' and '.' before path separator, but allow .\ and ..\. - if (IsPathDiv(s[1]) && (*s==' ' || *s=='.' && s>Name && - !IsPathDiv(s[-1]) && (s[-1]!='.' || s>Name+1 && !IsPathDiv(s[-2])))) - *s='_'; -#endif + if (wcschr(Extended ? L"?*<>|\"":L"?*",Name[I])!=NULL || + Extended && (uint)Name[I]<32) + Name[I]='_'; +#ifdef _UNIX + // We were asked to apply Windows-like conversion in Linux in case + // files are unpacked to Windows share. This code is invoked only + // if file failed to be created, so it doesn't affect extraction + // of Unix compatible names to native Unix drives. + if (Extended) + { + // Windows shares in Unix do not allow the drive letter, + // so unlike Windows version, we check all characters here. + if (Name[I]==':') + Name[I]='_'; + + // No spaces or dots before the path separator are allowed on Windows + // shares. But they are allowed and automatically removed at the end of + // file or folder name, so we need to replace them only before + // the path separator, but not at the end of file name. + // Since such files or folders are created successfully, a supposed + // conversion at the end of file name would never be invoked here. + // While converting dots, we preserve "." and ".." path components, + // such as when specifying ".." in the destination path. + if (IsPathDiv(Name[I+1]) && (Name[I]==' ' || Name[I]=='.' && I>0 && + !IsPathDiv(Name[I-1]) && (Name[I-1]!='.' || I>1 && !IsPathDiv(Name[I-2])))) + Name[I]='_'; + } +#else + if (I>1 && Name[I]==':') + Name[I]='_'; #endif } } @@ -492,20 +588,38 @@ void UnixSlashToDos(const char *SrcName,char *DestName,size_t MaxLength) } -void DosSlashToUnix(const char *SrcName,char *DestName,size_t MaxLength) +void UnixSlashToDos(const wchar *SrcName,wchar *DestName,size_t MaxLength) { size_t Copied=0; for (;Copied0) - *Dest=0; + Dest.clear(); return; } #ifdef _WIN_ALL { - wchar FullName[NM],*NamePtr; - DWORD Code=GetFullPathName(Src,ASIZE(FullName),FullName,&NamePtr); - if (Code==0 || Code>ASIZE(FullName)) + DWORD Code=GetFullPathName(Src.c_str(),0,NULL,NULL); // Get the buffer size. + if (Code!=0) { - wchar LongName[NM]; - if (GetWinLongPath(Src,LongName,ASIZE(LongName))) - Code=GetFullPathName(LongName,ASIZE(FullName),FullName,&NamePtr); + std::vector FullName(Code); + Code=GetFullPathName(Src.c_str(),(DWORD)FullName.size(),FullName.data(),NULL); + + if (Code>0 && Code<=FullName.size()) + { + Dest=FullName.data(); + return; + } } - if (Code!=0 && Code FullName(Code); + Code=GetFullPathName(LongName.c_str(),(DWORD)FullName.size(),FullName.data(),NULL); + + if (Code>0 && Code<=FullName.size()) + { + Dest=FullName.data(); + return; + } + } + } + if (Src!=Dest) + Dest=Src; // Copy source to destination in case of failure. } #elif defined(_UNIX) if (IsFullPath(Src)) - *Dest=0; + Dest.clear(); else { - char CurDirA[NM]; - if (getcwd(CurDirA,ASIZE(CurDirA))==NULL) - *CurDirA=0; - CharToWide(CurDirA,Dest,MaxSize); - AddEndSlash(Dest,MaxSize); + std::vector CurDirA(MAXPATHSIZE); + if (getcwd(CurDirA.data(),CurDirA.size())==NULL) + CurDirA[0]=0; + CharToWide(CurDirA.data(),Dest); + AddEndSlash(Dest); } - wcsncatz(Dest,Src,MaxSize); + Dest+=Src; #else - wcsncpyz(Dest,Src,MaxSize); + Dest=Src; #endif } -bool IsFullPath(const wchar *Path) +bool IsFullPath(const std::wstring &Path) { -/* - wchar PathOnly[NM]; - GetFilePath(Path,PathOnly,ASIZE(PathOnly)); - if (IsWildcard(PathOnly)) - return true; -*/ -#if defined(_WIN_ALL) || defined(_EMX) - return Path[0]=='\\' && Path[1]=='\\' || IsDriveLetter(Path) && IsPathDiv(Path[2]); +#ifdef _WIN_ALL + return Path.size()>=2 && Path[0]=='\\' && Path[1]=='\\' || + Path.size()>=3 && IsDriveLetter(Path) && IsPathDiv(Path[2]); #else - return IsPathDiv(Path[0]); + return Path.size()>=1 && IsPathDiv(Path[0]); #endif } -bool IsFullRootPath(const wchar *Path) +bool IsFullRootPath(const std::wstring &Path) { return IsFullPath(Path) || IsPathDiv(Path[0]); } -void GetPathRoot(const wchar *Path,wchar *Root,size_t MaxSize) +// Both source and destination can point to the same string. +void GetPathRoot(const std::wstring &Path,std::wstring &Root) { - *Root=0; if (IsDriveLetter(Path)) - swprintf(Root,MaxSize,L"%c:\\",*Path); + Root=Path.substr(0,2) + L"\\"; else if (Path[0]=='\\' && Path[1]=='\\') { - const wchar *Slash=wcschr(Path+2,'\\'); - if (Slash!=NULL) + size_t Slash=Path.find('\\',2); + if (Slash!=std::wstring::npos) { size_t Length; - if ((Slash=wcschr(Slash+1,'\\'))!=NULL) - Length=Slash-Path+1; + if ((Slash=Path.find('\\',Slash+1))!=std::wstring::npos) + Length=Slash+1; else - Length=wcslen(Path); - if (Length>=MaxSize) - Length=0; - wcsncpy(Root,Path,Length); - Root[Length]=0; + Length=Path.size(); + Root=Path.substr(0,Length); } } + else + Root.clear(); } -int ParseVersionFileName(wchar *Name,bool Truncate) +int ParseVersionFileName(std::wstring &Name,bool Truncate) { int Version=0; - wchar *VerText=wcsrchr(Name,';'); - if (VerText!=NULL) + auto VerPos=Name.rfind(';'); + if (VerPos!=std::wstring::npos && VerPos+1FirstName;ChPtr--) - if (IsDigit(*ChPtr)) + for (size_t Pos=GetVolNumPos(Name);Pos>0;Pos--) + if (IsDigit(Name[Pos])) { - *ChPtr=N; // Set the rightmost digit to '1' and others to '0'. + Name[Pos]=N; // Set the rightmost digit to '1' and others to '0'. N='0'; } else - if (N=='0') + if (N=='0') // If we already set the rightmost '1' before. { - VolNumStart=ChPtr+1; // Store the position of leftmost digit in volume number. + VolNumStart=Pos+1; // Store the position of leftmost digit in volume number. break; } } else { // Old volume numbering scheme. Just set the extension to ".rar". - SetExt(FirstName,L"rar",MaxSize); - VolNumStart=GetExt(FirstName); + SetExt(Name,L"rar"); + VolNumStart=GetExtPos(Name); } - if (!FileExist(FirstName)) + if (!FileExist(Name)) { // If the first volume, which name we just generated, does not exist, // check if volume with same name and any other extension is available. // It can help in case of *.exe or *.sfx first volume. - wchar Mask[NM]; - wcsncpyz(Mask,FirstName,ASIZE(Mask)); - SetExt(Mask,L"*",ASIZE(Mask)); + std::wstring Mask=Name; + SetExt(Mask,L"*"); FindFile Find; Find.SetMask(Mask); FindData FD; @@ -669,31 +812,34 @@ wchar* VolNameToFirstName(const wchar *VolName,wchar *FirstName,size_t MaxSize,b Archive Arc; if (Arc.Open(FD.Name,0) && Arc.IsArchive(true) && Arc.FirstVolume) { - wcsncpyz(FirstName,FD.Name,MaxSize); + Name=FD.Name; break; } } } + FirstName=Name; return VolNumStart; } #endif #ifndef SFX_MODULE -static void GenArcName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,uint ArcNumber,bool &ArcNumPresent) +static void GenArcName(std::wstring &ArcName,const std::wstring &GenerateMask,uint ArcNumber,bool &ArcNumPresent) { + size_t Pos=0; bool Prefix=false; - if (*GenerateMask=='+') + if (GenerateMask[0]=='+') { Prefix=true; // Add the time string before the archive name. - GenerateMask++; // Skip '+' in the beginning of time mask. + Pos++; // Skip '+' in the beginning of time mask. } - wchar Mask[MAX_GENERATE_MASK]; - wcsncpyz(Mask,*GenerateMask!=0 ? GenerateMask:L"yyyymmddhhmmss",ASIZE(Mask)); + // Set the default mask for -ag or -ag+, use the specified otherwise. + std::wstring Mask=GenerateMask.size()>Pos ? GenerateMask.substr(Pos):L"yyyymmddhhmmss"; - bool QuoteMode=false,Hours=false; - for (uint I=0;Mask[I]!=0;I++) + bool QuoteMode=false; + uint MAsMinutes=0; // By default we treat 'M' as months. + for (uint I=0;I0) + { + // Replace minutes with 'I'. We use 'M' both for months and minutes, + // so we treat as minutes only those 'M', which are found after hours. + Mask[I]='I'; + MAsMinutes--; + } + else + { + // Treat 3 or more 'M' as a month name and replace with 'O'. + if (I+2=4) CurWeek++; - char Field[10][6]; - - sprintf(Field[0],"%04u",rlt.Year); - sprintf(Field[1],"%02u",rlt.Month); - sprintf(Field[2],"%02u",rlt.Day); - sprintf(Field[3],"%02u",rlt.Hour); - sprintf(Field[4],"%02u",rlt.Minute); - sprintf(Field[5],"%02u",rlt.Second); - sprintf(Field[6],"%02u",(uint)CurWeek); - sprintf(Field[7],"%u",(uint)WeekDay+1); - sprintf(Field[8],"%03u",rlt.yDay+1); - sprintf(Field[9],"%05u",ArcNumber); + const size_t FieldSize=20; + wchar Field[12][FieldSize]; + + swprintf(Field[0],FieldSize,L"%04u",rlt.Year); + swprintf(Field[1],FieldSize,L"%02u",rlt.Month); + swprintf(Field[2],FieldSize,L"%02u",rlt.Day); + swprintf(Field[3],FieldSize,L"%02u",rlt.Hour); + swprintf(Field[4],FieldSize,L"%02u",rlt.Minute); + swprintf(Field[5],FieldSize,L"%02u",rlt.Second); + swprintf(Field[6],FieldSize,L"%02u",(uint)CurWeek); + swprintf(Field[7],FieldSize,L"%u",(uint)WeekDay+1); + swprintf(Field[8],FieldSize,L"%03u",rlt.yDay+1); + swprintf(Field[9],FieldSize,L"%05u",ArcNumber); + wcsncpyz(Field[10],uiGetWeekDayName(rlt.wDay),FieldSize); + wcsncpyz(Field[11],GetMonthName(rlt.Month-1),FieldSize); + + int LField[sizeof(Field)/sizeof(Field[0])]; // Field lengths. + for (size_t I=0;I=0) + DateText[J++]=Field[FieldPos][LField[FieldPos]-CField[FieldPos]--]; + DateText[J]=0; } - DateText[++J]=0; } if (Prefix) { - wchar NewName[NM]; - GetFilePath(ArcName,NewName,ASIZE(NewName)); - AddEndSlash(NewName,ASIZE(NewName)); - wcsncatz(NewName,DateText,ASIZE(NewName)); - wcsncatz(NewName,PointToName(ArcName),ASIZE(NewName)); - wcsncpyz(ArcName,NewName,MaxSize); + std::wstring NewName; + GetPathWithSep(ArcName,NewName); + NewName+=DateText; + NewName+=PointToName(ArcName); + ArcName=NewName; } else - wcsncatz(ArcName,DateText,MaxSize); - wcsncatz(ArcName,Ext,MaxSize); + ArcName+=DateText; + ArcName+=Ext; } -void GenerateArchiveName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,bool Archiving) +void GenerateArchiveName(std::wstring &ArcName,const std::wstring &GenerateMask,bool Archiving) { - wchar NewName[NM]; + std::wstring NewName; uint ArcNumber=1; while (true) // Loop for 'N' (archive number) processing. { - wcsncpyz(NewName,ArcName,ASIZE(NewName)); + NewName=ArcName; bool ArcNumPresent=false; - GenArcName(NewName,ASIZE(NewName),GenerateMask,ArcNumber,ArcNumPresent); + GenArcName(NewName,GenerateMask,ArcNumber,ArcNumPresent); if (!ArcNumPresent) break; @@ -868,103 +1040,66 @@ void GenerateArchiveName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask // If we perform non-archiving operation, we need to use the last // existing archive before the first unused name. So we generate // the name for (ArcNumber-1) below. - wcsncpyz(NewName,NullToEmpty(ArcName),ASIZE(NewName)); - GenArcName(NewName,ASIZE(NewName),GenerateMask,ArcNumber-1,ArcNumPresent); + NewName=ArcName; + GenArcName(NewName,GenerateMask,ArcNumber-1,ArcNumPresent); } break; } ArcNumber++; } - wcsncpyz(ArcName,NewName,MaxSize); + ArcName=NewName; } #endif -wchar* GetWideName(const char *Name,const wchar *NameW,wchar *DestW,size_t DestSize) -{ - if (NameW!=NULL && *NameW!=0) - { - if (DestW!=NameW) - wcsncpy(DestW,NameW,DestSize); - } - else - if (Name!=NULL) - CharToWide(Name,DestW,DestSize); - else - *DestW=0; - - // Ensure that we return a zero terminate string for security reasons. - if (DestSize>0) - DestW[DestSize-1]=0; - - return DestW; -} - - #ifdef _WIN_ALL // We should return 'true' even if resulting path is shorter than MAX_PATH, // because we can also use this function to open files with non-standard // characters, even if their path length is normal. -bool GetWinLongPath(const wchar *Src,wchar *Dest,size_t MaxSize) +bool GetWinLongPath(const std::wstring &Src,std::wstring &Dest) { - if (*Src==0) + if (Src.empty()) return false; - const wchar *Prefix=L"\\\\?\\"; - const size_t PrefixLength=4; - bool FullPath=IsDriveLetter(Src) && IsPathDiv(Src[2]); - size_t SrcLength=wcslen(Src); + const std::wstring Prefix=L"\\\\?\\"; + + bool FullPath=Src.size()>=3 && IsDriveLetter(Src) && IsPathDiv(Src[2]); if (IsFullPath(Src)) // Paths in d:\path\name format. { if (IsDriveLetter(Src)) { - if (MaxSize<=PrefixLength+SrcLength) - return false; - wcsncpyz(Dest,Prefix,MaxSize); - wcsncatz(Dest,Src,MaxSize); // "\\?\D:\very long path". + Dest=Prefix+Src; // "\\?\D:\very long path". return true; } else - if (Src[0]=='\\' && Src[1]=='\\') + if (Src.size()>2 && Src[0]=='\\' && Src[1]=='\\') { - if (MaxSize<=PrefixLength+SrcLength+2) - return false; - wcsncpyz(Dest,Prefix,MaxSize); - wcsncatz(Dest,L"UNC",MaxSize); - wcsncatz(Dest,Src+1,MaxSize); // "\\?\UNC\server\share". + Dest=Prefix+L"UNC"+Src.substr(1); // "\\?\UNC\server\share". return true; } - // We may be here only if we modify IsFullPath in the future. + // We can be here only if modify IsFullPath() in the future. return false; } else { - wchar CurDir[NM]; - DWORD DirCode=GetCurrentDirectory(ASIZE(CurDir)-1,CurDir); - if (DirCode==0 || DirCode>ASIZE(CurDir)-1) + std::wstring CurDir; + if (!GetCurDir(CurDir)) return false; if (IsPathDiv(Src[0])) // Paths in \path\name format. { - if (MaxSize<=PrefixLength+SrcLength+2) - return false; - wcsncpyz(Dest,Prefix,MaxSize); - CurDir[2]=0; - wcsncatz(Dest,CurDir,MaxSize); // Copy drive letter 'd:'. - wcsncatz(Dest,Src,MaxSize); + Dest=Prefix+CurDir[0]+L':'+Src; // Copy drive letter 'd:'. return true; } else // Paths in path\name format. { - AddEndSlash(CurDir,ASIZE(CurDir)); - if (MaxSize<=PrefixLength+wcslen(CurDir)+SrcLength) - return false; - wcsncpyz(Dest,Prefix,MaxSize); - wcsncatz(Dest,CurDir,MaxSize); + Dest=Prefix+CurDir; + AddEndSlash(Dest); + size_t Pos=0; if (Src[0]=='.' && IsPathDiv(Src[1])) // Remove leading .\ in pathname. - Src+=2; + Pos=2; - wcsncatz(Dest,Src,MaxSize); + Dest+=Src.substr(Pos); return true; } } @@ -973,37 +1108,131 @@ bool GetWinLongPath(const wchar *Src,wchar *Dest,size_t MaxSize) // Convert Unix, OS X and Android decomposed chracters to Windows precomposed. -void ConvertToPrecomposed(wchar *Name,size_t NameSize) +void ConvertToPrecomposed(std::wstring &Name) { - wchar FileName[NM]; - if (WinNT()>=WNT_VISTA && // MAP_PRECOMPOSED is not supported in XP. - FoldString(MAP_PRECOMPOSED,Name,-1,FileName,ASIZE(FileName))!=0) - { - FileName[ASIZE(FileName)-1]=0; - wcsncpyz(Name,FileName,NameSize); - } + if (WinNT() FileName(Size); + if (FoldString(MAP_PRECOMPOSED,Name.c_str(),-1,FileName.data(),(int)FileName.size())!=0) + Name=FileName.data(); } -// Remove trailing spaces and dots in file name and in dir names in path. -void MakeNameCompatible(wchar *Name) +void MakeNameCompatible(std::wstring &Name) { - int Src=0,Dest=0; - while (true) - { - if (IsPathDiv(Name[Src]) || Name[Src]==0) - for (int I=Dest-1;I>0 && (Name[I]==' ' || Name[I]=='.');I--) + // Remove trailing spaces and dots in file name and in dir names in path. + for (int I=0;I<(int)Name.size();I++) + if (I+1==Name.size() || IsPathDiv(Name[I+1])) + while (I>=0 && (Name[I]=='.' || Name[I]==' ')) { - // Permit path1/./path2 and ../path1 paths. - if (Name[I]=='.' && (IsPathDiv(Name[I-1]) || Name[I-1]=='.' && I==1)) - break; - Dest--; + if (Name[I]=='.') + { + // 2024.05.01: Permit ./path1, path1/./path2, ../path1, + // path1/../path2 and exotic Win32 d:.\path1, d:..\path1 paths + // requested by user. Leading dots are possible here if specified + // by user in the destination path. + if (I==0 || IsPathDiv(Name[I-1]) || I==2 && IsDriveLetter(Name)) + break; + if (I>=1 && Name[I-1]=='.' && (I==1 || IsPathDiv(Name[I-2]) || + I==3 && IsDriveLetter(Name))) + break; + } + + Name[I]='_'; + break; } - Name[Dest]=Name[Src]; - if (Name[Src]==0) + + // Rename reserved device names, such as aux.txt to _aux.txt. + // We check them in path components too, where they are also prohibited. + for (size_t I=0;I0 && IsPathDiv(Name[I-1])) + { + static const wchar *Devices[]={L"CON",L"PRN",L"AUX",L"NUL",L"COM#",L"LPT#"}; + const wchar *s=&Name[I]; + bool MatchFound=false; + for (uint J=0;J Path(256); + while (Path.size()<=MAXPATHSIZE) + { + if (GetModuleFileName(hModule,Path.data(),(DWORD)Path.size()) Buf(BufSize); + DWORD Code=GetCurrentDirectory((DWORD)Buf.size(),Buf.data()); + Dir=Buf.data(); + return Code!=0; +} +#endif + + diff --git a/unrar/pathfn.hpp b/unrar/pathfn.hpp index 63813d8a..57dab5a5 100644 --- a/unrar/pathfn.hpp +++ b/unrar/pathfn.hpp @@ -2,40 +2,52 @@ #define _RAR_PATHFN_ wchar* PointToName(const wchar *Path); +std::wstring PointToName(const std::wstring &Path); +size_t GetNamePos(const std::wstring &Path); wchar* PointToLastChar(const wchar *Path); -wchar* ConvertPath(const wchar *SrcPath,wchar *DestPath,size_t DestSize); -void SetName(wchar *FullName,const wchar *Name,size_t MaxSize); -void SetExt(wchar *Name,const wchar *NewExt,size_t MaxSize); -void SetSFXExt(wchar *SFXName,size_t MaxSize); +wchar GetLastChar(const std::wstring &Path); +size_t ConvertPath(const std::wstring *SrcPath,std::wstring *DestPath); +void SetName(std::wstring &FullName,const std::wstring &Name); +void SetExt(std::wstring &Name,std::wstring NewExt); +void RemoveExt(std::wstring &Name); +void SetSFXExt(std::wstring &SFXName); wchar *GetExt(const wchar *Name); -bool CmpExt(const wchar *Name,const wchar *Ext); -bool IsWildcard(const wchar *Str); +std::wstring GetExt(const std::wstring &Name); +std::wstring::size_type GetExtPos(const std::wstring &Name); +bool CmpExt(const std::wstring &Name,const std::wstring &Ext); +bool IsWildcard(const std::wstring &Str); bool IsPathDiv(int Ch); bool IsDriveDiv(int Ch); -bool IsDriveLetter(const wchar *Path); -int GetPathDisk(const wchar *Path); -void AddEndSlash(wchar *Path,size_t MaxLength); -void MakeName(const wchar *Path,const wchar *Name,wchar *Pathname,size_t MaxSize); -void GetFilePath(const wchar *FullName,wchar *Path,size_t MaxLength); -void RemoveNameFromPath(wchar *Path); +bool IsDriveLetter(const std::wstring &Path); +int GetPathDisk(const std::wstring &Path); +void AddEndSlash(std::wstring &Path); +void MakeName(const std::wstring &Path,const std::wstring &Name,std::wstring &Pathname); +void GetPathWithSep(const std::wstring &FullName,std::wstring &Path); +void RemoveNameFromPath(std::wstring &Path); #if defined(_WIN_ALL) && !defined(SFX_MODULE) -bool GetAppDataPath(wchar *Path,size_t MaxSize,bool Create); -void GetRarDataPath(wchar *Path,size_t MaxSize,bool Create); +bool GetAppDataPath(std::wstring &Path,bool Create); +void GetRarDataPath(std::wstring &Path,bool Create); +#endif +#ifdef _WIN_ALL +bool SHGetPathStrFromIDList(PCIDLIST_ABSOLUTE pidl,std::wstring &Path); #endif #ifndef SFX_MODULE -bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create); -void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckExist,bool Create); +bool EnumConfigPaths(uint Number,std::wstring &Path,bool Create); +void GetConfigName(const std::wstring &Name,std::wstring &FullName,bool CheckExist,bool Create); #endif -wchar* GetVolNumPart(const wchar *ArcName); -void NextVolumeName(wchar *ArcName,uint MaxLength,bool OldNumbering); -bool IsNameUsable(const wchar *Name); -void MakeNameUsable(char *Name,bool Extended); -void MakeNameUsable(wchar *Name,bool Extended); +size_t GetVolNumPos(const std::wstring &ArcName); +void NextVolumeName(std::wstring &ArcName,bool OldNumbering); +bool IsNameUsable(const std::wstring &Name); +void MakeNameUsable(std::wstring &Name,bool Extended); void UnixSlashToDos(const char *SrcName,char *DestName,size_t MaxLength); -void DosSlashToUnix(const char *SrcName,char *DestName,size_t MaxLength); void UnixSlashToDos(const wchar *SrcName,wchar *DestName,size_t MaxLength); +void UnixSlashToDos(const std::string &SrcName,std::string &DestName); +void UnixSlashToDos(const std::wstring &SrcName,std::wstring &DestName); +void DosSlashToUnix(const char *SrcName,char *DestName,size_t MaxLength); void DosSlashToUnix(const wchar *SrcName,wchar *DestName,size_t MaxLength); +void DosSlashToUnix(const std::string &SrcName,std::string &DestName); +void DosSlashToUnix(const std::wstring &SrcName,std::wstring &DestName); inline void SlashToNative(const char *SrcName,char *DestName,size_t MaxLength) { @@ -46,6 +58,15 @@ inline void SlashToNative(const char *SrcName,char *DestName,size_t MaxLength) #endif } +inline void SlashToNative(const std::string &SrcName,std::string &DestName) +{ +#ifdef _WIN_ALL + UnixSlashToDos(SrcName,DestName); +#else + DosSlashToUnix(SrcName,DestName); +#endif +} + inline void SlashToNative(const wchar *SrcName,wchar *DestName,size_t MaxLength) { #ifdef _WIN_ALL @@ -55,22 +76,45 @@ inline void SlashToNative(const wchar *SrcName,wchar *DestName,size_t MaxLength) #endif } -void ConvertNameToFull(const wchar *Src,wchar *Dest,size_t MaxSize); -bool IsFullPath(const wchar *Path); -bool IsFullRootPath(const wchar *Path); -void GetPathRoot(const wchar *Path,wchar *Root,size_t MaxSize); -int ParseVersionFileName(wchar *Name,bool Truncate); -wchar* VolNameToFirstName(const wchar *VolName,wchar *FirstName,size_t MaxSize,bool NewNumbering); -wchar* GetWideName(const char *Name,const wchar *NameW,wchar *DestW,size_t DestSize); +inline void SlashToNative(const std::wstring &SrcName,std::wstring &DestName) +{ +#ifdef _WIN_ALL + UnixSlashToDos(SrcName,DestName); +#else + DosSlashToUnix(SrcName,DestName); +#endif +} + +void ConvertNameToFull(const std::wstring &Src,std::wstring &Dest); +bool IsFullPath(const std::wstring &Path); +bool IsFullRootPath(const std::wstring &Path); +void GetPathRoot(const std::wstring &Path,std::wstring &Root); +int ParseVersionFileName(std::wstring &Name,bool Truncate); +size_t VolNameToFirstName(const std::wstring &VolName,std::wstring &FirstName,bool NewNumbering); #ifndef SFX_MODULE -void GenerateArchiveName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,bool Archiving); +void GenerateArchiveName(std::wstring &ArcName,const std::wstring &GenerateMask,bool Archiving); #endif #ifdef _WIN_ALL -bool GetWinLongPath(const wchar *Src,wchar *Dest,size_t MaxSize); -void ConvertToPrecomposed(wchar *Name,size_t NameSize); -void MakeNameCompatible(wchar *Name); +bool GetWinLongPath(const std::wstring &Src,std::wstring &Dest); +void ConvertToPrecomposed(std::wstring &Name); +void MakeNameCompatible(std::wstring &Name); +#endif + + +#ifdef _WIN_ALL +std::wstring GetModuleFileStr(); +std::wstring GetProgramFile(const std::wstring &Name); #endif +#if defined(_WIN_ALL) +bool SetCurDir(const std::wstring &Dir); +#endif + +#ifdef _WIN_ALL +bool GetCurDir(std::wstring &Dir); +#endif + + #endif diff --git a/unrar/qopen.cpp b/unrar/qopen.cpp index 43346b06..e4ae8a9d 100644 --- a/unrar/qopen.cpp +++ b/unrar/qopen.cpp @@ -97,7 +97,7 @@ void QuickOpen::Load(uint64 BlockPos) if (Arc->SubHead.Encrypted) { - RAROptions *Cmd=Arc->GetRAROptions(); + CommandData *Cmd=Arc->GetCommandData(); #ifndef RAR_NOCRYPT if (Cmd->Password.IsSet()) Crypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,Arc->SubHead.Salt, @@ -114,7 +114,7 @@ void QuickOpen::Load(uint64 BlockPos) RawDataPos=0; ReadBufSize=0; ReadBufPos=0; - LastReadHeader.Reset(); + LastReadHeader.clear(); LastReadHeaderPos=0; ReadBuffer(); @@ -126,7 +126,7 @@ bool QuickOpen::Read(void *Data,size_t Size,size_t &Result) if (!Loaded) return false; // Find next suitable cached block. - while (LastReadHeaderPos+LastReadHeader.Size()<=SeekPos) + while (LastReadHeaderPos+LastReadHeader.size()<=SeekPos) if (!ReadNext()) break; if (!Loaded) @@ -138,9 +138,9 @@ bool QuickOpen::Read(void *Data,size_t Size,size_t &Result) return false; } - if (SeekPos>=LastReadHeaderPos && SeekPos+Size<=LastReadHeaderPos+LastReadHeader.Size()) + if (SeekPos>=LastReadHeaderPos && SeekPos+Size<=LastReadHeaderPos+LastReadHeader.size()) { - memcpy(Data,LastReadHeader+size_t(SeekPos-LastReadHeaderPos),Size); + memcpy(Data,&LastReadHeader[size_t(SeekPos-LastReadHeaderPos)],Size); Result=Size; SeekPos+=Size; UnsyncSeekPos=true; @@ -292,8 +292,8 @@ bool QuickOpen::ReadNext() size_t HeaderSize=(size_t)Raw.GetV(); if (HeaderSize>MAX_HEADER_SIZE_RAR5) return false; - LastReadHeader.Alloc(HeaderSize); - Raw.GetB(&LastReadHeader[0],HeaderSize); + LastReadHeader.resize(HeaderSize); + Raw.GetB(LastReadHeader.data(),HeaderSize); // Calculate the absolute position as offset from quick open service header. LastReadHeaderPos=QOHeaderPos-Offset; return true; diff --git a/unrar/qopen.hpp b/unrar/qopen.hpp index d745cea8..7adca4b5 100644 --- a/unrar/qopen.hpp +++ b/unrar/qopen.hpp @@ -43,7 +43,7 @@ class QuickOpen uint64 RawDataPos; // Current read position in QO data. size_t ReadBufSize; // Size of Buf data currently read from QO. size_t ReadBufPos; // Current read position in Buf data. - Array LastReadHeader; + std::vector LastReadHeader; uint64 LastReadHeaderPos; uint64 SeekPos; bool UnsyncSeekPos; // QOpen SeekPos does not match an actual file pointer. diff --git a/unrar/rar.cpp b/unrar/rar.cpp index 34b4b278..cd20dbec 100644 --- a/unrar/rar.cpp +++ b/unrar/rar.cpp @@ -12,11 +12,11 @@ int main(int argc, char *argv[]) ErrHandler.SetSignalHandlers(true); #ifdef SFX_MODULE - wchar ModuleName[NM]; + std::wstring ModuleName; #ifdef _WIN_ALL - GetModuleFileName(NULL,ModuleName,ASIZE(ModuleName)); + ModuleName=GetModuleFileStr(); #else - CharToWide(argv[0],ModuleName,ASIZE(ModuleName)); + CharToWide(argv[0],ModuleName); #endif #endif @@ -35,9 +35,10 @@ int main(int argc, char *argv[]) try { - CommandData *Cmd=new CommandData; + // Use std::unique_ptr to free Cmd in case of exception. + std::unique_ptr Cmd(new CommandData); #ifdef SFX_MODULE - wcsncpyz(Cmd->Command,L"X",ASIZE(Cmd->Command)); + Cmd->Command=L"X"; char *Switch=argc>1 ? argv[1]:NULL; if (Switch!=NULL && Cmd->IsSwitch(Switch[0])) { @@ -46,7 +47,10 @@ int main(int argc, char *argv[]) { case 'T': case 'V': - Cmd->Command[0]=UpperCmd; + // Also copy 't' and 'a' modifiers for -v[t,a], if present. + Cmd->Command.clear(); + for (char *c=Switch+1;*c!=0;c++) + Cmd->Command+=etoupper(*c); break; case '?': Cmd->OutHelp(RARX_SUCCESS); @@ -68,7 +72,7 @@ int main(int argc, char *argv[]) #if defined(_WIN_ALL) && !defined(SFX_MODULE) ShutdownOnClose=Cmd->Shutdown; - if (ShutdownOnClose) + if (ShutdownOnClose!=POWERMODE_KEEP) ShutdownCheckAnother(true); #endif @@ -78,7 +82,6 @@ int main(int argc, char *argv[]) Cmd->OutTitle(); Cmd->ProcessCommand(); - delete Cmd; } catch (RAR_EXIT ErrCode) { @@ -100,6 +103,7 @@ int main(int argc, char *argv[]) Shutdown(ShutdownOnClose); #endif ErrHandler.MainExit=true; + CloseLogOptions(); return ErrHandler.GetErrorCode(); } #endif diff --git a/unrar/rar.hpp b/unrar/rar.hpp index 3f7414c8..d31ecd86 100644 --- a/unrar/rar.hpp +++ b/unrar/rar.hpp @@ -12,10 +12,11 @@ #include "version.hpp" #include "rardefs.hpp" #include "rarlang.hpp" +#include "rawint.hpp" #include "unicode.hpp" #include "errhnd.hpp" #include "secpassword.hpp" -#include "array.hpp" +#include "strlist.hpp" #include "timefn.hpp" #include "sha1.hpp" #include "sha256.hpp" @@ -28,13 +29,14 @@ #include "headers.hpp" #include "pathfn.hpp" #include "strfn.hpp" -#include "strlist.hpp" #ifdef _WIN_ALL #include "isnt.hpp" #endif +#ifdef PROPAGATE_MOTW +#include "motw.hpp" +#endif #include "file.hpp" #include "crc.hpp" -#include "ui.hpp" #include "filefn.hpp" #include "filestr.hpp" #include "find.hpp" @@ -47,11 +49,11 @@ #include "archive.hpp" #include "match.hpp" #include "cmddata.hpp" +#include "ui.hpp" #include "filcreat.hpp" #include "consio.hpp" #include "system.hpp" #include "log.hpp" -#include "rawint.hpp" #include "rawread.hpp" #include "encname.hpp" #include "resource.hpp" @@ -62,6 +64,8 @@ #include "threadpool.hpp" +#include "largepage.hpp" + #include "unpack.hpp" @@ -85,9 +89,6 @@ #include "global.hpp" -#if 0 -#include "benchmark.hpp" -#endif diff --git a/unrar/rardefs.hpp b/unrar/rardefs.hpp index 095792a0..378ccc7f 100644 --- a/unrar/rardefs.hpp +++ b/unrar/rardefs.hpp @@ -9,17 +9,36 @@ #define ASIZE(x) (sizeof(x)/sizeof(x[0])) -// MAXPASSWORD is expected to be multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE (16) -// for CryptProtectMemory in SecPassword. -#define MAXPASSWORD 128 +// MAXPASSWORD and MAXPASSWORD_RAR are expected to be multiple of +// CRYPTPROTECTMEMORY_BLOCK_SIZE (16) for CryptProtectMemory in SecPassword. +// We allow a larger MAXPASSWORD to unpack archives with lengthy passwords +// in non-RAR formats in GUI versions. For RAR format we set MAXPASSWORD_RAR +// to 128 for compatibility and because it is enough for AES-256. +#define MAXPASSWORD 512 +#define MAXPASSWORD_RAR 128 -#define MAXSFXSIZE 0x200000 +// Set some arbitrary sensible limit to maximum path length to prevent +// the excessive memory allocation for dynamically allocated strings. +#define MAXPATHSIZE 0x10000 + +#define MAXSFXSIZE 0x400000 #define MAXCMTSIZE 0x40000 +#ifdef _WIN_32 +#define DefSFXName L"default32.sfx" +#else #define DefSFXName L"default.sfx" +#endif #define DefSortListName L"rarfiles.lst" +// Maximum dictionary allowed by compression. Can be less than +// maximum dictionary supported by decompression. +#define PACK_MAX_DICT 0x1000000000ULL // 64 GB. + +// Maximum dictionary allowed by decompression. +#define UNPACK_MAX_DICT 0x1000000000ULL // 64 GB. + #ifndef SFX_MODULE #define USE_QOPEN @@ -28,4 +47,8 @@ // Produce the value, which is equal or larger than 'v' and aligned to 'a'. #define ALIGN_VALUE(v,a) (size_t(v) + ( (~size_t(v) + 1) & (a - 1) ) ) +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +#define PROPAGATE_MOTW // Propagate the archive Mark of the Web. +#endif + #endif diff --git a/unrar/raros.hpp b/unrar/raros.hpp index 4f4f2ae7..b701637a 100644 --- a/unrar/raros.hpp +++ b/unrar/raros.hpp @@ -1,15 +1,6 @@ #ifndef _RAR_RAROS_ #define _RAR_RAROS_ -#ifdef __EMX__ - #define _EMX -#endif - -#ifdef __DJGPP__ - #define _DJGPP - #define _EMX -#endif - #if defined(__WIN32__) || defined(_WIN32) #define _WIN_ALL // Defined for all Windows platforms, 32 and 64 bit, mobile and desktop. #ifdef _M_X64 @@ -29,7 +20,7 @@ #define _APPLE #endif -#if !defined(_EMX) && !defined(_WIN_ALL) && !defined(_BEOS) && !defined(_APPLE) +#if !defined(_WIN_ALL) && !defined(_UNIX) #define _UNIX #endif diff --git a/unrar/rartypes.hpp b/unrar/rartypes.hpp index 3d3111bc..a612c345 100644 --- a/unrar/rartypes.hpp +++ b/unrar/rartypes.hpp @@ -5,7 +5,7 @@ typedef uint8_t byte; // Unsigned 8 bits. typedef uint16_t ushort; // Preferably 16 bits, but can be more. -typedef unsigned int uint; // 32 bits or more. +typedef unsigned int uint; // Preferably 32 bits, likely can be more. typedef uint32_t uint32; // 32 bits exactly. typedef int32_t int32; // Signed 32 bits exactly. typedef uint64_t uint64; // 64 bits exactly. diff --git a/unrar/rarvm.cpp b/unrar/rarvm.cpp index 8d8675a3..c44e0210 100644 --- a/unrar/rarvm.cpp +++ b/unrar/rarvm.cpp @@ -246,7 +246,7 @@ bool RarVM::ExecuteStandardFilter(VM_StandardFilters FilterType) } else Predicted=PrevByte; - DestData[I]=PrevByte=(byte)(Predicted-*(SrcData++)); + PrevByte=DestData[I]=(byte)(Predicted-*(SrcData++)); } } for (uint I=PosR,Border=DataSize-2;I>16); D[3]=(byte)(Field>>24); #else - *(uint32 *)Data=Field; + *(uint32 *)Data=(uint32)Field; #endif } @@ -84,21 +84,35 @@ inline uint32 RawGetBE4(const byte *m) { #if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) return _byteswap_ulong(*(uint32 *)m); -#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +#elif defined(USE_MEM_BYTESWAP) && (defined(__clang__) || defined(__GNUC__)) return __builtin_bswap32(*(uint32 *)m); #else - return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3]; + return uint32(m[0])<<24 | uint32(m[1])<<16 | uint32(m[2])<<8 | m[3]; +#endif +} + + +// Load 8 big endian bytes from memory and return uint64. +inline uint64 RawGetBE8(const byte *m) +{ +#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) + return _byteswap_uint64(*(uint64 *)m); +#elif defined(USE_MEM_BYTESWAP) && (defined(__clang__) || defined(__GNUC__)) + return __builtin_bswap64(*(uint64 *)m); +#else + return uint64(m[0])<<56 | uint64(m[1])<<48 | uint64(m[2])<<40 | uint64(m[3])<<32 | + uint64(m[4])<<24 | uint64(m[5])<<16 | uint64(m[6])<<8 | m[7]; #endif } // Save integer to memory as big endian. -inline void RawPutBE4(uint32 i,byte *mem) +inline void RawPutBE4(uint i,byte *mem) { #if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) - *(uint32*)mem = _byteswap_ulong(i); -#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) - *(uint32*)mem = __builtin_bswap32(i); + *(uint32*)mem = _byteswap_ulong((uint32)i); +#elif defined(USE_MEM_BYTESWAP) && (defined(__clang__) || defined(__GNUC__)) + *(uint32*)mem = __builtin_bswap32((uint32)i); #else mem[0]=byte(i>>24); mem[1]=byte(i>>16); @@ -108,15 +122,60 @@ inline void RawPutBE4(uint32 i,byte *mem) } +// Save integer to memory as big endian. +inline void RawPutBE8(uint64 i,byte *mem) +{ +#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) + *(uint64*)mem = _byteswap_uint64(i); +#elif defined(USE_MEM_BYTESWAP) && (defined(__clang__) || defined(__GNUC__)) + *(uint64*)mem = __builtin_bswap64(i); +#else + mem[0]=byte(i>>56); + mem[1]=byte(i>>48); + mem[2]=byte(i>>40); + mem[3]=byte(i>>32); + mem[4]=byte(i>>24); + mem[5]=byte(i>>16); + mem[6]=byte(i>>8); + mem[7]=byte(i); +#endif +} + + inline uint32 ByteSwap32(uint32 i) { #ifdef _MSC_VER return _byteswap_ulong(i); -#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +#elif defined(__clang__) || defined(__GNUC__) return __builtin_bswap32(i); #else return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF); #endif } + + + +inline bool IsPow2(uint64 n) // Check if 'n' is power of 2. +{ + return (n & (n-1))==0; +} + + +inline uint64 GetGreaterOrEqualPow2(uint64 n) +{ + uint64 p=1; + while (pRead(&Data[FullSize],AlignedReadSize); Crypt->DecryptBlock(&Data[FullSize],AlignedReadSize); DataSize+=ReadSize==0 ? 0:Size; @@ -55,7 +55,7 @@ size_t RawRead::Read(size_t Size) #endif if (Size!=0) { - Data.Add(Size); + Data.resize(Data.size()+Size); ReadSize=SrcFile->Read(&Data[DataSize],Size); DataSize+=ReadSize; } @@ -67,13 +67,24 @@ void RawRead::Read(byte *SrcData,size_t Size) { if (Size!=0) { - Data.Add(Size); + Data.resize(Data.size()+Size); memcpy(&Data[DataSize],SrcData,Size); DataSize+=Size; } } +// Move unread data to beginning of buffer and adjust buffer size. +void RawRead::Compact() +{ + if (ReadPos Data; + std::vector Data; File *SrcFile; size_t DataSize; size_t ReadPos; @@ -15,6 +15,7 @@ class RawRead void Reset(); size_t Read(size_t Size); void Read(byte *SrcData,size_t Size); + void Compact(); byte Get1(); ushort Get2(); uint Get4(); @@ -27,7 +28,7 @@ class RawRead uint GetCRC50(); byte* GetDataPtr() {return &Data[0];} size_t Size() {return DataSize;} - size_t PaddedSize() {return Data.Size()-DataSize;} + size_t PaddedSize() {return Data.size()-DataSize;} size_t DataLeft() {return DataSize-ReadPos;} size_t GetPos() {return ReadPos;} void SetPos(size_t Pos) {ReadPos=Pos;} diff --git a/unrar/rdwrfn.cpp b/unrar/rdwrfn.cpp index 4200de28..feb63044 100644 --- a/unrar/rdwrfn.cpp +++ b/unrar/rdwrfn.cpp @@ -16,6 +16,7 @@ void ComprDataIO::Init() UnpackFromMemory=false; UnpackToMemory=false; UnpPackedSize=0; + UnpPackedLeft=0; ShowProgress=true; TestMode=false; SkipUnpCRC=false; @@ -35,7 +36,9 @@ void ComprDataIO::Init() SubHead=NULL; SubHeadPos=NULL; CurrentCommand=0; - ProcessedArcSize=TotalArcSize=0; + ProcessedArcSize=0; + LastArcSize=0; + TotalArcSize=0; } @@ -75,10 +78,10 @@ int ComprDataIO::UnpRead(byte *Addr,size_t Count) } else { - size_t SizeToRead=((int64)Count>UnpPackedSize) ? (size_t)UnpPackedSize:Count; + size_t SizeToRead=((int64)Count>UnpPackedLeft) ? (size_t)UnpPackedLeft:Count; if (SizeToRead > 0) { - if (UnpVolume && Decryption && (int64)Count>UnpPackedSize) + if (UnpVolume && Decryption && (int64)Count>UnpPackedLeft) { // We need aligned blocks for decryption and we want "Keep broken // files" to work efficiently with missing encrypted volumes. @@ -109,7 +112,7 @@ int ComprDataIO::UnpRead(byte *Addr,size_t Count) ReadAddr+=ReadSize; Count-=ReadSize; #endif - UnpPackedSize-=ReadSize; + UnpPackedLeft-=ReadSize; // Do not ask for next volume if we read something from current volume. // If next volume is missing, we need to process all data from current @@ -118,7 +121,7 @@ int ComprDataIO::UnpRead(byte *Addr,size_t Count) // we ask for next volume also if we have non-aligned encryption block. // Since we adjust data size for decryption earlier above, // it does not hurt "Keep broken files" mode efficiency. - if (UnpVolume && UnpPackedSize == 0 && + if (UnpVolume && UnpPackedLeft == 0 && (ReadSize==0 || Decryption && (TotalRead & CRYPT_BLOCK_MASK) != 0) ) { #ifndef NOVOLUME @@ -134,7 +137,7 @@ int ComprDataIO::UnpRead(byte *Addr,size_t Count) } Archive *SrcArc=(Archive *)SrcFile; if (SrcArc!=NULL) - ShowUnpRead(SrcArc->CurBlockPos+CurUnpRead,UnpArcSize); + ShowUnpRead(SrcArc->NextBlockPos-UnpPackedSize+CurUnpRead,TotalArcSize); if (ReadSize!=-1) { ReadSize=TotalRead; @@ -148,18 +151,11 @@ int ComprDataIO::UnpRead(byte *Addr,size_t Count) } -#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64) -// Disable the run time stack check for unrar.dll, so we can manipulate -// with ProcessDataProc call type below. Run time check would intercept -// a wrong ESP before we restore it. -#pragma runtime_checks( "s", off ) -#endif - void ComprDataIO::UnpWrite(byte *Addr,size_t Count) { #ifdef RARDLL - RAROptions *Cmd=((Archive *)SrcFile)->GetRAROptions(); + CommandData *Cmd=((Archive *)SrcFile)->GetCommandData(); if (Cmd->DllOpMode!=RAR_SKIP) { if (Cmd->Callback!=NULL && @@ -167,28 +163,7 @@ void ComprDataIO::UnpWrite(byte *Addr,size_t Count) ErrHandler.Exit(RARX_USERBREAK); if (Cmd->ProcessDataProc!=NULL) { - // Here we preserve ESP value. It is necessary for those developers, - // who still define ProcessDataProc callback as "C" type function, - // even though in year 2001 we announced in unrar.dll whatsnew.txt - // that it will be PASCAL type (for compatibility with Visual Basic). -#if defined(_MSC_VER) -#ifndef _WIN_64 - __asm mov ebx,esp -#endif -#elif defined(_WIN_ALL) && defined(__BORLANDC__) - _EBX=_ESP; -#endif int RetCode=Cmd->ProcessDataProc(Addr,(int)Count); - - // Restore ESP after ProcessDataProc with wrongly defined calling - // convention broken it. -#if defined(_MSC_VER) -#ifndef _WIN_64 - __asm mov esp,ebx -#endif -#elif defined(_WIN_ALL) && defined(__BORLANDC__) - _ESP=_EBX; -#endif if (RetCode==0) ErrHandler.Exit(RARX_USERBREAK); } @@ -219,11 +194,6 @@ void ComprDataIO::UnpWrite(byte *Addr,size_t Count) Wait(); } -#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64) -// Restore the run time stack check for unrar.dll. -#pragma runtime_checks( "s", restore ) -#endif - @@ -233,15 +203,11 @@ void ComprDataIO::ShowUnpRead(int64 ArcPos,int64 ArcSize) { if (ShowProgress && SrcFile!=NULL) { - if (TotalArcSize!=0) - { - // important when processing several archives or multivolume archive - ArcSize=TotalArcSize; - ArcPos+=ProcessedArcSize; - } + // Important when processing several archives or multivolume archive. + ArcPos+=ProcessedArcSize; Archive *SrcArc=(Archive *)SrcFile; - RAROptions *Cmd=SrcArc->GetRAROptions(); + CommandData *Cmd=SrcArc->GetCommandData(); int CurPercent=ToPercent(ArcPos,ArcSize); if (!Cmd->DisablePercentage && CurPercent!=LastPercent) @@ -283,28 +249,28 @@ void ComprDataIO::GetUnpackedData(byte **Data,size_t *Size) } -void ComprDataIO::SetEncryption(bool Encrypt,CRYPT_METHOD Method, +// Return true if encryption or decryption mode is set correctly. +bool ComprDataIO::SetEncryption(bool Encrypt,CRYPT_METHOD Method, SecPassword *Password,const byte *Salt,const byte *InitV, uint Lg2Cnt,byte *HashKey,byte *PswCheck) { -#ifndef RAR_NOCRYPT +#ifdef RAR_NOCRYPT + return false; +#else if (Encrypt) + { Encryption=Crypt->SetCryptKeys(true,Method,Password,Salt,InitV,Lg2Cnt,HashKey,PswCheck); + return Encryption; + } else + { Decryption=Decrypt->SetCryptKeys(false,Method,Password,Salt,InitV,Lg2Cnt,HashKey,PswCheck); + return Decryption; + } #endif } -#if !defined(SFX_MODULE) && !defined(RAR_NOCRYPT) -void ComprDataIO::SetAV15Encryption() -{ - Decryption=true; - Decrypt->SetAV15Encryption(); -} -#endif - - #if !defined(SFX_MODULE) && !defined(RAR_NOCRYPT) void ComprDataIO::SetCmt13Encryption() { @@ -322,3 +288,42 @@ void ComprDataIO::SetUnpackToMemory(byte *Addr,size_t Size) //changed from uint UnpackToMemoryAddr=Addr; UnpackToMemorySize=Size; } + + +// Extraction progress is based on the position in archive and we adjust +// the total archives size here, so trailing blocks do not prevent progress +// reaching 100% at the end of extraction. Alternatively we could print "100%" +// after completing the entire archive extraction, but then we would need +// to take into account possible messages like the checksum error after +// last file percent progress. +void ComprDataIO::AdjustTotalArcSize(Archive *Arc) +{ + // If we know a position of QO or RR blocks, use them to adjust the total + // packed size to beginning of these blocks. Earlier we already calculated + // the total size based on entire archive sizes. We also set LastArcSize + // to start of first trailing block, to add it later to ProcessedArcSize. + uint64 ArcLength=Arc->IsSeekable() ? Arc->FileLength() : 0; + // QO is always preceding RR record. + // Also we check QO and RR to be less than archive length to prevent + // negative "ArcLength-LastArcSize" and possible signed integer overflow + // when calculating TotalArcSize. + if (Arc->MainHead.QOpenOffset>0 && Arc->MainHead.QOpenOffsetMainHead.QOpenOffset; + else + if (Arc->MainHead.RROffset>0 && Arc->MainHead.RROffsetMainHead.RROffset; + else + { + // If neither QO nor RR are found, exclude the approximate size of + // end of archive block. + // We select EndBlock to be larger than typical 8 bytes HEAD_ENDARC, + // but to not exceed the smallest 22 bytes HEAD_FILE with 1 byte file + // name, so we do not have two files with 100% at the end of archive. + const uint EndBlock=23; + + if (ArcLength>EndBlock) + LastArcSize=ArcLength-EndBlock; + } + + TotalArcSize-=ArcLength-LastArcSize; +} diff --git a/unrar/rdwrfn.hpp b/unrar/rdwrfn.hpp index ac4e1793..4fa9435e 100644 --- a/unrar/rdwrfn.hpp +++ b/unrar/rdwrfn.hpp @@ -1,14 +1,11 @@ #ifndef _RAR_DATAIO_ #define _RAR_DATAIO_ +class Archive; class CmdAdd; class Unpack; class ArcFileSearch; -#if 0 -// We use external i/o calls for Benchmark command. -#define COMPRDATAIO_EXTIO -#endif class ComprDataIO { @@ -29,6 +26,7 @@ class ComprDataIO byte *UnpWrAddr; int64 UnpPackedSize; + int64 UnpPackedLeft; bool ShowProgress; bool TestMode; @@ -62,25 +60,24 @@ class ComprDataIO void EnableShowProgress(bool Show) {ShowProgress=Show;} void GetUnpackedData(byte **Data,size_t *Size); size_t GetUnpackToMemorySizeLeft(void) { return UnpackToMemorySize; } - void SetPackedSizeToRead(int64 Size) {UnpPackedSize=Size;} + void SetPackedSizeToRead(int64 Size) {UnpPackedSize=UnpPackedLeft=Size;} void SetTestMode(bool Mode) {TestMode=Mode;} void SetSkipUnpCRC(bool Skip) {SkipUnpCRC=Skip;} void SetNoFileHeader(bool Mode) {NoFileHeader=Mode;} void SetFiles(File *SrcFile,File *DestFile); void SetCommand(CmdAdd *Cmd) {Command=Cmd;} void SetSubHeader(FileHeader *hd,int64 *Pos) {SubHead=hd;SubHeadPos=Pos;} - void SetEncryption(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password, + bool SetEncryption(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password, const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck); - void SetAV15Encryption(); void SetCmt13Encryption(); void SetUnpackToMemory(byte *Addr,size_t Size); //changed by me void SetCurrentCommand(wchar Cmd) {CurrentCommand=Cmd;} + void AdjustTotalArcSize(Archive *Arc); bool PackVolume; bool UnpVolume; bool NextVolumeMissing; - int64 UnpArcSize; int64 CurPackRead,CurPackWrite,CurUnpRead,CurUnpWrite; @@ -88,6 +85,9 @@ class ComprDataIO // Used to calculate the total operation progress. int64 ProcessedArcSize; + // Last extracted archive size up to QO or RR block. + int64 LastArcSize; + int64 TotalArcSize; DataHash PackedDataHash; // Packed write and unpack read hash. diff --git a/unrar/recvol.cpp b/unrar/recvol.cpp index adf58404..426d9cee 100644 --- a/unrar/recvol.cpp +++ b/unrar/recvol.cpp @@ -1,11 +1,12 @@ #include "rar.hpp" + #include "recvol3.cpp" #include "recvol5.cpp" -bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent) +bool RecVolumesRestore(CommandData *Cmd,const std::wstring &Name,bool Silent) { Archive Arc(Cmd); if (!Arc.Open(Name)) @@ -42,24 +43,20 @@ bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent) } -void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name) +void RecVolumesTest(CommandData *Cmd,Archive *Arc,const std::wstring &Name) { - wchar RevName[NM]; - *RevName=0; - if (Arc!=NULL) + std::wstring RevName; + if (Arc==NULL) + RevName=Name; + else { // We received .rar or .exe volume as a parameter, trying to find // the matching .rev file number 1. bool NewNumbering=Arc->NewNumbering; - wchar ArcName[NM]; - wcsncpyz(ArcName,Name,ASIZE(ArcName)); - - wchar *VolNumStart=VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),NewNumbering); - wchar RecVolMask[NM]; - wcsncpyz(RecVolMask,ArcName,ASIZE(RecVolMask)); - size_t BaseNamePartLength=VolNumStart-ArcName; - wcsncpyz(RecVolMask+BaseNamePartLength,L"*.rev",ASIZE(RecVolMask)-BaseNamePartLength); + std::wstring RecVolMask; + size_t VolNumStart=VolNameToFirstName(Name,RecVolMask,NewNumbering); + RecVolMask.replace(VolNumStart, std::wstring::npos, L"*.rev"); FindFile Find; Find.SetMask(RecVolMask); @@ -67,31 +64,30 @@ void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name) while (Find.Next(&RecData)) { - wchar *Num=GetVolNumPart(RecData.Name); - if (*Num!='1') // Name must have "0...01" numeric part. + size_t NumPos=GetVolNumPos(RecData.Name); + if (RecData.Name[NumPos]!='1') // Name must have "0...01" numeric part. continue; bool FirstVol=true; - while (--Num>=RecData.Name && IsDigit(*Num)) - if (*Num!='0') + while (NumPos>0 && IsDigit(RecData.Name[--NumPos])) + if (RecData.Name[NumPos]!='0') { FirstVol=false; break; } if (FirstVol) { - wcsncpyz(RevName,RecData.Name,ASIZE(RevName)); - Name=RevName; + RevName=RecData.Name; break; } } - if (*RevName==0) // First .rev file not found. + if (RevName.empty()) // First .rev file not found. return; } File RevFile; - if (!RevFile.Open(Name)) + if (!RevFile.Open(RevName)) { - ErrHandler.OpenErrorMsg(Name); // It also sets RARX_OPEN. + ErrHandler.OpenErrorMsg(RevName); // It also sets RARX_OPEN. return; } mprintf(L"\n"); @@ -101,11 +97,11 @@ void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name) if (Rev5) { RecVolumes5 RecVol(Cmd,true); - RecVol.Test(Cmd,Name); + RecVol.Test(Cmd,RevName); } else { RecVolumes3 RecVol(Cmd,true); - RecVol.Test(Cmd,Name); + RecVol.Test(Cmd,RevName); } } diff --git a/unrar/recvol.hpp b/unrar/recvol.hpp index 06510a21..159bd1ad 100644 --- a/unrar/recvol.hpp +++ b/unrar/recvol.hpp @@ -8,24 +8,24 @@ class RecVolumes3 { private: File *SrcFile[256]; - Array Buf; + std::vector Buf; #ifdef RAR_SMP ThreadPool *RSThreadPool; #endif public: - RecVolumes3(RAROptions *Cmd,bool TestOnly); + RecVolumes3(CommandData *Cmd,bool TestOnly); ~RecVolumes3(); - void Make(RAROptions *Cmd,wchar *ArcName); - bool Restore(RAROptions *Cmd,const wchar *Name,bool Silent); - void Test(RAROptions *Cmd,const wchar *Name); + void Make(CommandData *Cmd,std::wstring ArcName); + bool Restore(CommandData *Cmd,const std::wstring &Name,bool Silent); + void Test(CommandData *Cmd,const std::wstring &Name); }; struct RecVolItem { File *f; - wchar Name[NM]; + std::wstring Name; uint CRC; uint64 FileSize; bool New; // Newly created RAR volume. @@ -48,11 +48,11 @@ struct RecRSThreadData class RecVolumes5 { private: - void ProcessRS(RAROptions *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode); - void ProcessRS(RAROptions *Cmd,uint MaxRead,bool Encode); + void ProcessRS(CommandData *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode); + void ProcessRS(CommandData *Cmd,uint MaxRead,bool Encode); uint ReadHeader(File *RecFile,bool FirstRev); - Array RecItems; + std::vector RecItems; byte *RealReadBuffer; // Real pointer returned by 'new'. byte *ReadBuffer; // Pointer aligned for SSE instructions. @@ -76,13 +76,13 @@ class RecVolumes5 public: // 'public' only because called from thread functions. void ProcessAreaRS(RecRSThreadData *td); public: - RecVolumes5(RAROptions *Cmd,bool TestOnly); + RecVolumes5(CommandData *Cmd,bool TestOnly); ~RecVolumes5(); - bool Restore(RAROptions *Cmd,const wchar *Name,bool Silent); - void Test(RAROptions *Cmd,const wchar *Name); + bool Restore(CommandData *Cmd,const std::wstring &Name,bool Silent); + void Test(CommandData *Cmd,const std::wstring &Name); }; -bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent); -void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name); +bool RecVolumesRestore(CommandData *Cmd,const std::wstring &Name,bool Silent); +void RecVolumesTest(CommandData *Cmd,Archive *Arc,const std::wstring &Name); #endif diff --git a/unrar/recvol3.cpp b/unrar/recvol3.cpp index 9fb846a2..4199deed 100644 --- a/unrar/recvol3.cpp +++ b/unrar/recvol3.cpp @@ -23,12 +23,6 @@ class RSEncode // Encode or decode data area, one object per one thread. #ifdef RAR_SMP -THREAD_PROC(RSEncodeThread) -{ - RSEncode *rs=(RSEncode *)Data; - rs->EncodeBuf(); -} - THREAD_PROC(RSDecodeThread) { RSEncode *rs=(RSEncode *)Data; @@ -36,7 +30,7 @@ THREAD_PROC(RSDecodeThread) } #endif -RecVolumes3::RecVolumes3(RAROptions *Cmd,bool TestOnly) +RecVolumes3::RecVolumes3(CommandData *Cmd,bool TestOnly) { memset(SrcFile,0,sizeof(SrcFile)); if (TestOnly) @@ -47,8 +41,7 @@ RecVolumes3::RecVolumes3(RAROptions *Cmd,bool TestOnly) } else { - Buf.Alloc(TotalBufferSize); - memset(SrcFile,0,sizeof(SrcFile)); + Buf.resize(TotalBufferSize); #ifdef RAR_SMP RSThreadPool=new ThreadPool(Cmd->Threads); #endif @@ -68,30 +61,16 @@ RecVolumes3::~RecVolumes3() -void RSEncode::EncodeBuf() -{ - for (int BufPos=BufStart;BufPosName;Ext--) - if (!IsDigit(*Ext)) - if (*Ext=='_' && IsDigit(*(Ext-1))) + for (ExtPos--;ExtPos>0;ExtPos--) + if (!IsDigit(Name[ExtPos])) + if (Name[ExtPos]=='_' && IsDigit(Name[ExtPos-1])) DigitGroup++; else break; @@ -99,19 +78,19 @@ static bool IsNewStyleRev(const wchar *Name) } -bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) +bool RecVolumes3::Restore(CommandData *Cmd,const std::wstring &Name,bool Silent) { - wchar ArcName[NM]; - wcsncpyz(ArcName,Name,ASIZE(ArcName)); - wchar *Ext=GetExt(ArcName); + std::wstring ArcName=Name; bool NewStyle=false; // New style .rev volumes are supported since RAR 3.10. - bool RevName=Ext!=NULL && wcsicomp(Ext,L".rev")==0; + bool RevName=CmpExt(ArcName,L"rev"); if (RevName) { NewStyle=IsNewStyleRev(ArcName); - while (Ext>ArcName+1 && (IsDigit(*(Ext-1)) || *(Ext-1)=='_')) - Ext--; - wcsncpyz(Ext,L"*.*",ASIZE(ArcName)-(Ext-ArcName)); + + size_t ExtPos=GetExtPos(ArcName); + while (ExtPos>1 && (IsDigit(ArcName[ExtPos-1]) || ArcName[ExtPos-1]=='_')) + ExtPos--; + ArcName.replace(ExtPos,std::wstring::npos,L"*.*"); FindFile Find; Find.SetMask(ArcName); @@ -121,7 +100,7 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) Archive Arc(Cmd); if (Arc.WOpen(fd.Name) && Arc.IsArchive(true)) { - wcsncpyz(ArcName,fd.Name,ASIZE(ArcName)); + ArcName=fd.Name; break; } } @@ -138,11 +117,10 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) bool NewNumbering=Arc.NewNumbering; Arc.Close(); - wchar *VolNumStart=VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),NewNumbering); - wchar RecVolMask[NM]; - wcsncpyz(RecVolMask,ArcName,ASIZE(RecVolMask)); - size_t BaseNamePartLength=VolNumStart-ArcName; - wcsncpyz(RecVolMask+BaseNamePartLength,L"*.rev",ASIZE(RecVolMask)-BaseNamePartLength); + size_t VolNumStart=VolNameToFirstName(ArcName,ArcName,NewNumbering); + std::wstring RecVolMask=ArcName; + RecVolMask.replace(VolNumStart,std::wstring::npos,L"*.rev"); + size_t BaseNamePartLength=VolNumStart; int64 RecFileSize=0; @@ -155,25 +133,25 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) Find.SetMask(RecVolMask); FindData RecData; int FileNumber=0,RecVolNumber=0,FoundRecVolumes=0,MissingVolumes=0; - wchar PrevName[NM]; + std::wstring PrevName; while (Find.Next(&RecData)) { - wchar *CurName=RecData.Name; + std::wstring CurName=RecData.Name; int P[3]; if (!RevName && !NewStyle) { NewStyle=true; - wchar *Dot=GetExt(CurName); - if (Dot!=NULL) + size_t DotPos=GetExtPos(CurName); + if (DotPos!=std::wstring::npos) { - int LineCount=0; - Dot--; - while (Dot>CurName && *Dot!='.') + uint LineCount=0; + DotPos--; + while (DotPos>0 && CurName[DotPos]!='.') { - if (*Dot=='_') + if (CurName[DotPos]=='_') LineCount++; - Dot--; + DotPos--; } if (LineCount==2) NewStyle=false; @@ -209,24 +187,24 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) } else { - wchar *Dot=GetExt(CurName); - if (Dot==NULL) + size_t DotPos=GetExtPos(CurName); + if (DotPos==std::wstring::npos) continue; bool WrongParam=false; for (size_t I=0;I=CurName+BaseNamePartLength); - P[I]=atoiw(Dot+1); + DotPos--; + } while (IsDigit(CurName[DotPos]) && DotPos>=BaseNamePartLength); + P[I]=atoiw(&CurName[DotPos+1]); if (P[I]==0 || P[I]>255) WrongParam=true; } if (WrongParam) continue; } - if (P[1]+P[2]>255) + if (P[0]<=0 || P[1]<=0 || P[2]<=0 || P[1]+P[2]>255 || P[0]+P[2]-1>255) continue; if (RecVolNumber!=0 && RecVolNumber!=P[1] || FileNumber!=0 && FileNumber!=P[2]) { @@ -235,10 +213,17 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) } RecVolNumber=P[1]; FileNumber=P[2]; - wcsncpyz(PrevName,CurName,ASIZE(PrevName)); + PrevName=CurName; File *NewFile=new File; NewFile->TOpen(CurName); - SrcFile[FileNumber+P[0]-1]=NewFile; + + // This check is redundant taking into account P[I]>255 and P[0]+P[2]-1>255 + // checks above. Still we keep it here for better clarity and security. + int SrcPos=FileNumber+P[0]-1; + if (SrcPos<0 || SrcPos>=ASIZE(SrcFile)) + continue; + SrcFile[SrcPos]=NewFile; + FoundRecVolumes++; if (RecFileSize==0) @@ -249,11 +234,9 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) if (FoundRecVolumes==0) return false; - bool WriteFlags[256]; - memset(WriteFlags,0,sizeof(WriteFlags)); + bool WriteFlags[256]{}; - wchar LastVolName[NM]; - *LastVolName=0; + std::wstring LastVolName; for (int CurArcNum=0;CurArcNumClose(); - wchar NewName[NM]; - wcsncpyz(NewName,ArcName,ASIZE(NewName)); - wcsncatz(NewName,L".bad",ASIZE(NewName)); + std::wstring NewName=ArcName+L".bad"; uiMsg(UIMSG_BADARCHIVE,ArcName); uiMsg(UIMSG_RENAMING,ArcName,NewName); @@ -322,13 +303,13 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) MissingVolumes++; if (CurArcNum==FileNumber-1) - wcsncpyz(LastVolName,ArcName,ASIZE(LastVolName)); + LastVolName=ArcName; uiMsg(UIMSG_MISSINGVOL,ArcName); uiMsg(UIEVENT_NEWARCHIVE,ArcName); } SrcFile[CurArcNum]=(File*)NewFile; - NextVolumeName(ArcName,ASIZE(ArcName),!NewNumbering); + NextVolumeName(ArcName,!NewNumbering); } uiMsg(UIMSG_RECVOLMISSING,MissingVolumes); @@ -453,7 +434,7 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) CurFile->Close(); SrcFile[I]=NULL; } - if (*LastVolName!=0) + if (!LastVolName.empty()) { // Truncate the last volume to its real size. Archive Arc(Cmd); @@ -497,7 +478,7 @@ void RSEncode::DecodeBuf() } -void RecVolumes3::Test(RAROptions *Cmd,const wchar *Name) +void RecVolumes3::Test(CommandData *Cmd,const std::wstring &Name) { if (!IsNewStyleRev(Name)) // RAR 3.0 name#_#_#.rev do not include CRC32. { @@ -505,8 +486,7 @@ void RecVolumes3::Test(RAROptions *Cmd,const wchar *Name) return; } - wchar VolName[NM]; - wcsncpyz(VolName,Name,ASIZE(VolName)); + std::wstring VolName=Name; while (FileExist(VolName)) { @@ -518,7 +498,7 @@ void RecVolumes3::Test(RAROptions *Cmd,const wchar *Name) } if (!uiStartFileExtract(VolName,false,true,false)) return; - mprintf(St(MExtrTestFile),VolName); + mprintf(St(MExtrTestFile),VolName.c_str()); mprintf(L" "); CurFile.Seek(0,SEEK_END); int64 Length=CurFile.Tell(); @@ -539,6 +519,6 @@ void RecVolumes3::Test(RAROptions *Cmd,const wchar *Name) ErrHandler.SetErrorCode(RARX_CRC); } - NextVolumeName(VolName,ASIZE(VolName),false); + NextVolumeName(VolName,false); } } diff --git a/unrar/recvol5.cpp b/unrar/recvol5.cpp index 3c524d8e..e094b171 100644 --- a/unrar/recvol5.cpp +++ b/unrar/recvol5.cpp @@ -1,6 +1,10 @@ static const uint MaxVolumes=65535; -RecVolumes5::RecVolumes5(RAROptions *Cmd,bool TestOnly) +// We select this limit arbitrarily, to prevent user creating too many +// rev files by mistake. +#define MAX_REV_TO_DATA_RATIO 10 // 1000% of rev files. + +RecVolumes5::RecVolumes5(CommandData *Cmd,bool TestOnly) { RealBuf=NULL; RealReadBuffer=NULL; @@ -44,8 +48,8 @@ RecVolumes5::~RecVolumes5() { delete[] RealBuf; delete[] RealReadBuffer; - for (uint I=0;IArcName && IsDigit(*(Num-1))) - Num--; - if (Num==ArcName) + size_t NumPos=GetVolNumPos(ArcName); + while (NumPos>0 && IsDigit(ArcName[NumPos-1])) + NumPos--; + if (NumPos<=GetNamePos(ArcName)) return false; // Numeric part is missing or entire volume name is numeric, not possible for RAR or REV volume. - wcsncpyz(Num,L"*.*",ASIZE(ArcName)-(Num-ArcName)); + ArcName.replace(NumPos,std::wstring::npos,L"*.*"); - wchar FirstVolName[NM]; - *FirstVolName=0; + std::wstring FirstVolName; + std::wstring LongestRevName; int64 RecFileSize=0; @@ -164,7 +167,7 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) Archive *Vol=new Archive(Cmd); int ItemPos=-1; - if (Vol->WOpen(fd.Name)) + if (!fd.IsDir && Vol->WOpen(fd.Name)) { if (CmpExt(fd.Name,L"rev")) { @@ -176,6 +179,9 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) ItemPos=RecNum; FoundRecVolumes++; + + if (fd.Name.size()>LongestRevName.size()) + LongestRevName=fd.Name; } } else @@ -194,35 +200,35 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) // RAR volume found. Get its number, store the handle in appropriate // array slot, clean slots in between if we had to grow the array. - wchar *Num=GetVolNumPart(fd.Name); + size_t NumPos=GetVolNumPos(fd.Name); uint VolNum=0; - for (uint K=1;Num>=fd.Name && IsDigit(*Num);K*=10,Num--) - VolNum+=(*Num-'0')*K; + for (uint K=1;(int)NumPos>=0 && IsDigit(fd.Name[NumPos]);K*=10,NumPos--) + VolNum+=(fd.Name[NumPos]-'0')*K; if (VolNum==0 || VolNum>MaxVolumes) continue; - size_t CurSize=RecItems.Size(); + size_t CurSize=RecItems.size(); if (VolNum>CurSize) { - RecItems.Alloc(VolNum); - for (size_t I=CurSize;If=Vol; Item->New=false; - wcsncpyz(Item->Name,fd.Name,ASIZE(Item->Name)); + Item->Name=fd.Name; } } @@ -231,6 +237,15 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) if (FoundRecVolumes==0) return false; + // If we did not find even a single .rar volume, create .rar volume name + // based on the longest .rev file name. Use longest .rev, so we have + // enough space for volume number. + if (FirstVolName.empty()) + { + SetExt(LongestRevName,L"rar"); + VolNameToFirstName(LongestRevName,FirstVolName,true); + } + uiMsg(UIMSG_RECVOLCALCCHECKSUM); MissingVolumes=0; @@ -290,9 +305,8 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) { Item->f->Close(); - wchar NewName[NM]; - wcsncpyz(NewName,Item->Name,ASIZE(NewName)); - wcsncatz(NewName,L".bad",ASIZE(NewName)); + std::wstring NewName; + NewName=Item->Name+L".bad"; uiMsg(UIMSG_BADARCHIVE,Item->Name); uiMsg(UIMSG_RENAMING,Item->Name,NewName); @@ -301,14 +315,14 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) Item->f=NULL; } - if ((Item->New=(Item->f==NULL))) // Additional parentheses to avoid GCC warning. + if ((Item->New=(Item->f==NULL))==true) { - wcsncpyz(Item->Name,FirstVolName,ASIZE(Item->Name)); + Item->Name=FirstVolName; uiMsg(UIMSG_CREATING,Item->Name); uiMsg(UIEVENT_NEWARCHIVE,Item->Name); File *NewVol=new File; bool UserReject; - if (!FileCreate(Cmd,NewVol,Item->Name,ASIZE(Item->Name),&UserReject)) + if (!FileCreate(Cmd,NewVol,Item->Name,&UserReject)) { if (!UserReject) ErrHandler.CreateErrorMsg(Item->Name); @@ -316,9 +330,8 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) } NewVol->Prealloc(Item->FileSize); Item->f=NewVol; - Item->New=true; } - NextVolumeName(FirstVolName,ASIZE(FirstVolName),false); + NextVolumeName(FirstVolName,false); } @@ -346,13 +359,11 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) RecBufferSize&=~(SSE_ALIGNMENT-1); // Align for SSE. #endif - uint *Data=new uint[TotalCount]; - RSCoder16 RS; if (!RS.Init(DataCount,RecCount,ValidFlags)) { + uiMsg(UIERROR_OPFAILED); delete[] ValidFlags; - delete[] Data; return false; // Should not happen, we check parameter validity above. } @@ -373,7 +384,7 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) J++; VolNum=J++; // Use next valid REV volume data instead of RAR. } - RecVolItem *Item=RecItems+VolNum; + RecVolItem *Item=&RecItems[VolNum]; byte *B=&ReadBuf[0]; int ReadSize=0; @@ -395,7 +406,7 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) for (uint I=0,J=0;IFileSize); Item->f->Write(Buf+(J++)*RecBufferSize,WriteSize); Item->FileSize-=WriteSize; @@ -415,7 +426,6 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) RecItems[I].f->Close(); delete[] ValidFlags; - delete[] Data; #if !defined(SILENT) if (!Cmd->DisablePercentage) mprintf(L"\b\b\b\b100%%"); @@ -462,10 +472,10 @@ uint RecVolumes5::ReadHeader(File *RecFile,bool FirstRev) { // If we have read the first valid REV file, init data structures // using information from REV header. - size_t CurSize=RecItems.Size(); - RecItems.Alloc(TotalCount); - for (size_t I=CurSize;I #endif -static byte S[256],S5[256],rcon[30]; +static byte S[256]= +{ + 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, + 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, + 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, + 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, + 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, + 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, + 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, + 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, + 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, + 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, + 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 +}; + +static byte S5[256]; + +// Round constants. 10 items are used by AES-128, 8 by AES-192, 7 by AES-256. +static byte rcon[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36}; + static byte T1[256][4],T2[256][4],T3[256][4],T4[256][4]; static byte T5[256][4],T6[256][4],T7[256][4],T8[256][4]; static byte U1[256][4],U2[256][4],U3[256][4],U4[256][4]; - inline void Xor128(void *dest,const void *arg1,const void *arg2) { #ifdef ALLOW_MISALIGNED @@ -63,26 +82,53 @@ inline void Copy128(byte *dest,const byte *src) Rijndael::Rijndael() { - if (S[0]==0) + if (S5[0]==0) GenerateTables(); + m_uRounds = 0; CBCMode = true; // Always true for RAR. +#ifdef USE_SSE + AES_NI=false; +#endif +#ifdef USE_NEON_AES + AES_Neon=false; +#endif } void Rijndael::Init(bool Encrypt,const byte *key,uint keyLen,const byte * initVector) { -#ifdef USE_SSE - // Check SSE here instead of constructor, so if object is a part of some - // structure memset'ed before use, this variable is not lost. + // Check SIMD here instead of constructor, so if object is a part of some + // structure memset'ed before use, these variables are not lost. +#if defined(USE_SSE) + +#ifdef _MSC_VER int CPUInfo[4]; - __cpuid(CPUInfo, 0x80000000); // Get the maximum supported cpuid function. - if ((CPUInfo[0] & 0x7fffffff)>=1) + __cpuid(CPUInfo, 0); + if (CPUInfo[0]>=1) // Check the maximum supported cpuid function. { __cpuid(CPUInfo, 1); AES_NI=(CPUInfo[2] & 0x2000000)!=0; } else - AES_NI=0; + AES_NI=false; +#elif defined(__GNUC__) + AES_NI=__builtin_cpu_supports("aes"); +#endif + +#elif defined(USE_NEON_AES) + #ifdef _APPLE + // getauxval isn't available in OS X + uint Value=0; + size_t Size=sizeof(Value); + int RetCode=sysctlbyname("hw.optional.arm.FEAT_AES",&Value,&Size,NULL,0); + + // We treat sysctlbyname failure with -1 return code as AES presence, + // because "hw.optional.arm.FEAT_AES" was missing in OS X 11, but AES + // still was supported by Neon. + AES_Neon=RetCode!=0 || Value!=0; + #else + AES_Neon=(getauxval(AT_HWCAP) & HWCAP_AES)!=0; + #endif #endif // Other developers asked us to initialize it to suppress "may be used @@ -122,18 +168,25 @@ void Rijndael::Init(bool Encrypt,const byte *key,uint keyLen,const byte * initVe keyEncToDec(); } + void Rijndael::blockEncrypt(const byte *input,size_t inputLen,byte *outBuffer) { if (inputLen <= 0) return; size_t numBlocks = inputLen/16; -#ifdef USE_SSE +#if defined(USE_SSE) if (AES_NI) { blockEncryptSSE(input,numBlocks,outBuffer); return; } +#elif defined(USE_NEON_AES) + if (AES_Neon) + { + blockEncryptNeon(input,numBlocks,outBuffer); + return; + } #endif byte *prevBlock = m_initVector; @@ -220,6 +273,40 @@ void Rijndael::blockEncryptSSE(const byte *input,size_t numBlocks,byte *outBuffe } #endif + +#ifdef USE_NEON_AES +void Rijndael::blockEncryptNeon(const byte *input,size_t numBlocks,byte *outBuffer) +{ + byte *prevBlock = m_initVector; + while (numBlocks > 0) + { + byte block[16]; + if (CBCMode) + vst1q_u8(block, veorq_u8(vld1q_u8(prevBlock), vld1q_u8(input))); + else + vst1q_u8(block, vld1q_u8(input)); + + uint8x16_t data = vld1q_u8(block); + for (uint i = 0; i < m_uRounds-1; i++) + { + data = vaeseq_u8(data, vld1q_u8((byte *)m_expandedKey[i])); + data = vaesmcq_u8(data); + } + data = vaeseq_u8(data, vld1q_u8((byte *)(m_expandedKey[m_uRounds-1]))); + data = veorq_u8(data, vld1q_u8((byte *)(m_expandedKey[m_uRounds]))); + vst1q_u8(outBuffer, data); + + prevBlock=outBuffer; + + outBuffer += 16; + input += 16; + numBlocks--; + } + vst1q_u8(m_initVector, vld1q_u8(prevBlock)); + return; +} +#endif + void Rijndael::blockDecrypt(const byte *input, size_t inputLen, byte *outBuffer) { @@ -227,12 +314,18 @@ void Rijndael::blockDecrypt(const byte *input, size_t inputLen, byte *outBuffer) return; size_t numBlocks=inputLen/16; -#ifdef USE_SSE +#if defined(USE_SSE) if (AES_NI) { blockDecryptSSE(input,numBlocks,outBuffer); return; } +#elif defined(USE_NEON_AES) + if (AES_Neon) + { + blockDecryptNeon(input,numBlocks,outBuffer); + return; + } #endif byte block[16], iv[4][4]; @@ -324,6 +417,41 @@ void Rijndael::blockDecryptSSE(const byte *input, size_t numBlocks, byte *outBuf #endif +#ifdef USE_NEON_AES +void Rijndael::blockDecryptNeon(const byte *input, size_t numBlocks, byte *outBuffer) +{ + byte iv[16]; + memcpy(iv,m_initVector,16); + + while (numBlocks > 0) + { + uint8x16_t data = vld1q_u8(input); + + for (int i=m_uRounds-1; i>0; i--) + { + data = vaesdq_u8(data, vld1q_u8((byte *)m_expandedKey[i+1])); + data = vaesimcq_u8(data); + } + + data = vaesdq_u8(data, vld1q_u8((byte *)m_expandedKey[1])); + data = veorq_u8(data, vld1q_u8((byte *)m_expandedKey[0])); + + if (CBCMode) + data = veorq_u8(data, vld1q_u8(iv)); + + vst1q_u8(iv, vld1q_u8(input)); + vst1q_u8(outBuffer, data); + + input += 16; + outBuffer += 16; + numBlocks--; + } + + memcpy(m_initVector,iv,16); +} +#endif + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ALGORITHM ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -416,51 +544,40 @@ void Rijndael::keyEncToDec() } -#define ff_poly 0x011b -#define ff_hi 0x80 - -#define FFinv(x) ((x) ? pow[255 - log[x]]: 0) - -#define FFmul02(x) (x ? pow[log[x] + 0x19] : 0) -#define FFmul03(x) (x ? pow[log[x] + 0x01] : 0) -#define FFmul09(x) (x ? pow[log[x] + 0xc7] : 0) -#define FFmul0b(x) (x ? pow[log[x] + 0x68] : 0) -#define FFmul0d(x) (x ? pow[log[x] + 0xee] : 0) -#define FFmul0e(x) (x ? pow[log[x] + 0xdf] : 0) -#define fwd_affine(x) \ - (w = (uint)x, w ^= (w<<1)^(w<<2)^(w<<3)^(w<<4), (byte)(0x63^(w^(w>>8)))) +static byte gmul(byte a, byte b) // Galois field "peasant's algorithm" multiplication. +{ + const byte poly=0x1b; // Lower byte of AES 0x11b irreducible polynomial. + byte result = 0; + while (b>0) + { + if ((b & 1) != 0) + result ^= a; + a = (a & 0x80) ? (a<<1)^poly : a<<1; + b >>= 1; + } + return result; +} -#define inv_affine(x) \ - (w = (uint)x, w = (w<<1)^(w<<3)^(w<<6), (byte)(0x05^(w^(w>>8)))) +// 2021-09-24: changed to slower and simpler code without interim tables. +// It is still fast enough for our purpose. void Rijndael::GenerateTables() { - unsigned char pow[512],log[256]; - int i = 0, w = 1; - do - { - pow[i] = (byte)w; - pow[i + 255] = (byte)w; - log[w] = (byte)i++; - w ^= (w << 1) ^ (w & ff_hi ? ff_poly : 0); - } while (w != 1); - - for (int i = 0,w = 1; i < sizeof(rcon)/sizeof(rcon[0]); i++) - { - rcon[i] = w; - w = (w << 1) ^ (w & ff_hi ? ff_poly : 0); - } - for(int i = 0; i < 256; ++i) + for (int I=0;I<256;I++) + S5[S[I]]=I; + + for (int I=0;I<256;I++) { - unsigned char b=S[i]=fwd_affine(FFinv((byte)i)); - T1[i][1]=T1[i][2]=T2[i][2]=T2[i][3]=T3[i][0]=T3[i][3]=T4[i][0]=T4[i][1]=b; - T1[i][0]=T2[i][1]=T3[i][2]=T4[i][3]=FFmul02(b); - T1[i][3]=T2[i][0]=T3[i][1]=T4[i][2]=FFmul03(b); - S5[i] = b = FFinv(inv_affine((byte)i)); - U1[b][3]=U2[b][0]=U3[b][1]=U4[b][2]=T5[i][3]=T6[i][0]=T7[i][1]=T8[i][2]=FFmul0b(b); - U1[b][1]=U2[b][2]=U3[b][3]=U4[b][0]=T5[i][1]=T6[i][2]=T7[i][3]=T8[i][0]=FFmul09(b); - U1[b][2]=U2[b][3]=U3[b][0]=U4[b][1]=T5[i][2]=T6[i][3]=T7[i][0]=T8[i][1]=FFmul0d(b); - U1[b][0]=U2[b][1]=U3[b][2]=U4[b][3]=T5[i][0]=T6[i][1]=T7[i][2]=T8[i][3]=FFmul0e(b); + byte s=S[I]; + T1[I][1]=T1[I][2]=T2[I][2]=T2[I][3]=T3[I][0]=T3[I][3]=T4[I][0]=T4[I][1]=s; + T1[I][0]=T2[I][1]=T3[I][2]=T4[I][3]=gmul(s,2); + T1[I][3]=T2[I][0]=T3[I][1]=T4[I][2]=gmul(s,3); + + byte b=S5[I]; + U1[b][3]=U2[b][0]=U3[b][1]=U4[b][2]=T5[I][3]=T6[I][0]=T7[I][1]=T8[I][2]=gmul(b,0xb); + U1[b][1]=U2[b][2]=U3[b][3]=U4[b][0]=T5[I][1]=T6[I][2]=T7[I][3]=T8[I][0]=gmul(b,0x9); + U1[b][2]=U2[b][3]=U3[b][0]=U4[b][1]=T5[I][2]=T6[I][3]=T7[I][0]=T8[I][1]=gmul(b,0xd); + U1[b][0]=U2[b][1]=U3[b][2]=U4[b][3]=T5[I][0]=T6[I][1]=T7[I][2]=T8[I][3]=gmul(b,0xe); } } @@ -493,16 +610,16 @@ void TestRijndael() for (uint L=0;L<3;L++) { byte Out[16]; - wchar Str[sizeof(Out)*2+1]; + std::wstring Str; uint KeyLength=128+L*64; rij.Init(true,Key[L],KeyLength,IV); for (uint I=0;I ValidECC || NE == 0 || ValidECC == 0) return false; } - if (ND + NR > gfSize || NR > ND || ND == 0 || NR == 0) + + // 2021.09.01 - we allowed RR and REV >100%, so no more NR > ND check. + if (ND + NR > gfSize || /*NR > ND ||*/ ND == 0 || NR == 0) return false; delete[] MX; @@ -144,7 +146,7 @@ void RSCoder16::MakeDecoderMatrix() } -// Apply GaussJordan elimination to find inverse of decoder matrix. +// Apply Gauss-Jordan elimination to find inverse of decoder matrix. // We have the square NDxND matrix, but we do not store its trivial // diagonal "1" rows matching valid data, so we work with NExND matrix. // Our original Cauchy matrix does not contain 0, so we skip search @@ -331,14 +333,14 @@ bool RSCoder16::SSE_UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte for (uint I=0;I<16;I++) { - ((byte *)&T0L)[I]=gfMul(I,M); - ((byte *)&T0H)[I]=gfMul(I,M)>>8; - ((byte *)&T1L)[I]=gfMul(I<<4,M); - ((byte *)&T1H)[I]=gfMul(I<<4,M)>>8; - ((byte *)&T2L)[I]=gfMul(I<<8,M); - ((byte *)&T2H)[I]=gfMul(I<<8,M)>>8; - ((byte *)&T3L)[I]=gfMul(I<<12,M); - ((byte *)&T3H)[I]=gfMul(I<<12,M)>>8; + ((byte *)&T0L)[I]=byte(gfMul(I,M)); + ((byte *)&T0H)[I]=byte(gfMul(I,M)>>8); + ((byte *)&T1L)[I]=byte(gfMul(I<<4,M)); + ((byte *)&T1H)[I]=byte(gfMul(I<<4,M)>>8); + ((byte *)&T2L)[I]=byte(gfMul(I<<8,M)); + ((byte *)&T2H)[I]=byte(gfMul(I<<8,M)>>8); + ((byte *)&T3L)[I]=byte(gfMul(I<<12,M)); + ((byte *)&T3H)[I]=byte(gfMul(I<<12,M)>>8); } size_t Pos=0; diff --git a/unrar/rs16.hpp b/unrar/rs16.hpp index b67a7ca8..3833313c 100644 --- a/unrar/rs16.hpp +++ b/unrar/rs16.hpp @@ -17,6 +17,9 @@ class RSCoder16 void InvertDecoderMatrix(); #ifdef USE_SSE +#if defined(USE_SSE) && defined(__GNUC__) + __attribute__((target("ssse3"))) +#endif bool SSE_UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte *ECC, size_t BlockSize); #endif diff --git a/unrar/savepos.hpp b/unrar/savepos.hpp deleted file mode 100644 index 1f8353f6..00000000 --- a/unrar/savepos.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _RAR_SAVEPOS_ -#define _RAR_SAVEPOS_ - -class SaveFilePos -{ - private: - File *SaveFile; - int64 SavePos; - public: - SaveFilePos(File &Src) - { - SaveFile=&Src; - SavePos=Src.Tell(); - } - ~SaveFilePos() - { - // Unless the file is already closed either by current exception - // processing or intentionally by external code. - if (SaveFile->IsOpened()) - { - try - { - SaveFile->Seek(SavePos,SEEK_SET); - } - catch(RAR_EXIT) - { - // Seek() can throw an exception and it terminates process - // if we are already processing another exception. Also in C++ 11 - // an exception in destructor always terminates process unless - // we mark destructor with noexcept(false). So we do not want to - // throw here. To prevent data loss we do not want to continue - // execution after seek error, so we close the file. - // Any next access to this file will return an error. - SaveFile->Close(); - } - } - } -}; - -#endif diff --git a/unrar/scantree.cpp b/unrar/scantree.cpp index a13a3ebc..945a083a 100644 --- a/unrar/scantree.cpp +++ b/unrar/scantree.cpp @@ -10,12 +10,11 @@ ScanTree::ScanTree(StringList *FileMasks,RECURSE_MODE Recurse,bool GetLinks,SCAN ScanEntireDisk=false; FolderWildcards=false; + FindStack.push_back(NULL); // We need a single NULL pointer for initial Depth==0. + SetAllMaskDepth=0; - *CurMask=0; - memset(FindStack,0,sizeof(FindStack)); Depth=0; Errors=0; - *ErrArcName=0; Cmd=NULL; ErrDirList=NULL; ErrDirSpecPathLength=NULL; @@ -42,7 +41,7 @@ SCAN_CODE ScanTree::GetNext(FindData *FD) SCAN_CODE FindCode; while (1) { - if (*CurMask==0 && !GetNextMask()) + if (CurMask.empty() && !GetNextMask()) return SCAN_DONE; #ifndef SILENT @@ -79,7 +78,7 @@ bool ScanTree::ExpandFolderMask() { bool WildcardFound=false; uint SlashPos=0; - for (int I=0;CurMask[I]!=0;I++) + for (uint I=0;I0 && ExpandedFolderList.GetString(CurMask,ASIZE(CurMask))) + if (ExpandedFolderList.ItemsCount()>0 && ExpandedFolderList.GetString(CurMask)) return true; FolderWildcards=false; FilterList.Reset(); - if (!FileMasks->GetString(CurMask,ASIZE(CurMask))) + if (!FileMasks->GetString(CurMask)) return false; // Check if folder wildcards present. @@ -144,10 +141,10 @@ bool ScanTree::GetFilteredMask() uint SlashPos=0; uint StartPos=0; #ifdef _WIN_ALL // Not treat the special NTFS \\?\d: path prefix as a wildcard. - if (CurMask[0]=='\\' && CurMask[1]=='\\' && CurMask[2]=='?' && CurMask[3]=='\\') + if (starts_with(CurMask,L"\\\\?\\")) StartPos=4; #endif - for (uint I=StartPos;CurMask[I]!=0;I++) + for (uint I=StartPos;I2 && CurMask[0]==CPATHDIVIDER && CurMask[1]==CPATHDIVIDER) + { + auto Slash=CurMask.find(CPATHDIVIDER,2); + if (Slash!=std::wstring::npos) + { + Slash=CurMask.find(CPATHDIVIDER,Slash+1); + // If path separator is mssing or it is the last string character. + ScanEntireDisk=Slash==std::wstring::npos || + Slash!=std::wstring::npos && Slash+1==CurMask.size(); + + // Win32 FindFirstFile fails for \\server\share names without + // the trailing backslash. So we add it here. + if (Slash==std::wstring::npos) + CurMask+=CPATHDIVIDER; + } + } + else + ScanEntireDisk=IsDriveLetter(CurMask) && IsPathDiv(CurMask[2]) && CurMask[3]==0; + + // Calculate the name position again, because we could modify UNC path above. + auto NamePos=GetNamePos(CurMask); + std::wstring Name=CurMask.substr(NamePos); + if (Name.empty()) + CurMask+=MASKALL; + if (Name==L"." || Name==L"..") { - AddEndSlash(CurMask,ASIZE(CurMask)); - wcsncatz(CurMask,MASKALL,ASIZE(CurMask)); + AddEndSlash(CurMask); + CurMask+=MASKALL; } - SpecPathLength=Name-CurMask; Depth=0; - wcsncpyz(OrigCurMask,CurMask,ASIZE(OrigCurMask)); + OrigCurMask=CurMask; return true; } @@ -239,7 +261,7 @@ bool ScanTree::GetNextMask() SCAN_CODE ScanTree::FindProc(FindData *FD) { - if (*CurMask==0) + if (CurMask.empty()) return SCAN_NEXT; bool FastFindFile=false; @@ -272,10 +294,9 @@ SCAN_CODE ScanTree::FindProc(FindData *FD) // Create the new FindFile object for wildcard based search. FindStack[Depth]=new FindFile; - wchar SearchMask[NM]; - wcsncpyz(SearchMask,CurMask,ASIZE(SearchMask)); + std::wstring SearchMask=CurMask; if (SearchAll) - SetName(SearchMask,MASKALL,ASIZE(SearchMask)); + SetName(SearchMask,MASKALL); FindStack[Depth]->SetMask(SearchMask); } else @@ -314,7 +335,7 @@ SCAN_CODE ScanTree::FindProc(FindData *FD) // It is not necessary for directories, because even in "fast find" // mode, directory recursing will quit by (Depth < 0) condition, // which returns SCAN_DONE to calling function. - *CurMask=0; + CurMask.clear(); return RetCode; } @@ -333,9 +354,6 @@ SCAN_CODE ScanTree::FindProc(FindData *FD) if (Error) ScanError(Error); - wchar DirName[NM]; - *DirName=0; - // Going to at least one directory level higher. delete FindStack[Depth]; FindStack[Depth--]=NULL; @@ -351,29 +369,32 @@ SCAN_CODE ScanTree::FindProc(FindData *FD) return SCAN_DONE; } - wchar *Slash=wcsrchr(CurMask,CPATHDIVIDER); - if (Slash!=NULL) + auto Slash=CurMask.rfind(CPATHDIVIDER); + if (Slash!=std::wstring::npos) { - wchar Mask[NM]; - wcsncpyz(Mask,Slash,ASIZE(Mask)); + std::wstring Mask; + Mask=CurMask.substr(Slash); // Name mask with leading slash like \*.* if (DepthIsDir) + { + FD->Flags|=FDDF_SECONDDIR; + return Error ? SCAN_ERROR:SCAN_SUCCESS; } - } - if (GetDirs==SCAN_GETDIRSTWICE && - FindFile::FastFind(DirName,FD,GetLinks) && FD->IsDir) - { - FD->Flags|=FDDF_SECONDDIR; - return Error ? SCAN_ERROR:SCAN_SUCCESS; } return Error ? SCAN_ERROR:SCAN_NEXT; } @@ -403,22 +424,22 @@ SCAN_CODE ScanTree::FindProc(FindData *FD) return FastFindFile ? SCAN_DONE:SCAN_NEXT; } - wchar Mask[NM]; - - wcsncpyz(Mask,FastFindFile ? MASKALL:PointToName(CurMask),ASIZE(Mask)); - wcsncpyz(CurMask,FD->Name,ASIZE(CurMask)); + std::wstring Mask=FastFindFile ? MASKALL:PointToName(CurMask); + CurMask=FD->Name; - if (wcslen(CurMask)+wcslen(Mask)+1>=NM || Depth>=MAXSCANDEPTH-1) + if (CurMask.size()+Mask.size()+1>=MAXPATHSIZE || Depth>=MAXSCANDEPTH-1) { uiMsg(UIERROR_PATHTOOLONG,CurMask,SPATHDIVIDER,Mask); return SCAN_ERROR; } - AddEndSlash(CurMask,ASIZE(CurMask)); - wcsncatz(CurMask,Mask,ASIZE(CurMask)); + AddEndSlash(CurMask); + CurMask+=Mask; Depth++; + FindStack.resize(Depth+1); + // We need to use OrigCurMask for depths less than SetAllMaskDepth // and "*" for depths equal or larger than SetAllMaskDepth. // It is important when "fast finding" directories at Depth > 0. @@ -459,19 +480,18 @@ void ScanTree::ScanError(bool &Error) // We cannot just check FD->FileAttr here, it can be undefined // if we process "folder\*" mask or if we process "folder" mask, // but "folder" is inaccessible. - wchar *Slash=PointToName(CurMask); - if (Slash>CurMask) + auto Slash=GetNamePos(CurMask); + if (Slash>1) { - *(Slash-1)=0; - DWORD Attr=GetFileAttributes(CurMask); - *(Slash-1)=CPATHDIVIDER; + std::wstring Parent=CurMask.substr(0,Slash-1); + DWORD Attr=GetFileAttr(Parent); if (Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0) Error=false; } // Do not display an error if we cannot scan contents of // "System Volume Information" folder. Normally it is not accessible. - if (wcsstr(CurMask,L"System Volume Information\\")!=NULL) + if (CurMask.find(L"System Volume Information\\")!=std::wstring::npos) Error=false; } #endif @@ -484,10 +504,16 @@ void ScanTree::ScanError(bool &Error) if (ErrDirList!=NULL) ErrDirList->AddString(CurMask); if (ErrDirSpecPathLength!=NULL) - ErrDirSpecPathLength->Push((uint)SpecPathLength); - wchar FullName[NM]; + ErrDirSpecPathLength->push_back((uint)SpecPathLength); + std::wstring FullName; // This conversion works for wildcard masks too. - ConvertNameToFull(CurMask,FullName,ASIZE(FullName)); + ConvertNameToFull(CurMask,FullName); + + // 2025.04.29: remove the trailing mask, so we issue errors like + // "Cannot read contents of "c:\dir"" instead of "c:\path\dir\file.ext", + // when searching for file.ext in inaccessible c:\path\dir. + RemoveNameFromPath(FullName); + uiMsg(UIERROR_DIRSCAN,FullName); ErrHandler.SysErrMsg(); } diff --git a/unrar/scantree.hpp b/unrar/scantree.hpp index 7ebe69ad..06bf545c 100644 --- a/unrar/scantree.hpp +++ b/unrar/scantree.hpp @@ -11,20 +11,21 @@ enum SCAN_DIRS enum SCAN_CODE { SCAN_SUCCESS,SCAN_DONE,SCAN_ERROR,SCAN_NEXT }; -#define MAXSCANDEPTH (NM/2) - class CommandData; class ScanTree { private: + static constexpr size_t MAXSCANDEPTH = MAXPATHSIZE/2; + bool ExpandFolderMask(); bool GetFilteredMask(); bool GetNextMask(); SCAN_CODE FindProc(FindData *FD); void ScanError(bool &Error); - FindFile *FindStack[MAXSCANDEPTH]; +// FindFile *FindStack[MAXSCANDEPTH]; + std::vector FindStack; int Depth; int SetAllMaskDepth; @@ -33,13 +34,13 @@ class ScanTree RECURSE_MODE Recurse; bool GetLinks; SCAN_DIRS GetDirs; - int Errors; + uint Errors; // Set when processing paths like c:\ (root directory without wildcards). bool ScanEntireDisk; - wchar CurMask[NM]; - wchar OrigCurMask[NM]; + std::wstring CurMask; + std::wstring OrigCurMask; // Store all folder masks generated from folder wildcard mask in non-recursive mode. StringList ExpandedFolderList; @@ -49,7 +50,7 @@ class ScanTree // Save the list of unreadable dirs here. StringList *ErrDirList; - Array *ErrDirSpecPathLength; + std::vector *ErrDirSpecPathLength; // Set if processing a folder wildcard mask. bool FolderWildcards; @@ -57,7 +58,7 @@ class ScanTree bool SearchAllInRoot; size_t SpecPathLength; - wchar ErrArcName[NM]; + std::wstring ErrArcName; CommandData *Cmd; public: @@ -65,10 +66,10 @@ class ScanTree ~ScanTree(); SCAN_CODE GetNext(FindData *FindData); size_t GetSpecPathLength() {return SpecPathLength;} - int GetErrors() {return Errors;}; - void SetErrArcName(const wchar *Name) {wcsncpyz(ErrArcName,Name,ASIZE(ErrArcName));} + uint GetErrors() {return Errors;}; + void SetErrArcName(const std::wstring &Name) {ErrArcName=Name;} void SetCommandData(CommandData *Cmd) {ScanTree::Cmd=Cmd;} - void SetErrDirList(StringList *List,Array *Lengths) + void SetErrDirList(StringList *List,std::vector *Lengths) { ErrDirList=List; ErrDirSpecPathLength=Lengths; diff --git a/unrar/secpassword.cpp b/unrar/secpassword.cpp index 4865b3fd..8d8f2986 100644 --- a/unrar/secpassword.cpp +++ b/unrar/secpassword.cpp @@ -51,12 +51,11 @@ class CryptLoader }; // We need to call FreeLibrary when RAR is exiting. -CryptLoader GlobalCryptLoader; +static CryptLoader GlobalCryptLoader; #endif SecPassword::SecPassword() { - CrossProcess=false; Set(L""); } @@ -70,7 +69,8 @@ SecPassword::~SecPassword() void SecPassword::Clean() { PasswordSet=false; - cleandata(Password,sizeof(Password)); + if (!Password.empty()) + cleandata(Password.data(),Password.size()*sizeof(Password[0])); } @@ -79,7 +79,7 @@ void SecPassword::Clean() // So we use our own function for this purpose. void cleandata(void *data,size_t size) { - if (data==NULL || size==0) + if (data==nullptr || size==0) return; #if defined(_WIN_ALL) && defined(_MSC_VER) SecureZeroMemory(data,size); @@ -104,7 +104,7 @@ void SecPassword::Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstS // Source string can be shorter than destination as in case when we process // -p parameter, so we need to take into account both sizes. memcpy(Dst,Src,Min(SrcSize,DstSize)*sizeof(*Dst)); - SecHideData(Dst,DstSize*sizeof(*Dst),Encode,CrossProcess); + SecHideData(Dst,DstSize*sizeof(*Dst),Encode,false); } @@ -112,7 +112,7 @@ void SecPassword::Get(wchar *Psw,size_t MaxSize) { if (PasswordSet) { - Process(Password,ASIZE(Password),Psw,MaxSize,false); + Process(&Password[0],Password.size(),Psw,MaxSize,false); Psw[MaxSize-1]=0; } else @@ -120,19 +120,26 @@ void SecPassword::Get(wchar *Psw,size_t MaxSize) } +void SecPassword::Get(std::wstring &Psw) +{ + wchar PswBuf[MAXPASSWORD]; + Get(PswBuf,ASIZE(PswBuf)); + Psw=PswBuf; +} + + void SecPassword::Set(const wchar *Psw) { - if (*Psw==0) - { - PasswordSet=false; - memset(Password,0,sizeof(Password)); - } - else + // Eliminate any traces of previously stored password for security reason + // in case it was longer than new one. + Clean(); + + if (*Psw!=0) { PasswordSet=true; - Process(Psw,wcslen(Psw)+1,Password,ASIZE(Password),true); + Process(Psw,wcslen(Psw)+1,&Password[0],Password.size(),true); } } @@ -142,7 +149,7 @@ size_t SecPassword::Length() wchar Plain[MAXPASSWORD]; Get(Plain,ASIZE(Plain)); size_t Length=wcslen(Plain); - cleandata(Plain,ASIZE(Plain)); + cleandata(Plain,sizeof(Plain)); return Length; } @@ -157,12 +164,15 @@ bool SecPassword::operator == (SecPassword &psw) Get(Plain1,ASIZE(Plain1)); psw.Get(Plain2,ASIZE(Plain2)); bool Result=wcscmp(Plain1,Plain2)==0; - cleandata(Plain1,ASIZE(Plain1)); - cleandata(Plain2,ASIZE(Plain2)); + cleandata(Plain1,sizeof(Plain1)); + cleandata(Plain2,sizeof(Plain2)); return Result; } +// Set CrossProcess to true if we need to pass a password to another process. +// We use CrossProcess when transferring parameters to UAC elevated WinRAR +// and Windows GUI SFX modules. void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess) { // CryptProtectMemory is not available in UWP and CryptProtectData diff --git a/unrar/secpassword.hpp b/unrar/secpassword.hpp index 375d3887..44e073c9 100644 --- a/unrar/secpassword.hpp +++ b/unrar/secpassword.hpp @@ -8,28 +8,23 @@ class SecPassword private: void Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstSize,bool Encode); - wchar Password[MAXPASSWORD]; - - // It is important to have this 'bool' value, so if our object is cleaned - // with memset as a part of larger structure, it is handled correctly. + std::vector Password = std::vector(MAXPASSWORD); bool PasswordSet; public: SecPassword(); ~SecPassword(); void Clean(); void Get(wchar *Psw,size_t MaxSize); + void Get(std::wstring &Psw); void Set(const wchar *Psw); bool IsSet() {return PasswordSet;} size_t Length(); bool operator == (SecPassword &psw); - - // Set to true if we need to pass a password to another process. - // We use it when transferring parameters to UAC elevated WinRAR. - bool CrossProcess; }; void cleandata(void *data,size_t size); +inline void cleandata(std::wstring &s) {cleandata(&s[0],s.size()*sizeof(s[0]));} void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess); #endif diff --git a/unrar/sha256.cpp b/unrar/sha256.cpp index f90d2c09..4c93aba7 100644 --- a/unrar/sha256.cpp +++ b/unrar/sha256.cpp @@ -63,7 +63,7 @@ static void sha256_transform(sha256_context *ctx) for (uint I = 0; I < 64; I++) { - uint T1 = v[7] + Sg1(v[4]) + Ch(v[4], v[5], v[6]) + K[I] + W[I]; + uint32 T1 = v[7] + Sg1(v[4]) + Ch(v[4], v[5], v[6]) + K[I] + W[I]; // It is possible to eliminate variable copying if we unroll loop // and rename variables every time. But my test did not show any speed @@ -74,7 +74,7 @@ static void sha256_transform(sha256_context *ctx) v[4] = v[3] + T1; // It works a little faster when moved here from beginning of loop. - uint T2 = Sg0(v[0]) + Maj(v[0], v[1], v[2]); + uint32 T2 = Sg0(v[0]) + Maj(v[0], v[1], v[2]); v[3] = v[2]; v[2] = v[1]; @@ -146,3 +146,13 @@ void sha256_done(sha256_context *ctx, byte *Digest) sha256_init(ctx); } + + +void sha256_get(const void *Data, size_t Size, byte *Digest) +{ + sha256_context ctx; + sha256_init(&ctx); + sha256_process(&ctx, Data, Size); + sha256_done(&ctx, Digest); +} + diff --git a/unrar/sha256.hpp b/unrar/sha256.hpp index b6837e76..186297c0 100644 --- a/unrar/sha256.hpp +++ b/unrar/sha256.hpp @@ -13,5 +13,6 @@ typedef struct void sha256_init(sha256_context *ctx); void sha256_process(sha256_context *ctx, const void *Data, size_t Size); void sha256_done(sha256_context *ctx, byte *Digest); +void sha256_get(const void *Data, size_t Size, byte *Digest); #endif diff --git a/unrar/smallfn.cpp b/unrar/smallfn.cpp index 81259d02..924e195b 100644 --- a/unrar/smallfn.cpp +++ b/unrar/smallfn.cpp @@ -15,5 +15,3 @@ int ToPercentUnlim(int64 N1,int64 N2) return 0; return (int)(N1*100/N2); } - - diff --git a/unrar/smallfn.hpp b/unrar/smallfn.hpp index f53daa8b..1e706a57 100644 --- a/unrar/smallfn.hpp +++ b/unrar/smallfn.hpp @@ -3,6 +3,5 @@ int ToPercent(int64 N1,int64 N2); int ToPercentUnlim(int64 N1,int64 N2); -void RARInitData(); #endif diff --git a/unrar/strfn.cpp b/unrar/strfn.cpp index 8904b907..ed8f91fe 100644 --- a/unrar/strfn.cpp +++ b/unrar/strfn.cpp @@ -2,66 +2,87 @@ const char *NullToEmpty(const char *Str) { - return Str==NULL ? "":Str; + return Str==nullptr ? "":Str; } const wchar *NullToEmpty(const wchar *Str) { - return Str==NULL ? L"":Str; + return Str==nullptr ? L"":Str; } -void IntToExt(const char *Src,char *Dest,size_t DestSize) +// Convert from OEM encoding. +void OemToExt(const std::string &Src,std::string &Dest) { #ifdef _WIN_ALL - // OemToCharBuff does not stop at 0, so let's check source length. - size_t SrcLength=strlen(Src)+1; - if (DestSize>SrcLength) - DestSize=SrcLength; - OemToCharBuffA(Src,Dest,(DWORD)DestSize); - Dest[DestSize-1]=0; + if (std::addressof(Src)!=std::addressof(Dest)) + Dest=Src; + // OemToCharA use seems to be discouraged. So we use OemToCharBuffA, + // which doesn't stop at 0 and converts the entire passed length. + OemToCharBuffA(&Dest[0],&Dest[0],(DWORD)Dest.size()); + + std::string::size_type Pos=Dest.find('\0'); // Avoid zeroes inside of Dest. + if (Pos!=std::string::npos) + Dest.erase(Pos); + #else - if (Dest!=Src) - strncpyz(Dest,Src,DestSize); + if (std::addressof(Src)!=std::addressof(Dest)) + Dest=Src; #endif } // Convert archived names and comments to Unicode. // Allows user to select a code page in GUI. -void ArcCharToWide(const char *Src,wchar *Dest,size_t DestSize,ACTW_ENCODING Encoding) +void ArcCharToWide(const char *Src,std::wstring &Dest,ACTW_ENCODING Encoding) { + // Optimized code for English only strings. Improved performance on archives + // with millions of small files. + bool IsLowAscii=true; + size_t SrcSize=0; + // "unsigned" is important here for correct comparison. + for (const unsigned char *S=(const unsigned char *)Src;*S!=0;S++,SrcSize++) + if (*S>127) + { + IsLowAscii=false; + break; + } + if (IsLowAscii) + { + Dest.resize(SrcSize); + for (size_t I=0;Src[I]!=0;I++) + Dest[I]=Src[I]; + return; + } + #if defined(_WIN_ALL) // Console Windows RAR. if (Encoding==ACTW_UTF8) - UtfToWide(Src,Dest,DestSize); + UtfToWide(Src,Dest); else { - Array NameA; + std::string NameA; if (Encoding==ACTW_OEM) { - NameA.Alloc(DestSize+1); - IntToExt(Src,&NameA[0],NameA.Size()); - Src=&NameA[0]; + OemToExt(Src,NameA); + Src=NameA.data(); } - CharToWide(Src,Dest,DestSize); + CharToWide(Src,Dest); } #else // RAR for Unix. if (Encoding==ACTW_UTF8) - UtfToWide(Src,Dest,DestSize); + UtfToWide(Src,Dest); else - CharToWide(Src,Dest,DestSize); + CharToWide(Src,Dest); #endif - // Ensure that we return a zero terminate string for security reason. - // While [Jni]CharToWide might already do it, be protected in case of future - // changes in these functions. - if (DestSize>0) - Dest[DestSize-1]=0; + TruncateAtZero(Dest); // Ensure there are no zeroes inside of string. } + + int stricomp(const char *s1,const char *s2) { #ifdef _WIN_ALL @@ -113,56 +134,54 @@ wchar* RemoveEOL(wchar *Str) } -wchar* RemoveLF(wchar *Str) +void RemoveEOL(std::wstring &Str) { - for (int I=(int)wcslen(Str)-1;I>=0 && (Str[I]=='\r' || Str[I]=='\n');I--) - Str[I]=0; - return Str; + while (!Str.empty()) + { + wchar c=Str.back(); + if (c=='\r' || c=='\n' || c==' ' || c=='\t') + Str.pop_back(); + else + break; + } } -unsigned char loctolower(unsigned char ch) +wchar* RemoveLF(wchar *Str) { -#if defined(_WIN_ALL) - // Convert to LPARAM first to avoid a warning in 64 bit mode. - // Convert to uintptr_t to avoid Clang/win error: cast to 'char *' from smaller integer type 'unsigned char' [-Werror,-Wint-to-pointer-cast] - return (int)(LPARAM)CharLowerA((LPSTR)(uintptr_t)ch); -#else - return tolower(ch); -#endif + for (int I=(int)wcslen(Str)-1;I>=0 && (Str[I]=='\r' || Str[I]=='\n');I--) + Str[I]=0; + return Str; } -unsigned char loctoupper(unsigned char ch) +void RemoveLF(std::wstring &Str) { -#if defined(_WIN_ALL) - // Convert to LPARAM first to avoid a warning in 64 bit mode. - // Convert to uintptr_t to avoid Clang/win error: cast to 'char *' from smaller integer type 'unsigned char' [-Werror,-Wint-to-pointer-cast] - return (int)(LPARAM)CharUpperA((LPSTR)(uintptr_t)ch); -#else - return toupper(ch); -#endif + for (int I=(int)Str.size()-1;I>=0 && (Str[I]=='\r' || Str[I]=='\n');I--) + Str.erase(I); } -// toupper with English only results if English input is provided. -// It avoids Turkish (small i) -> (big I with dot) conversion problem. -// We do not define 'ch' as 'int' to avoid necessity to cast all +#if defined(SFX_MODULE) +// char version of etoupperw. Used in console SFX module only. +// Fast toupper for English only input and output. Additionally to speed, +// it also avoids Turkish small i to big I with dot conversion problem. +// We do not define 'c' as 'int' to avoid necessity to cast all // signed chars passed to this function to unsigned char. -unsigned char etoupper(unsigned char ch) +unsigned char etoupper(unsigned char c) { - if (ch=='i') - return 'I'; - return toupper(ch); + return c>='a' && c<='z' ? c-'a'+'A' : c; } +#endif -// Unicode version of etoupper. -wchar etoupperw(wchar ch) +// Fast toupper for English only input and output. Additionally to speed, +// it also avoids Turkish small i to big I with dot conversion problem. +// We do not define 'c' as 'int' to avoid necessity to cast all +// signed wchars passed to this function to unsigned char. +wchar etoupperw(wchar c) { - if (ch=='i') - return 'I'; - return toupperw(ch); + return c>='a' && c<='z' ? c-'a'+'A' : c; } @@ -198,30 +217,18 @@ bool IsAlpha(int ch) -void BinToHex(const byte *Bin,size_t BinSize,char *HexA,wchar *HexW,size_t HexSize) +void BinToHex(const byte *Bin,size_t BinSize,std::wstring &Hex) { - uint A=0,W=0; // ASCII and Unicode hex output positions. + Hex.clear(); for (uint I=0;I> 4; uint Low=Bin[I] & 0xf; - uint HighHex=High>9 ? 'a'+High-10:'0'+High; - uint LowHex=Low>9 ? 'a'+Low-10:'0'+Low; - if (HexA!=NULL && A9 ? 'a'+High-10 : '0'+High; + uint LowHex=Low>9 ? 'a'+Low-10 : '0'+Low; + Hex+=HighHex; + Hex+=LowHex; } - if (HexA!=NULL && HexSize>0) - HexA[A]=0; - if (HexW!=NULL && HexSize>0) - HexW[W]=0; } @@ -239,22 +246,25 @@ uint GetDigits(uint Number) #endif -bool LowAscii(const char *Str) +bool LowAscii(const std::string &Str) { - for (size_t I=0;Str[I]!=0;I++) - if (/*(byte)Str[I]<32 || */(byte)Str[I]>127) + for (char Ch : Str) + { + // We convert char to byte in case char is signed. + if (/*(uint)Ch<32 || */(byte)Ch>127) return false; + } return true; } -bool LowAscii(const wchar *Str) +bool LowAscii(const std::wstring &Str) { - for (size_t I=0;Str[I]!=0;I++) + for (wchar Ch : Str) { // We convert wchar_t to uint just in case if some compiler // uses signed wchar_t. - if (/*(uint)Str[I]<32 || */(uint)Str[I]>127) + if (/*(uint)Ch<32 || */(uint)Ch>127) return false; } return true; @@ -271,6 +281,12 @@ int wcsicompc(const wchar *s1,const wchar *s2) // For path comparison. } +int wcsicompc(const std::wstring &s1,const std::wstring &s2) +{ + return wcsicompc(s1.c_str(),s2.c_str()); +} + + int wcsnicompc(const wchar *s1,const wchar *s2,size_t n) { #if defined(_UNIX) @@ -281,6 +297,12 @@ int wcsnicompc(const wchar *s1,const wchar *s2,size_t n) } +int wcsnicompc(const std::wstring &s1,const std::wstring &s2,size_t n) +{ + return wcsnicompc(s1.c_str(),s2.c_str(),n); +} + + // Safe copy: copies maxlen-1 max and for maxlen>0 returns zero terminated dest. void strncpyz(char *dest, const char *src, size_t maxlen) { @@ -379,88 +401,190 @@ void itoa(int64 n,wchar *Str,size_t MaxSize) } -const wchar* GetWide(const char *Src) +// Convert the number to string using thousand separators. +void fmtitoa(int64 n,wchar *Str,size_t MaxSize) +{ + static wchar ThSep=0; // Thousands separator. +#ifdef _WIN_ALL + wchar Info[10]; + if (!ThSep!=0 && GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_STHOUSAND,Info,ASIZE(Info))>0) + ThSep=*Info; +#elif defined(_UNIX) + ThSep=*localeconv()->thousands_sep; +#endif + if (ThSep==0) // If failed to detect the actual separator value. + ThSep=' '; + wchar RawText[30]; // 20 characters are enough for largest unsigned 64 bit int. + itoa(n,RawText,ASIZE(RawText)); + uint S=0,D=0,L=wcslen(RawText)%3; + while (RawText[S]!=0 && D+1= ASIZE(StrTable)) - StrNum=0; - wchar *Str=StrTable[StrNum]; - CharToWide(Src,Str,MaxLength); - Str[MaxLength-1]=0; + std::wstring Str; + CharToWide(Src,Str); return Str; } // Parse string containing parameters separated with spaces. -// Support quote marks. Param can be NULL to return the pointer to next -// parameter, which can be used to estimate the buffer size for Param. -const wchar* GetCmdParam(const wchar *CmdLine,wchar *Param,size_t MaxSize) +// Support quote marks. Accepts and updates the current position in the string. +// Returns false if there is nothing to parse. +bool GetCmdParam(const std::wstring &CmdLine,std::wstring::size_type &Pos,std::wstring &Param) { - while (IsSpace(*CmdLine)) - CmdLine++; - if (*CmdLine==0) - return NULL; + Param.clear(); + + while (IsSpace(CmdLine[Pos])) + Pos++; + if (Pos==CmdLine.size()) + return false; - size_t ParamSize=0; bool Quote=false; - while (*CmdLine!=0 && (Quote || !IsSpace(*CmdLine))) + while (Pos=0 || Msg.size()>MaxAllocSize) + break; + Msg.resize(Msg.size()*4); } - Cvt[Dest]=0; + std::wstring::size_type ZeroPos=Msg.find(L'\0'); + if (ZeroPos!=std::wstring::npos) + Msg.resize(ZeroPos); // Remove excessive zeroes at the end. + + return Msg; +} +#endif + + +#ifdef _WIN_ALL +bool ExpandEnvironmentStr(std::wstring &Str) +{ + DWORD ExpCode=ExpandEnvironmentStrings(Str.c_str(),nullptr,0); + if (ExpCode==0) + return false; + std::vector Buf(ExpCode); + ExpCode=ExpandEnvironmentStrings(Str.c_str(),Buf.data(),(DWORD)Buf.size()); + if (ExpCode==0 || ExpCode>Buf.size()) + return false; + Str=Buf.data(); + return true; } #endif + + +void TruncateAtZero(std::wstring &Str) +{ + std::wstring::size_type Pos=Str.find(L'\0'); + if (Pos!=std::wstring::npos) + Str.erase(Pos); +} + + +void ReplaceEsc(std::wstring &Str) +{ + std::wstring::size_type Pos=0; + while (true) + { + Pos=Str.find(L'\033',Pos); + if (Pos==std::wstring::npos) + break; + Str[Pos]=L'\''; + Str.insert(Pos+1,L"\\033'"); + Pos+=6; + } +} diff --git a/unrar/strfn.hpp b/unrar/strfn.hpp index 2984f8c3..ed77e5a7 100644 --- a/unrar/strfn.hpp +++ b/unrar/strfn.hpp @@ -3,49 +3,67 @@ const char* NullToEmpty(const char *Str); const wchar* NullToEmpty(const wchar *Str); -void IntToExt(const char *Src,char *Dest,size_t DestSize); +void OemToExt(const std::string &Src,std::string &Dest); enum ACTW_ENCODING { ACTW_DEFAULT, ACTW_OEM, ACTW_UTF8}; -void ArcCharToWide(const char *Src,wchar *Dest,size_t DestSize,ACTW_ENCODING Encoding); +void ArcCharToWide(const char *Src,std::wstring &Dest,ACTW_ENCODING Encoding); int stricomp(const char *s1,const char *s2); int strnicomp(const char *s1,const char *s2,size_t n); wchar* RemoveEOL(wchar *Str); +void RemoveEOL(std::wstring &Str); wchar* RemoveLF(wchar *Str); -unsigned char loctolower(unsigned char ch); -unsigned char loctoupper(unsigned char ch); +void RemoveLF(std::wstring &Str); void strncpyz(char *dest, const char *src, size_t maxlen); void wcsncpyz(wchar *dest, const wchar *src, size_t maxlen); void strncatz(char* dest, const char* src, size_t maxlen); void wcsncatz(wchar* dest, const wchar* src, size_t maxlen); -unsigned char etoupper(unsigned char ch); -wchar etoupperw(wchar ch); +#if defined(SFX_MODULE) +unsigned char etoupper(unsigned char c); +#endif +wchar etoupperw(wchar c); bool IsDigit(int ch); bool IsSpace(int ch); bool IsAlpha(int ch); -void BinToHex(const byte *Bin,size_t BinSize,char *Hex,wchar *HexW,size_t HexSize); +void BinToHex(const byte *Bin,size_t BinSize,std::wstring &Hex); #ifndef SFX_MODULE uint GetDigits(uint Number); #endif -bool LowAscii(const char *Str); -bool LowAscii(const wchar *Str); +bool LowAscii(const std::string &Str); +bool LowAscii(const std::wstring &Str); int wcsicompc(const wchar *s1,const wchar *s2); +int wcsicompc(const std::wstring &s1,const std::wstring &s2); int wcsnicompc(const wchar *s1,const wchar *s2,size_t n); +int wcsnicompc(const std::wstring &s1,const std::wstring &s2,size_t n); + +// std::[w]string::starts_with() replacement for C++17 and earlier. +inline bool starts_with(const std::string &s,const std::string &p) {return s.rfind(p,0)==0;} +inline bool starts_with(const std::wstring &s,const std::wstring &p) {return s.rfind(p,0)==0;} void itoa(int64 n,char *Str,size_t MaxSize); void itoa(int64 n,wchar *Str,size_t MaxSize); -const wchar* GetWide(const char *Src); -const wchar* GetCmdParam(const wchar *CmdLine,wchar *Param,size_t MaxSize); +void fmtitoa(int64 n,wchar *Str,size_t MaxSize); +std::wstring GetWide(const char *Src); +bool GetCmdParam(const std::wstring &CmdLine,std::wstring::size_type &Pos,std::wstring &Param); #ifndef RARDLL -void PrintfPrepareFmt(const wchar *Org,wchar *Cvt,size_t MaxSize); +void PrintfPrepareFmt(const wchar *Org,std::wstring &Cvt); +std::wstring wstrprintf(const wchar *fmt,...); +std::wstring vwstrprintf(const wchar *fmt,va_list arglist); #endif +#ifdef _WIN_ALL +bool ExpandEnvironmentStr(std::wstring &Str); +#endif + +void TruncateAtZero(std::wstring &Str); +void ReplaceEsc(std::wstring &Str); + #endif diff --git a/unrar/strlist.cpp b/unrar/strlist.cpp index 50d69c71..e76654fc 100644 --- a/unrar/strlist.cpp +++ b/unrar/strlist.cpp @@ -9,18 +9,20 @@ StringList::StringList() void StringList::Reset() { Rewind(); - StringData.Reset(); + StringData.clear(); StringsCount=0; SavePosNumber=0; } +/* void StringList::AddStringA(const char *Str) { - Array StrW(strlen(Str)); - CharToWide(Str,&StrW[0],StrW.Size()); - AddString(&StrW[0]); + std::wstring StrW; + CharToWide(Str,StrW); + AddString(StrW); } +*/ void StringList::AddString(const wchar *Str) @@ -28,22 +30,30 @@ void StringList::AddString(const wchar *Str) if (Str==NULL) Str=L""; - size_t PrevSize=StringData.Size(); - StringData.Add(wcslen(Str)+1); + size_t PrevSize=StringData.size(); + StringData.resize(PrevSize+wcslen(Str)+1); wcscpy(&StringData[PrevSize],Str); StringsCount++; } +void StringList::AddString(const std::wstring &Str) +{ + AddString(Str.c_str()); +} + + +/* bool StringList::GetStringA(char *Str,size_t MaxLength) { - Array StrW(MaxLength); - if (!GetString(&StrW[0],StrW.Size())) + std::wstring StrW; + if (!GetString(StrW)) return false; - WideToChar(&StrW[0],Str,MaxLength); + WideToChar(StrW.c_str(),Str,MaxLength); return true; } +*/ bool StringList::GetString(wchar *Str,size_t MaxLength) @@ -56,6 +66,16 @@ bool StringList::GetString(wchar *Str,size_t MaxLength) } +bool StringList::GetString(std::wstring &Str) +{ + wchar *StrPtr; + if (!GetString(&StrPtr)) + return false; + Str=StrPtr; + return true; +} + + #ifndef SFX_MODULE bool StringList::GetString(wchar *Str,size_t MaxLength,int StringNum) { @@ -71,6 +91,22 @@ bool StringList::GetString(wchar *Str,size_t MaxLength,int StringNum) RestorePosition(); return RetCode; } + + +bool StringList::GetString(std::wstring &Str,int StringNum) +{ + SavePosition(); + Rewind(); + bool RetCode=true; + while (StringNum-- >=0) + if (!GetString(Str)) + { + RetCode=false; + break; + } + RestorePosition(); + return RetCode; +} #endif @@ -84,7 +120,7 @@ wchar* StringList::GetString() bool StringList::GetString(wchar **Str) { - if (CurPos>=StringData.Size()) // No more strings left unprocessed. + if (CurPos>=StringData.size()) // No more strings left unprocessed. { if (Str!=NULL) *Str=NULL; @@ -107,7 +143,7 @@ void StringList::Rewind() #ifndef SFX_MODULE -bool StringList::Search(const wchar *Str,bool CaseSensitive) +bool StringList::Search(const std::wstring &Str,bool CaseSensitive) { SavePosition(); Rewind(); @@ -115,8 +151,8 @@ bool StringList::Search(const wchar *Str,bool CaseSensitive) wchar *CurStr; while (GetString(&CurStr)) { - if (Str!=NULL && CurStr!=NULL) - if ((CaseSensitive ? wcscmp(Str,CurStr):wcsicomp(Str,CurStr))!=0) + if (CurStr!=NULL) + if (CaseSensitive && Str!=CurStr || !CaseSensitive && wcsicomp(Str,CurStr)!=0) continue; Found=true; break; diff --git a/unrar/strlist.hpp b/unrar/strlist.hpp index 16a2cbb0..d89ba5c9 100644 --- a/unrar/strlist.hpp +++ b/unrar/strlist.hpp @@ -4,7 +4,7 @@ class StringList { private: - Array StringData; + std::vector StringData; size_t CurPos; size_t StringsCount; @@ -13,17 +13,20 @@ class StringList public: StringList(); void Reset(); - void AddStringA(const char *Str); +// void AddStringA(const char *Str); void AddString(const wchar *Str); - bool GetStringA(char *Str,size_t MaxLength); + void AddString(const std::wstring &Str); +// bool GetStringA(char *Str,size_t MaxLength); bool GetString(wchar *Str,size_t MaxLength); + bool GetString(std::wstring &Str); bool GetString(wchar *Str,size_t MaxLength,int StringNum); + bool GetString(std::wstring &Str,int StringNum); wchar* GetString(); bool GetString(wchar **Str); void Rewind(); size_t ItemsCount() {return StringsCount;}; - size_t GetCharCount() {return StringData.Size();} - bool Search(const wchar *Str,bool CaseSensitive); + size_t GetCharCount() {return StringData.size();} + bool Search(const std::wstring &Str,bool CaseSensitive); void SavePosition(); void RestorePosition(); }; diff --git a/unrar/system.cpp b/unrar/system.cpp index 4ae2b890..b8f96cbb 100644 --- a/unrar/system.cpp +++ b/unrar/system.cpp @@ -101,19 +101,35 @@ void Wait() -#if defined(_WIN_ALL) && !defined(SFX_MODULE) -void Shutdown(POWER_MODE Mode) +#ifdef _WIN_ALL +bool SetPrivilege(LPCTSTR PrivName) { + bool Success=false; + HANDLE hToken; - TOKEN_PRIVILEGES tkp; - if (OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken)) + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { - LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid); - tkp.PrivilegeCount = 1; - tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + TOKEN_PRIVILEGES tp; + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0); + if (LookupPrivilegeValue(NULL,PrivName,&tp.Privileges[0].Luid) && + AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) && + GetLastError() == ERROR_SUCCESS) + Success=true; + + CloseHandle(hToken); } + + return Success; +} +#endif + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +void Shutdown(POWER_MODE Mode) +{ + SetPrivilege(SE_SHUTDOWN_NAME); if (Mode==POWERMODE_OFF) ExitWindowsEx(EWX_SHUTDOWN|EWX_FORCE,SHTDN_REASON_FLAG_PLANNED); if (Mode==POWERMODE_SLEEP) @@ -150,16 +166,18 @@ bool ShutdownCheckAnother(bool Open) + #if defined(_WIN_ALL) // Load library from Windows System32 folder. Use this function to prevent // loading a malicious code from current folder or same folder as exe. HMODULE WINAPI LoadSysLibrary(const wchar *Name) { - wchar SysDir[NM]; - if (GetSystemDirectory(SysDir,ASIZE(SysDir))==0) - return NULL; - MakeName(SysDir,Name,SysDir,ASIZE(SysDir)); - return LoadLibrary(SysDir); + std::vector SysDir(MAX_PATH); + if (GetSystemDirectory(SysDir.data(),(UINT)SysDir.size())==0) + return nullptr; + std::wstring FullName; + MakeName(SysDir.data(),Name,FullName); + return LoadLibrary(FullName.c_str()); } @@ -186,11 +204,12 @@ SSE_VERSION _SSE_Version=GetSSEVersion(); SSE_VERSION GetSSEVersion() { +#ifdef _MSC_VER int CPUInfo[4]; - __cpuid(CPUInfo, 0x80000000); + __cpuid(CPUInfo, 0); - // Maximum supported cpuid function. For example, Pentium M 755 returns 4 here. - uint MaxSupported=CPUInfo[0] & 0x7fffffff; + // Maximum supported cpuid function. + uint MaxSupported=CPUInfo[0]; if (MaxSupported>=7) { @@ -210,6 +229,18 @@ SSE_VERSION GetSSEVersion() if ((CPUInfo[3] & 0x2000000)!=0) return SSE_SSE; } +#elif defined(__GNUC__) + if (__builtin_cpu_supports("avx2")) + return SSE_AVX2; + if (__builtin_cpu_supports("sse4.1")) + return SSE_SSE41; + if (__builtin_cpu_supports("ssse3")) + return SSE_SSSE3; + if (__builtin_cpu_supports("sse2")) + return SSE_SSE2; + if (__builtin_cpu_supports("sse")) + return SSE_SSE; +#endif return SSE_NONE; } #endif diff --git a/unrar/system.hpp b/unrar/system.hpp index a56d6b7f..ff6a6693 100644 --- a/unrar/system.hpp +++ b/unrar/system.hpp @@ -21,7 +21,10 @@ void InitSystemOptions(int SleepTime); void SetPriority(int Priority); clock_t MonoClock(); void Wait(); -bool EmailFile(const wchar *FileName,const wchar *MailToW); +bool EmailFile(const std::wstring &FileName,std::wstring MailToW); +#ifdef _WIN_ALL +bool SetPrivilege(LPCTSTR PrivName); +#endif void Shutdown(POWER_MODE Mode); bool ShutdownCheckAnother(bool Open); @@ -30,7 +33,6 @@ HMODULE WINAPI LoadSysLibrary(const wchar *Name); bool IsUserAdmin(); #endif - #ifdef USE_SSE enum SSE_VERSION {SSE_NONE,SSE_SSE,SSE_SSE2,SSE_SSSE3,SSE_SSE41,SSE_AVX2}; SSE_VERSION GetSSEVersion(); diff --git a/unrar/threadmisc.cpp b/unrar/threadmisc.cpp index 02650290..cc935b12 100644 --- a/unrar/threadmisc.cpp +++ b/unrar/threadmisc.cpp @@ -123,6 +123,42 @@ uint GetNumberOfCPU() return sysctlbyname("hw.ncpu",&Count,&Size,NULL,0)==0 ? Count:1; #endif #else // !_UNIX + +#ifdef WIN32_CPU_GROUPS + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessaffinitymask + // "Starting with Windows 11 and Windows Server 2022, on a system with + // more than 64 processors, process and thread affinities span all + // processors in the system, across all processor groups, by default." + // Supposing there are 80 CPUs in 2 processor groups 40 CPUs each. + // Looks like, beginning from Windows 11 an app can use them all by default, + // not resorting to processor groups API. But if we use GetProcessAffinityMask + // to count CPUs, we would be limited to 40 CPUs only. So we call + // GetActiveProcessorCount() if it is available anf if there are multiple + // processor groups. For a single group we prefer the affinity aware + // GetProcessAffinityMask(). Out thread pool code handles the case + // with restricted processor group affinity. So we avoid the complicated + // code to calculate all processor groups affinity here, such as using + // GetLogicalProcessorInformationEx, and resort to GetActiveProcessorCount(). + HMODULE hKernel=GetModuleHandle(L"kernel32.dll"); + if (hKernel!=nullptr) + { + typedef DWORD (WINAPI *GETACTIVEPROCESSORCOUNT)(WORD GroupNumber); + GETACTIVEPROCESSORCOUNT pGetActiveProcessorCount=(GETACTIVEPROCESSORCOUNT)GetProcAddress(hKernel,"GetActiveProcessorCount"); + typedef WORD (WINAPI *GETACTIVEPROCESSORGROUPCOUNT)(); + GETACTIVEPROCESSORGROUPCOUNT pGetActiveProcessorGroupCount=(GETACTIVEPROCESSORGROUPCOUNT)GetProcAddress(hKernel,"GetActiveProcessorGroupCount"); + if (pGetActiveProcessorCount!=nullptr && pGetActiveProcessorGroupCount!=nullptr && + pGetActiveProcessorGroupCount()>1) + { + // Once the thread pool called SetThreadGroupAffinity(), + // GetProcessAffinityMask() below will return 0. So we shall always + // use GetActiveProcessorCount() here if there are multiple processor + // groups, which makes SetThreadGroupAffinity() call possible. + DWORD Count=pGetActiveProcessorCount(ALL_PROCESSOR_GROUPS); + return Count; + } + } +#endif + DWORD_PTR ProcessMask; DWORD_PTR SystemMask; @@ -148,3 +184,7 @@ uint GetNumberOfThreads() return MaxPoolThreads; return NumCPU; } + + + + diff --git a/unrar/threadpool.cpp b/unrar/threadpool.cpp index 8c63a8bd..1d211316 100644 --- a/unrar/threadpool.cpp +++ b/unrar/threadpool.cpp @@ -1,5 +1,6 @@ #include "rar.hpp" + #ifdef RAR_SMP #include "threadmisc.cpp" @@ -92,11 +93,103 @@ ThreadPool::~ThreadPool() void ThreadPool::CreateThreads() { - for(uint I=0;I1) // If we have multiple processor groups. + { + if (I>=CumulativeGroupSize) // Filled the processor group, go to next. + { + if (++CurGroupNumber>=GroupCount) + { + // If we exceeded the group number, such as when user specified + // -mt64 for lower core count, start assigning from beginning. + CurGroupNumber=0; + CumulativeGroupSize=0; + } + // Current group size. + CurGroupSize=pGetActiveProcessorCount(CurGroupNumber); + // Size of all preceding groups including the current. + CumulativeGroupSize+=CurGroupSize; + } + GROUP_AFFINITY GroupAffinity; + pGetThreadGroupAffinity(hThread,&GroupAffinity); + + // Since normally before Windows 11 all threads belong to same source + // group, we could set this value only once. But we set it every time + // in case we'll decide for some reason to use it to rearrange threads + // from different source groups in Windows 11+. + uint SrcGroupSize=pGetActiveProcessorCount(GroupAffinity.Group); + + // Shifting by 64 would be the undefined behavior, so we treat 64 separately. + KAFFINITY SrcGroupMask=(KAFFINITY)(SrcGroupSize==64 ? (uint64)0xffffffffffffffff:(uint64(1)<Year; - st.wMonth=lt->Month; - st.wDay=lt->Day; - st.wHour=lt->Hour; - st.wMinute=lt->Minute; - st.wSecond=lt->Second; + st.wYear=(WORD)lt->Year; + st.wMonth=(WORD)lt->Month; + st.wDay=(WORD)lt->Day; + st.wHour=(WORD)lt->Hour; + st.wMinute=(WORD)lt->Minute; + st.wSecond=(WORD)lt->Second; st.wMilliseconds=0; st.wDayOfWeek=0; - FILETIME lft; - if (SystemTimeToFileTime(&st,&lft)) - { - FILETIME ft; - - if (WinNT() < WNT_VISTA) - { - // TzSpecificLocalTimeToSystemTime based code produces 1 hour error on XP. - LocalFileTimeToFileTime(&lft,&ft); - } - else - { - // Reverse procedure which we do in GetLocal. - SYSTEMTIME st1,st2; - FileTimeToSystemTime(&lft,&st2); // st2 might be unequal to st, because we added lt->Reminder to lft. - TzSpecificLocalTimeToSystemTime(NULL,&st2,&st1); - SystemTimeToFileTime(&st1,&ft); - - // Correct precision loss (low 4 decimal digits) in FileTimeToSystemTime. - FILETIME rft; - SystemTimeToFileTime(&st2,&rft); - uint64 Corrected=INT32TO64(lft.dwHighDateTime,lft.dwLowDateTime)- - INT32TO64(rft.dwHighDateTime,rft.dwLowDateTime)+ - INT32TO64(ft.dwHighDateTime,ft.dwLowDateTime); - ft.dwLowDateTime=(DWORD)Corrected; - ft.dwHighDateTime=(DWORD)(Corrected>>32); - } - SetWinFT(&ft); + FILETIME ft; + if (WinNT() < WNT_VISTA) + { + // TzSpecificLocalTimeToSystemTime based code produces 1 hour error on XP. + FILETIME lft; + SystemTimeToFileTime(&st,&lft); + LocalFileTimeToFileTime(&lft,&ft); } else - Reset(); + { + // Reverse procedure which we do in GetLocal. + SYSTEMTIME st1; + TzSpecificLocalTimeToSystemTime(NULL,&st,&st1); + SystemTimeToFileTime(&st1,&ft); + } + + SetWinFT(&ft); + #else struct tm t; @@ -183,8 +170,7 @@ void RarTime::SetUnix(time_t ut) // Get the high precision Unix time in nanoseconds since 01-01-1970. uint64 RarTime::GetUnixNS() { - // 11644473600000000000 - number of ns between 01-01-1601 and 01-01-1970. - uint64 ushift=INT32TO64(0xA1997B0B,0x4C6A0000); + const uint64 ushift=11644473600000000000ULL; // ns between 01-01-1601 and 01-01-1970. return itime*(1000000000/TICKS_PER_SECOND)-ushift; } @@ -192,8 +178,7 @@ uint64 RarTime::GetUnixNS() // Set the high precision Unix time in nanoseconds since 01-01-1970. void RarTime::SetUnixNS(uint64 ns) { - // 11644473600000000000 - number of ns between 01-01-1601 and 01-01-1970. - uint64 ushift=INT32TO64(0xA1997B0B,0x4C6A0000); + const uint64 ushift=11644473600000000000ULL; // ns between 01-01-1601 and 01-01-1970. itime=(ns+ushift)/(1000000000/TICKS_PER_SECOND); } @@ -273,12 +258,12 @@ void RarTime::SetAgeText(const wchar *TimeText) uint Seconds=0,Value=0; for (uint I=0;TimeText[I]!=0;I++) { - int Ch=TimeText[I]; + wchar Ch=TimeText[I]; if (IsDigit(Ch)) Value=Value*10+Ch-'0'; else { - switch(etoupper(Ch)) + switch(etoupperw(Ch)) { case 'D': Seconds+=Value*24*3600; @@ -327,14 +312,14 @@ void RarTime::Adjust(int64 ns) #ifndef SFX_MODULE -const wchar *GetMonthName(int Month) +const wchar *GetMonthName(uint Month) { return uiGetMonthName(Month); } #endif -bool IsLeapYear(int Year) +bool IsLeapYear(uint Year) { return (Year&3)==0 && (Year%100!=0 || Year%400==0); } diff --git a/unrar/timefn.hpp b/unrar/timefn.hpp index 52713616..5b11e867 100644 --- a/unrar/timefn.hpp +++ b/unrar/timefn.hpp @@ -22,6 +22,17 @@ class RarTime // Internal time representation in 1/TICKS_PER_SECOND since 01.01.1601. // We use nanoseconds here to handle the high precision Unix time. + // It allows dates up to July 2185. + // + // If we'll ever need to extend the date range, we can define a lower + // precision Windows version of TICKS_PER_SECOND. But then Unix and Windows + // versions can differ in least significant digits of "lt" time output + // for Unix archives. + // Alternatively we can introduce 'bool HighPrecision' set to true + // in SetUnixNS() and TicksPerSecond() instead of constant above. + // It might be more reliable than defining TicksPerSecond variable, + // which wouldn't survive memset of any structure hosting RarTime. + // We would need to eliminate all such memsets in the entire code first. uint64 itime; public: // RarLocalTime::Reminder precision. Must be equal to TICKS_PER_SECOND. @@ -29,12 +40,12 @@ class RarTime static const uint REMINDER_PRECISION = TICKS_PER_SECOND; public: RarTime() {Reset();} - bool operator == (RarTime &rt) {return itime==rt.itime;} - bool operator != (RarTime &rt) {return itime!=rt.itime;} - bool operator < (RarTime &rt) {return itime (RarTime &rt) {return itime>rt.itime;} - bool operator >= (RarTime &rt) {return itime>rt.itime || itime==rt.itime;} + bool operator == (const RarTime &rt) const {return itime==rt.itime;} + bool operator != (const RarTime &rt) const {return itime!=rt.itime;} + bool operator < (const RarTime &rt) const {return itime (const RarTime &rt) const {return itime>rt.itime;} + bool operator >= (const RarTime &rt) const {return itime>rt.itime || itime==rt.itime;} void GetLocal(RarLocalTime *lt); void SetLocal(RarLocalTime *lt); @@ -59,7 +70,7 @@ class RarTime void Adjust(int64 ns); }; -const wchar *GetMonthName(int Month); -bool IsLeapYear(int Year); +const wchar *GetMonthName(uint Month); +bool IsLeapYear(uint Year); #endif diff --git a/unrar/ui.hpp b/unrar/ui.hpp index 2654387c..e25c111a 100644 --- a/unrar/ui.hpp +++ b/unrar/ui.hpp @@ -19,27 +19,29 @@ enum UIMESSAGE_CODE { UIERROR_SUBHEADERBROKEN, UIERROR_SUBHEADERUNKNOWN, UIERROR_SUBHEADERDATABROKEN, UIERROR_RRDAMAGED, UIERROR_UNKNOWNMETHOD, UIERROR_UNKNOWNENCMETHOD, UIERROR_RENAMING, UIERROR_NEWERRAR, - UIERROR_NOTSFX, UIERROR_OLDTOSFX, - UIERROR_WRONGSFXVER, UIERROR_HEADENCMISMATCH, UIERROR_DICTOUTMEM, + UIERROR_NOTSFX, UIERROR_OLDTOSFX,UIERROR_WRONGSFXVER, + UIERROR_HEADENCMISMATCH, UIERROR_DICTOUTMEM,UIERROR_EXTRDICTOUTMEM, UIERROR_USESMALLERDICT, UIERROR_MODIFYUNKNOWN, UIERROR_MODIFYOLD, UIERROR_MODIFYLOCKED, UIERROR_MODIFYVOLUME, UIERROR_NOTVOLUME, UIERROR_NOTFIRSTVOLUME, UIERROR_RECVOLLIMIT, UIERROR_RECVOLDIFFSETS, UIERROR_RECVOLALLEXIST, UIERROR_RECVOLFOUND, UIERROR_RECONSTRUCTING, UIERROR_RECVOLCANNOTFIX, UIERROR_OPFAILED, UIERROR_UNEXPEOF, - UIERROR_BADARCHIVE, UIERROR_CMTBROKEN, UIERROR_INVALIDNAME, - UIERROR_NEWRARFORMAT, UIERROR_NOTSUPPORTED, UIERROR_ENCRNOTSUPPORTED, - UIERROR_RARZIPONLY, UIERROR_REPAIROLDFORMAT, UIERROR_NOFILESREPAIRED, - UIERROR_NOFILESTOADD, UIERROR_NOFILESTODELETE, UIERROR_NOFILESTOEXTRACT, - UIERROR_MISSINGVOL, UIERROR_NEEDPREVVOL, UIERROR_UNKNOWNEXTRA, - UIERROR_CORRUPTEXTRA, UIERROR_NTFSREQUIRED, UIERROR_ZIPVOLSFX, - UIERROR_FILERO, UIERROR_TOOLARGESFX, UIERROR_NOZIPSFX, UIERROR_EMAIL, - UIERROR_ACLGET, UIERROR_ACLBROKEN, UIERROR_ACLUNKNOWN, UIERROR_ACLSET, - UIERROR_STREAMBROKEN, UIERROR_STREAMUNKNOWN, UIERROR_INCOMPATSWITCH, - UIERROR_PATHTOOLONG, UIERROR_DIRSCAN, UIERROR_UOWNERGET, - UIERROR_UOWNERBROKEN, UIERROR_UOWNERGETOWNERID, UIERROR_UOWNERGETGROUPID, - UIERROR_UOWNERSET, UIERROR_ULINKREAD, UIERROR_ULINKEXIST, - UIERROR_OPENPRESERVEATIME, UIERROR_READERRTRUNCATED, UIERROR_READERRCOUNT, - UIERROR_DIRNAMEEXISTS, + UIERROR_TRUNCSERVICE, UIERROR_BADARCHIVE, UIERROR_CMTBROKEN, + UIERROR_INVALIDNAME, UIERROR_NEWRARFORMAT, UIERROR_NOTSUPPORTED, + UIERROR_ENCRNOTSUPPORTED, UIERROR_RARZIPONLY, UIERROR_REPAIROLDFORMAT, + UIERROR_NOFILESREPAIRED, UIERROR_NOFILESTOADD, UIERROR_NOFILESTODELETE, + UIERROR_NOFILESTOEXTRACT, UIERROR_MISSINGVOL, UIERROR_NEEDPREVVOL, + UIERROR_UNKNOWNEXTRA, UIERROR_CORRUPTEXTRA, UIERROR_NTFSREQUIRED, + UIERROR_ZIPVOLSFX, UIERROR_FILERO, UIERROR_TOOLARGESFX, UIERROR_NOZIPSFX, + UIERROR_NEEEDSFX64, UIERROR_EMAIL, UIERROR_ACLGET, UIERROR_ACLBROKEN, + UIERROR_ACLUNKNOWN, UIERROR_ACLSET, UIERROR_STREAMBROKEN, + UIERROR_STREAMUNKNOWN, UIERROR_INCOMPATSWITCH, UIERROR_PATHTOOLONG, + UIERROR_DIRSCAN, UIERROR_UOWNERGET, UIERROR_UOWNERBROKEN, + UIERROR_UOWNERGETOWNERID, UIERROR_UOWNERGETGROUPID, UIERROR_UOWNERSET, + UIERROR_ULINKREAD, UIERROR_ULINKEXIST, UIERROR_OPENPRESERVEATIME, + UIERROR_READERRTRUNCATED, UIERROR_READERRCOUNT, UIERROR_DIRNAMEEXISTS, + UIERROR_TRUNCPSW, UIERROR_ADJUSTVALUE, UIERROR_SKIPUNSAFELINK, + UIERROR_AMBIGUOUSDATA, UIMSG_FIRST, UIMSG_STRING, UIMSG_BUILD, UIMSG_RRSEARCH, UIMSG_ANALYZEFILEDATA, @@ -49,6 +51,7 @@ enum UIMESSAGE_CODE { UIMSG_CORRECTINGNAME, UIMSG_BADARCHIVE, UIMSG_CREATING, UIMSG_RENAMING, UIMSG_RECVOLCALCCHECKSUM, UIMSG_RECVOLFOUND, UIMSG_RECVOLMISSING, UIMSG_MISSINGVOL, UIMSG_RECONSTRUCTING, UIMSG_CHECKSUM, UIMSG_FAT32SIZE, + UIMSG_SKIPENCARC, UIMSG_FILERENAME, UIWAIT_FIRST, UIWAIT_DISKFULLNEXT, UIWAIT_FCREATEERROR, UIWAIT_BADPSW, @@ -65,7 +68,8 @@ enum UIMESSAGE_CODE { // Flags for uiAskReplace function. enum UIASKREP_FLAGS { - UIASKREP_F_NORENAME=1,UIASKREP_F_EXCHSRCDEST=2,UIASKREP_F_SHOWNAMEONLY=4 + UIASKREP_F_NORENAME=1,UIASKREP_F_EXCHSRCDEST=2,UIASKREP_F_SHOWNAMEONLY=4, + UIASKREP_F_SINGLEFILE=8 }; // Codes returned by uiAskReplace. Note that uiAskReplaceEx returns only @@ -75,19 +79,26 @@ enum UIASKREP_RESULT { UIASKREP_R_RENAME,UIASKREP_R_RENAMEAUTO,UIASKREP_R_CANCEL,UIASKREP_R_UNUSED }; -UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags); -UIASKREP_RESULT uiAskReplaceEx(RAROptions *Cmd,wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags); +UIASKREP_RESULT uiAskReplace(std::wstring &Name,int64 FileSize,RarTime *FileTime,uint Flags); +UIASKREP_RESULT uiAskReplaceEx(CommandData *Cmd,std::wstring &Name,int64 FileSize,RarTime *FileTime,uint Flags); +bool GetAutoRenamedName(std::wstring &Name); void uiInit(SOUND_NOTIFY_MODE Sound); -void uiStartArchiveExtract(bool Extract,const wchar *ArcName); -bool uiStartFileExtract(const wchar *FileName,bool Extract,bool Test,bool Skip); +void uiStartArchiveExtract(bool Extract,const std::wstring &ArcName); +bool uiStartFileExtract(const std::wstring &FileName,bool Extract,bool Test,bool Skip); void uiExtractProgress(int64 CurFileSize,int64 TotalFileSize,int64 CurSize,int64 TotalSize); void uiProcessProgress(const char *Command,int64 CurSize,int64 TotalSize); -enum UIPASSWORD_TYPE {UIPASSWORD_GLOBAL,UIPASSWORD_FILE,UIPASSWORD_ARCHIVE}; -bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password); +enum UIPASSWORD_TYPE +{ + UIPASSWORD_GLOBAL, // For -p, -hp without parameter or Ctrl+P in WinRAR. + UIPASSWORD_FILE, // Extracting an encrypted file. + UIPASSWORD_ARCHIVE, // Extracting or opening an encrypted header archive. +}; + +bool uiGetPassword(UIPASSWORD_TYPE Type,const std::wstring &FileName,SecPassword *Password,CheckPassword *CheckPwd); bool uiIsGlobalPasswordSet(); enum UIALARM_TYPE {UIALARM_ERROR, UIALARM_INFO, UIALARM_QUESTION}; @@ -95,14 +106,17 @@ void uiAlarm(UIALARM_TYPE Type); void uiEolAfterMsg(); -bool uiAskNextVolume(wchar *VolName,size_t MaxSize); +bool uiAskNextVolume(std::wstring &VolName); #if !defined(SILENT) && !defined(SFX_MODULE) -void uiAskRepeatRead(const wchar *FileName,bool &Ignore,bool &All,bool &Retry,bool &Quit); +void uiAskRepeatRead(const std::wstring &FileName,bool &Ignore,bool &All,bool &Retry,bool &Quit); #endif -bool uiAskRepeatWrite(const wchar *FileName,bool DiskFull); +bool uiAskRepeatWrite(const std::wstring &FileName,bool DiskFull); + +bool uiDictLimit(CommandData *Cmd,const std::wstring &FileName,uint64 DictSize,uint64 MaxDictSize); #ifndef SFX_MODULE -const wchar *uiGetMonthName(int Month); +const wchar *uiGetMonthName(uint Month); +const wchar *uiGetWeekDayName(uint Day); #endif class uiMsgStore @@ -130,6 +144,12 @@ class uiMsgStore Str[StrSize++]=s; return *this; } + uiMsgStore& operator << (const std::wstring &s) + { + if (StrSize void uiMsg(UIMESSAGE_CODE Code,T1 a1) +template void uiMsgBase(uiMsgStore &Store,T1&& a1,TN&&... aN) { - uiMsgStore Store(Code); + // Process first parameter and pass the rest to same uiMsgBase. Store< void uiMsg(UIMESSAGE_CODE Code,T1 a1,T2 a2) -{ - uiMsgStore Store(Code); - Store< void uiMsg(UIMESSAGE_CODE code,T1 a1,T2 a2,T3 a3) +// Use variadic templates. +// +// We must pass variable parameters by reference, so no temporary copies are +// created for custom string objects like CStringBase in 7-Zip decompression +// code. Such temporary copies would be destroyed inside of recursive +// uiMsgBase calls, leaving us with Str[] items pointing at released memory. +// Since we pass integer values as well, we can't use & references +// and must resort to && rvalue references. +template void uiMsg(UIMESSAGE_CODE Code,TN&&... aN) { - uiMsgStore Store(code); - Store<Overwrite==OVERWRITE_NONE) return UIASKREP_R_SKIP; #if !defined(SFX_MODULE) && !defined(SILENT) // Must be before Cmd->AllYes check or -y switch would override -or. - if (Cmd->Overwrite==OVERWRITE_AUTORENAME && GetAutoRenamedName(Name,MaxNameSize)) + if (Cmd->Overwrite==OVERWRITE_AUTORENAME && GetAutoRenamedName(Name)) return UIASKREP_R_REPLACE; #endif - // This check must be after OVERWRITE_AUTORENAME processing or -y switch - // would override -or. - if (Cmd->AllYes || Cmd->Overwrite==OVERWRITE_ALL) - { - PrepareToDelete(Name); - return UIASKREP_R_REPLACE; - } - - wchar NewName[NM]; - wcsncpyz(NewName,Name,ASIZE(NewName)); - UIASKREP_RESULT Choice=uiAskReplace(NewName,ASIZE(NewName),FileSize,FileTime,Flags); + std::wstring NewName=Name; + UIASKREP_RESULT Choice=Cmd->AllYes || Cmd->Overwrite==OVERWRITE_ALL ? + UIASKREP_R_REPLACE : uiAskReplace(NewName,FileSize,FileTime,Flags); if (Choice==UIASKREP_R_REPLACE || Choice==UIASKREP_R_REPLACEALL) + { PrepareToDelete(Name); + // Overwrite the link itself instead of its target. + // For normal files we prefer to inherit file attributes, permissions + // and hard links. + FindData FD; + if (FindFile::FastFind(Name,&FD,true) && FD.IsLink) + DelFile(Name); + } + if (Choice==UIASKREP_R_REPLACEALL) { Cmd->Overwrite=OVERWRITE_ALL; @@ -46,16 +47,16 @@ UIASKREP_RESULT uiAskReplaceEx(RAROptions *Cmd,wchar *Name,size_t MaxNameSize,in } if (Choice==UIASKREP_R_RENAME) { - if (PointToName(NewName)==NewName) - SetName(Name,NewName,MaxNameSize); + if (GetNamePos(NewName)==0) + SetName(Name,NewName); else - wcsncpyz(Name,NewName,MaxNameSize); + Name=NewName; if (FileExist(Name)) - return uiAskReplaceEx(Cmd,Name,MaxNameSize,FileSize,FileTime,Flags); + return uiAskReplaceEx(Cmd,Name,FileSize,FileTime,Flags); return UIASKREP_R_REPLACE; } #if !defined(SFX_MODULE) && !defined(SILENT) - if (Choice==UIASKREP_R_RENAMEAUTO && GetAutoRenamedName(Name,MaxNameSize)) + if (Choice==UIASKREP_R_RENAMEAUTO && GetAutoRenamedName(Name)) { Cmd->Overwrite=OVERWRITE_AUTORENAME; return UIASKREP_R_REPLACE; @@ -63,3 +64,23 @@ UIASKREP_RESULT uiAskReplaceEx(RAROptions *Cmd,wchar *Name,size_t MaxNameSize,in #endif return Choice; } + + +bool GetAutoRenamedName(std::wstring &Name) +{ + std::wstring Ext=GetExt(Name); + for (uint FileVer=1;FileVer<1000000;FileVer++) + { + std::wstring NewName=Name; + RemoveExt(NewName); + wchar Ver[10]; + itoa(FileVer,Ver,ASIZE(Ver)); + NewName = NewName + L"(" + Ver + L")" + Ext; + if (!FileExist(NewName)) + { + Name=NewName; + return true; + } + } + return false; +} diff --git a/unrar/uiconsole.cpp b/unrar/uiconsole.cpp index f29ca477..cb7d58b5 100644 --- a/unrar/uiconsole.cpp +++ b/unrar/uiconsole.cpp @@ -1,12 +1,11 @@ -static bool AnyMessageDisplayed=0; // For console -idn switch. +static bool AnyMessageDisplayed=false; // For console -idn switch. // Purely user interface function. Gets and returns user input. -UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags) +UIASKREP_RESULT uiAskReplace(std::wstring &Name,int64 FileSize,RarTime *FileTime,uint Flags) { wchar SizeText1[20],DateStr1[50],SizeText2[20],DateStr2[50]; - FindData ExistingFD; - memset(&ExistingFD,0,sizeof(ExistingFD)); // In case find fails. + FindData ExistingFD={}; // Init in case find fails. FindFile::FastFind(Name,&ExistingFD); itoa(ExistingFD.Size,SizeText1,ASIZE(SizeText1)); ExistingFD.mtime.GetText(DateStr1,ASIZE(DateStr1),false); @@ -14,16 +13,16 @@ UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTi if (FileSize==INT64NDF || FileTime==NULL) { eprintf(L"\n"); - eprintf(St(MAskOverwrite),Name); + eprintf(St(MAskOverwrite),Name.c_str()); } else { itoa(FileSize,SizeText2,ASIZE(SizeText2)); FileTime->GetText(DateStr2,ASIZE(DateStr2),false); if ((Flags & UIASKREP_F_EXCHSRCDEST)==0) - eprintf(St(MAskReplace),Name,SizeText1,DateStr1,SizeText2,DateStr2); + eprintf(St(MAskReplace),Name.c_str(),SizeText1,DateStr1,SizeText2,DateStr2); else - eprintf(St(MAskReplace),Name,SizeText2,DateStr2,SizeText1,DateStr1); + eprintf(St(MAskReplace),Name.c_str(),SizeText2,DateStr2,SizeText1,DateStr1); } bool AllowRename=(Flags & UIASKREP_F_NORENAME)==0; @@ -46,10 +45,8 @@ UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTi if (AllowRename && Choice==5) { mprintf(St(MAskNewName)); - if (getwstr(Name,MaxNameSize)) - return UIASKREP_R_RENAME; - else - return UIASKREP_R_SKIP; // Process fwgets failure as if user answered 'No'. + getwstr(Name); + return UIASKREP_R_RENAME; } return UIASKREP_R_CANCEL; } @@ -57,13 +54,13 @@ UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTi -void uiStartArchiveExtract(bool Extract,const wchar *ArcName) +void uiStartArchiveExtract(bool Extract,const std::wstring &ArcName) { - mprintf(St(Extract ? MExtracting : MExtrTest), ArcName); + mprintf(St(Extract ? MExtracting : MExtrTest), ArcName.c_str()); } -bool uiStartFileExtract(const wchar *FileName,bool Extract,bool Test,bool Skip) +bool uiStartFileExtract(const std::wstring &FileName,bool Extract,bool Test,bool Skip) { return true; } @@ -71,7 +68,10 @@ bool uiStartFileExtract(const wchar *FileName,bool Extract,bool Test,bool Skip) void uiExtractProgress(int64 CurFileSize,int64 TotalFileSize,int64 CurSize,int64 TotalSize) { - int CurPercent=ToPercent(CurSize,TotalSize); + // We set the total size to 0 to update only the current progress and keep + // the total progress intact in WinRAR. Unlike WinRAR, console RAR has only + // the total progress and updates it with current values in such case. + int CurPercent=TotalSize!=0 ? ToPercent(CurSize,TotalSize) : ToPercent(CurFileSize,TotalFileSize); mprintf(L"\b\b\b\b%3d%%",CurPercent); } @@ -85,7 +85,18 @@ void uiProcessProgress(const char *Command,int64 CurSize,int64 TotalSize) void uiMsgStore::Msg() { - AnyMessageDisplayed=true; + // When creating volumes, AnyMessageDisplayed must be reset for UIEVENT_NEWARCHIVE, + // so it ignores this and all earlier messages like UIEVENT_PROTECTEND + // and UIEVENT_PROTECTEND, because they precede "Creating archive" message + // and do not interfere with -idn and file names. If we do not ignore them, + // uiEolAfterMsg() in uiStartFileAddit() can cause unneeded carriage return + // in archiving percent after creating a new volume with -v -idn (and -rr + // for UIEVENT_PROTECT*) switches. AnyMessageDisplayed is set for messages + // after UIEVENT_NEWARCHIVE, so archiving percent with -idn is moved to + // next line and does not delete their last characters. + // Similarly we ignore UIEVENT_RRTESTINGEND for volumes, because it is issued + // before "Testing archive" and would add an excessive \n otherwise. + AnyMessageDisplayed=(Code!=UIEVENT_NEWARCHIVE && Code!=UIEVENT_RRTESTINGEND); switch(Code) { @@ -169,6 +180,7 @@ void uiMsgStore::Msg() Log(NULL,St(MNeedAdmin)); break; case UIERROR_ARCBROKEN: + mprintf(L"\n"); // So it is not merged with preceding UIERROR_HEADERBROKEN. Log(Str[0],St(MErrBrokenArc)); break; case UIERROR_HEADERBROKEN: @@ -222,9 +234,27 @@ void uiMsgStore::Msg() case UIERROR_RECVOLCANNOTFIX: mprintf(St(MRecVolCannotFix)); break; + case UIERROR_EXTRDICTOUTMEM: + Log(Str[0],St(MExtrDictOutMem),Num[0]); +#ifdef _WIN_32 + Log(Str[0],St(MSuggest64bit)); +#endif + break; case UIERROR_UNEXPEOF: Log(Str[0],St(MLogUnexpEOF)); break; + case UIERROR_TRUNCSERVICE: + { + const wchar *Type=nullptr; + if (wcscmp(Str[1],SUBHEAD_TYPE_QOPEN)==0) + Type=St(MHeaderQO); + else + if (wcscmp(Str[1],SUBHEAD_TYPE_RR)==0) + Type=St(MHeaderRR); + if (Type!=nullptr) + Log(Str[0],St(MTruncService),Type); + } + break; case UIERROR_BADARCHIVE: Log(Str[0],St(MBadArc),Str[0]); break; @@ -236,6 +266,9 @@ void uiMsgStore::Msg() mprintf(L"\n"); // Needed when called from CmdExtract::ExtractCurrentFile. break; #ifndef SFX_MODULE + case UIERROR_OPFAILED: + Log(NULL,St(MOpFailed)); + break; case UIERROR_NEWRARFORMAT: Log(Str[0],St(MNewRarFormat)); break; @@ -245,6 +278,7 @@ void uiMsgStore::Msg() break; case UIERROR_MISSINGVOL: Log(Str[0],St(MAbsNextVol),Str[0]); + mprintf(L" "); // For progress percent. break; #ifndef SFX_MODULE case UIERROR_NEEDPREVVOL: @@ -318,6 +352,16 @@ void uiMsgStore::Msg() case UIERROR_DIRNAMEEXISTS: Log(NULL,St(MDirNameExists)); break; + case UIERROR_TRUNCPSW: + eprintf(St(MTruncPsw),Num[0]); + eprintf(L"\n"); + break; + case UIERROR_ADJUSTVALUE: + Log(NULL,St(MAdjustValue),Str[0],Str[1]); + break; + case UIERROR_SKIPUNSAFELINK: + Log(NULL,St(MSkipUnsafeLink),Str[0],Str[1]); + break; #ifndef SFX_MODULE case UIMSG_STRING: @@ -358,7 +402,9 @@ void uiMsgStore::Msg() mprintf(St(MFAT32Size)); mprintf(L" "); // For progress percent. break; - + case UIMSG_SKIPENCARC: + Log(NULL,St(MSkipEncArc),Str[0]); + break; case UIEVENT_RRTESTINGSTART: @@ -368,7 +414,8 @@ void uiMsgStore::Msg() } -bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) +bool uiGetPassword(UIPASSWORD_TYPE Type,const std::wstring &FileName, + SecPassword *Password,CheckPassword *CheckPwd) { // Unlike GUI we cannot provide Cancel button here, so we use the empty // password to abort. Otherwise user not knowing a password would need to @@ -403,14 +450,14 @@ void uiAlarm(UIALARM_TYPE Type) -bool uiAskNextVolume(wchar *VolName,size_t MaxSize) +bool uiAskNextVolume(std::wstring &VolName) { - eprintf(St(MAskNextVol),VolName); + eprintf(St(MAskNextVol),VolName.c_str()); return Ask(St(MContinueQuit))!=2; } -void uiAskRepeatRead(const wchar *FileName,bool &Ignore,bool &All,bool &Retry,bool &Quit) +void uiAskRepeatRead(const std::wstring &FileName,bool &Ignore,bool &All,bool &Retry,bool &Quit) { eprintf(St(MErrReadInfo)); int Code=Ask(St(MIgnoreAllRetryQuit)); @@ -422,22 +469,53 @@ void uiAskRepeatRead(const wchar *FileName,bool &Ignore,bool &All,bool &Retry,bo } -bool uiAskRepeatWrite(const wchar *FileName,bool DiskFull) +bool uiAskRepeatWrite(const std::wstring &FileName,bool DiskFull) { mprintf(L"\n"); - Log(NULL,St(DiskFull ? MNotEnoughDisk:MErrWrite),FileName); + Log(NULL,St(DiskFull ? MNotEnoughDisk:MErrWrite),FileName.c_str()); return Ask(St(MRetryAbort))==1; } +bool uiDictLimit(CommandData *Cmd,const std::wstring &FileName,uint64 DictSize,uint64 MaxDictSize) +{ + mprintf(L"\n%s",FileName.c_str()); + const uint64 GB=1024*1024*1024; // We display sizes in GB here. + + // Include the reminder for switches like -md6400m. + DictSize=DictSize/GB+(DictSize%GB!=0 ? 1:0); + + // Exclude reminder for in case it is less than archive dictionary size, + // but still more than nearest GB value. Otherwise we could have message + // like "7 GB dictionary exceeds 7 GB limit", where first 7 might be actually + // 6272 MB and second is 6400 MB. + MaxDictSize/=GB; + + mprintf(St(MDictNotAllowed),(uint)DictSize,(uint)MaxDictSize,(uint)DictSize); + mprintf(St(MDictExtrAnyway),(uint)DictSize,(uint)DictSize); + mprintf(L"\n"); + return false; // Stop extracting. +} + + #ifndef SFX_MODULE -const wchar *uiGetMonthName(int Month) +const wchar *uiGetMonthName(uint Month) { static MSGID MonthID[12]={ MMonthJan,MMonthFeb,MMonthMar,MMonthApr,MMonthMay,MMonthJun, MMonthJul,MMonthAug,MMonthSep,MMonthOct,MMonthNov,MMonthDec }; - return St(MonthID[Month]); + return MonthCallback!=nullptr && + Cmd->Callback(UCM_LARGEDICT,Cmd->UserData,(LPARAM)(DictSize/1024),(LPARAM)(MaxDictSize/1024))==1) + return true; // Continue extracting if unrar.dll callback permits it. +#endif + return false; // Stop extracting. +} + + #ifndef SFX_MODULE -const wchar *uiGetMonthName(int Month) +const wchar *uiGetMonthName(uint Month) +{ + return L""; +} + + +const wchar *uiGetWeekDayName(uint Day) { return L""; } diff --git a/unrar/ulinks.cpp b/unrar/ulinks.cpp index 1656824f..4dece439 100644 --- a/unrar/ulinks.cpp +++ b/unrar/ulinks.cpp @@ -1,18 +1,23 @@ -static bool UnixSymlink(const char *Target,const wchar *LinkName,RarTime *ftm,RarTime *fta) +static bool UnixSymlink(CommandData *Cmd,const std::string &Target,const wchar *LinkName,RarTime *ftm,RarTime *fta) { - CreatePath(LinkName,true); + CreatePath(LinkName,true,Cmd->DisableNames); + + // Overwrite prompt was already issued and confirmed earlier, so we can + // remove existing symlink or regular file here. PrepareToDelete was also + // called earlier inside of uiAskReplaceEx. DelFile(LinkName); - char LinkNameA[NM]; - WideToChar(LinkName,LinkNameA,ASIZE(LinkNameA)); - if (symlink(Target,LinkNameA)==-1) // Error. + + std::string LinkNameA; + WideToChar(LinkName,LinkNameA); + if (symlink(Target.c_str(),LinkNameA.c_str())==-1) // Error. { if (errno==EEXIST) uiMsg(UIERROR_ULINKEXIST,LinkName); else { - uiMsg(UIERROR_SLINKCREATE,UINULL,LinkName); + uiMsg(UIERROR_SLINKCREATE,L"",LinkName); ErrHandler.SetErrorCode(RARX_WARNING); } return false; @@ -24,14 +29,14 @@ static bool UnixSymlink(const char *Target,const wchar *LinkName,RarTime *ftm,Ra times[0].tv_nsec=fta->IsSet() ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW; times[1].tv_sec=ftm->GetUnix(); times[1].tv_nsec=ftm->IsSet() ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW; - utimensat(AT_FDCWD,LinkNameA,times,AT_SYMLINK_NOFOLLOW); + utimensat(AT_FDCWD,LinkNameA.c_str(),times,AT_SYMLINK_NOFOLLOW); #else struct timeval tv[2]; tv[0].tv_sec=fta->GetUnix(); tv[0].tv_usec=long(fta->GetUnixNS()%1000000000/1000); tv[1].tv_sec=ftm->GetUnix(); tv[1].tv_usec=long(ftm->GetUnixNS()%1000000000/1000); - lutimes(LinkNameA,tv); + lutimes(LinkNameA.c_str(),tv); #endif #endif @@ -45,20 +50,41 @@ static bool IsFullPath(const char *PathA) // Unix ASCII version. } -bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName) +// For security purpose we prefer to be sure that CharToWide completed +// successfully and even if it truncated a string for some reason, +// it didn't affect the number of path related characters we analyze +// in IsRelativeSymlinkSafe later. +// This check is likely to be excessive, but let's keep it anyway. +static bool SafeCharToWide(const std::string &Src,std::wstring &Dest) +{ + if (!CharToWide(Src,Dest) || Dest.empty()) + return false; + uint SrcChars=0,DestChars=0; + for (uint I=0;Src[I]!=0;I++) + if (Src[I]=='/' || Src[I]=='.') + SrcChars++; + for (uint I=0;Dest[I]!=0;I++) + if (Dest[I]=='/' || Dest[I]=='.') + DestChars++; + return SrcChars==DestChars; +} + + +static bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc, + const wchar *LinkName,bool &UpLink) { - char Target[NM]; if (IsLink(Arc.FileHead.FileAttr)) { size_t DataSize=(size_t)Arc.FileHead.PackSize; - if (DataSize>ASIZE(Target)-1) + if (DataSize>MAXPATHSIZE) return false; - if ((size_t)DataIO.UnpRead((byte *)Target,DataSize)!=DataSize) + std::vector TargetBuf(DataSize+1); + if ((size_t)DataIO.UnpRead((byte*)TargetBuf.data(),DataSize)!=DataSize) return false; - Target[DataSize]=0; + std::string Target(TargetBuf.data(),TargetBuf.size()); DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,1); - DataIO.UnpHash.Update(Target,strlen(Target)); + DataIO.UnpHash.Update(Target.data(),strlen(Target.data())); DataIO.UnpHash.Result(&Arc.FileHead.FileHash); // Return true in case of bad checksum, so link will be processed further @@ -66,40 +92,62 @@ bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const w if (!DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL)) return true; - wchar TargetW[NM]; - CharToWide(Target,TargetW,ASIZE(TargetW)); - // Check for *TargetW==0 to catch CharToWide failure. + std::wstring TargetW; + if (!SafeCharToWide(Target.data(),TargetW)) + return false; + TruncateAtZero(TargetW); // Use Arc.FileHead.FileName instead of LinkName, since LinkName // can include the destination path as a prefix, which can // confuse IsRelativeSymlinkSafe algorithm. - if (!Cmd->AbsoluteLinks && (*TargetW==0 || IsFullPath(TargetW) || - !IsRelativeSymlinkSafe(Cmd,Arc.FileHead.FileName,LinkName,TargetW))) + if (!Cmd->AbsoluteLinks && (IsFullPath(TargetW) || + !IsRelativeSymlinkSafe(Cmd,Arc.FileHead.FileName.c_str(),LinkName,TargetW.c_str()))) + { + uiMsg(UIERROR_SKIPUNSAFELINK,Arc.FileHead.FileName,TargetW); + ErrHandler.SetErrorCode(RARX_WARNING); return false; - return UnixSymlink(Target,LinkName,&Arc.FileHead.mtime,&Arc.FileHead.atime); + } + UpLink=Target.find("..")!=std::string::npos; + return UnixSymlink(Cmd,Target,LinkName,&Arc.FileHead.mtime,&Arc.FileHead.atime); } return false; } -bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd) +static bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd) { - char Target[NM]; - WideToChar(hd->RedirName,Target,ASIZE(Target)); + std::string Target; + WideToChar(hd->RedirName,Target); if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_JUNCTION) { - // Cannot create Windows absolute path symlinks in Unix. Only relative path - // Windows symlinks can be created here. RAR 5.0 used \??\ prefix + // Windows absolute path symlinks in Unix. RAR 5.0 used \??\ prefix // for Windows absolute symlinks, since RAR 5.1 /??/ is used. // We escape ? as \? to avoid "trigraph" warning - if (strncmp(Target,"\\??\\",4)==0 || strncmp(Target,"/\?\?/",4)==0) + if (starts_with(Target,"\\??\\") || starts_with(Target,"/\?\?/")) + { +#if 0 // 2024.12.26: Not used anymore. We unpack absolute Windows symlinks even in Unix. + uiMsg(UIERROR_SLINKCREATE,nullptr,L"\"" + hd->FileName + L"\" -> \"" + hd->RedirName + L"\""); + ErrHandler.SetErrorCode(RARX_WARNING); return false; - DosSlashToUnix(Target,Target,ASIZE(Target)); +#endif + + // 2024.12.26: User asked to unpack absolute Windows symlinks even in Unix. + Target=Target.substr(4); // Remove \??\ prefix. + } + DosSlashToUnix(Target,Target); } + + std::wstring TargetW; + if (!SafeCharToWide(Target,TargetW)) + return false; // Use hd->FileName instead of LinkName, since LinkName can include // the destination path as a prefix, which can confuse // IsRelativeSymlinkSafe algorithm. - if (!Cmd->AbsoluteLinks && (IsFullPath(Target) || - !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,hd->RedirName))) + if (!Cmd->AbsoluteLinks && (IsFullPath(TargetW) || + !IsRelativeSymlinkSafe(Cmd,hd->FileName.c_str(),Name,TargetW.c_str()))) + { + uiMsg(UIERROR_SKIPUNSAFELINK,hd->FileName,TargetW); + ErrHandler.SetErrorCode(RARX_WARNING); return false; - return UnixSymlink(Target,Name,&hd->mtime,&hd->atime); + } + return UnixSymlink(Cmd,Target,Name,&hd->mtime,&hd->atime); } diff --git a/unrar/unicode.cpp b/unrar/unicode.cpp index 641f6c89..a9d103db 100644 --- a/unrar/unicode.cpp +++ b/unrar/unicode.cpp @@ -128,6 +128,26 @@ bool CharToWide(const char *Src,wchar *Dest,size_t DestSize) } +bool WideToChar(const std::wstring &Src,std::string &Dest) +{ + // We need more than 1 char per wchar_t for DBCS and up to 4 for UTF-8. + std::vector DestA(4*Src.size()+1); // "+1" for terminating zero. + bool Result=WideToChar(Src.c_str(),DestA.data(),DestA.size()); + Dest=DestA.data(); + return Result; +} + + +bool CharToWide(const std::string &Src,std::wstring &Dest) +{ + // 2 wchar_t per char in case char is converted to UTF-16 surrogate pair. + std::vector DestW(2*Src.size()+1); // "+1" for terminating zero. + bool Result=CharToWide(Src.c_str(),DestW.data(),DestW.size()); + Dest=DestW.data(); + return Result; +} + + #if defined(_UNIX) && defined(MBFUNCTIONS) // Convert and restore mapped inconvertible Unicode characters. // We use it for extended ASCII names in Unix. @@ -229,10 +249,11 @@ void CharToWideMap(const char *Src,wchar *Dest,size_t DestSize,bool &Success) #endif -// SrcSize is in wide characters, not in bytes. -byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize) +// SrcSize is source data size in wide characters, not in bytes. +// DestSize is the maximum allowed destination size. +byte* WideToRaw(const wchar *Src,size_t SrcSize,byte *Dest,size_t DestSize) { - for (size_t I=0;I>8); @@ -243,6 +264,23 @@ byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize) } +// Store UTF-16 raw byte stream. +void WideToRaw(const std::wstring &Src,std::vector &Dest) +{ + for (wchar C : Src) + { + Dest.push_back((byte)C); + Dest.push_back((byte)(C>>8)); + } + // In STL version of this function we do not add the trailing zero. + // Otherwise we would need to remove it when restoring std::wstring + // from raw data. + + // Dest.push_back(0); // 2 bytes of trailing UTF-16 zero. + // Dest.push_back(0); +} + + wchar* RawToWide(const byte *Src,wchar *Dest,size_t DestSize) { for (size_t I=0;I &Src) +{ + std::wstring Dest; + for (size_t I=0;I+1>6)); + Dest.push_back(0x80|(c&0x3f)); + } + else + { + if (c>=0xd800 && c<=0xdbff && I=0xdc00 && Src[I]<=0xdfff) // Surrogate pair. + { + c=((c-0xd800)<<10)+(Src[I]-0xdc00)+0x10000; + I++; + } + if (c<0x10000) + { + Dest.push_back(0xe0|(c>>12)); + Dest.push_back(0x80|((c>>6)&0x3f)); + Dest.push_back(0x80|(c&0x3f)); + } + else + if (c < 0x200000) + { + Dest.push_back(0xf0|(c>>18)); + Dest.push_back(0x80|((c>>12)&0x3f)); + Dest.push_back(0x80|((c>>6)&0x3f)); + Dest.push_back(0x80|(c&0x3f)); + } + } + } +} + + + size_t WideToUtfSize(const wchar *Src) { size_t Size=0; @@ -396,6 +488,146 @@ bool UtfToWide(const char *Src,wchar *Dest,size_t DestSize) } +bool UtfToWide(const char *Src,std::wstring &Dest) +{ + bool Success=true; + Dest.clear(); + while (*Src!=0) + { + uint c=byte(*(Src++)),d; + if (c<0x80) + d=c; + else + if ((c>>5)==6) + { + if ((*Src&0xc0)!=0x80) + { + Success=false; + break; + } + d=((c&0x1f)<<6)|(*Src&0x3f); + Src++; + } + else + if ((c>>4)==14) + { + if ((Src[0]&0xc0)!=0x80 || (Src[1]&0xc0)!=0x80) + { + Success=false; + break; + } + d=((c&0xf)<<12)|((Src[0]&0x3f)<<6)|(Src[1]&0x3f); + Src+=2; + } + else + if ((c>>3)==30) + { + if ((Src[0]&0xc0)!=0x80 || (Src[1]&0xc0)!=0x80 || (Src[2]&0xc0)!=0x80) + { + Success=false; + break; + } + d=((c&7)<<18)|((Src[0]&0x3f)<<12)|((Src[1]&0x3f)<<6)|(Src[2]&0x3f); + Src+=3; + } + else + { + Success=false; + break; + } + if (d>0xffff) + { + if (d>0x10ffff) // UTF-8 must end at 0x10ffff according to RFC 3629. + { + Success=false; + continue; + } + if (sizeof(wchar_t)==2) // Use the surrogate pair. + { + Dest.push_back( ((d-0x10000)>>10)+0xd800 ); + Dest.push_back( (d&0x3ff)+0xdc00 ); + } + else + Dest.push_back( d ); + } + else + Dest.push_back( d ); + } + return Success; +} + + +/* +bool UtfToWide(const std::vector &Src,std::wstring &Dest) +{ + bool Success=true; + Dest.clear(); + for (size_t I=0;I>5)==6) + { + if (Src.size()-I<1 || (Src[I]&0xc0)!=0x80) + { + Success=false; + break; + } + d=((c&0x1f)<<6)|(Src[I]&0x3f); + I++; + } + else + if ((c>>4)==14) + { + if (Src.size()-I<2 || (Src[I]&0xc0)!=0x80 || (Src[I+1]&0xc0)!=0x80) + { + Success=false; + break; + } + d=((c&0xf)<<12)|((Src[I]&0x3f)<<6)|(Src[I+1]&0x3f); + I+=2; + } + else + if ((c>>3)==30) + { + if (Src.size()-I<3 || (Src[I]&0xc0)!=0x80 || (Src[I+1]&0xc0)!=0x80 || (Src[I+2]&0xc0)!=0x80) + { + Success=false; + break; + } + d=((c&7)<<18)|((Src[I]&0x3f)<<12)|((Src[I+1]&0x3f)<<6)|(Src[I+2]&0x3f); + I+=3; + } + else + { + Success=false; + break; + } + if (d>0xffff) + { + if (d>0x10ffff) // UTF-8 must end at 0x10ffff according to RFC 3629. + { + Success=false; + continue; + } + if (sizeof(Dest[0])==2) // Use the surrogate pair. + { + Dest.push_back( ((d-0x10000)>>10)+0xd800 ); + Dest.push_back( (d&0x3ff)+0xdc00 ); + } + else + Dest.push_back( d ); + } + else + Dest.push_back( d ); + } + return Success; +} +*/ + + // For zero terminated strings. bool IsTextUtf8(const byte *Src) { @@ -424,6 +656,40 @@ bool IsTextUtf8(const byte *Src,size_t SrcSize) int wcsicomp(const wchar *s1,const wchar *s2) { + // If strings are English or numeric, perform the fast comparison. + // It improves speed in cases like comparing against a lot of MOTW masks. + bool FastMode=true; + while (true) + { + // English uppercase, English lowercase and digit flags. + bool u1=*s1>='A' && *s1<='Z', l1=*s1>='a' && *s1<='z', d1=*s1>='0' && *s1<='9'; + bool u2=*s2>='A' && *s2<='Z', l2=*s2>='a' && *s2<='z', d2=*s2>='0' && *s2<='9'; + + // Fast comparison is impossible if both characters are not alphanumeric or 0. + if (!u1 && !l1 && !d1 && *s1!=0 && !u2 && !l2 && !d2 && *s2!=0) + { + FastMode=false; + break; + } + // Convert lowercase to uppercase, keep numeric and not alphanumeric as is. + wchar c1 = l1 ? *s1-'a'+'A' : *s1; + wchar c2 = l2 ? *s2-'a'+'A' : *s2; + + // If characters mistmatch, to return a proper value we must compare + // already converted, case insensitive characters instead of original ones. + // So we place a.txt before B.txt and can perform the correct case + // insensitive binary search in different string lists. + if (c1 != c2) + return c1 < c2 ? -1 : 1; + + if (*s1==0) + break; + s1++; + s2++; + } + if (FastMode) + return 0; + #ifdef _WIN_ALL return CompareStringW(LOCALE_USER_DEFAULT,NORM_IGNORECASE|SORT_STRINGSORT,s1,-1,s2,-1)-2; #else @@ -431,6 +697,11 @@ int wcsicomp(const wchar *s1,const wchar *s2) { wchar u1 = towupper(*s1); wchar u2 = towupper(*s2); + + // If characters mistmatch, to return a proper value we must compare + // already converted, case insensitive characters instead of original ones. + // So we place a.txt before B.txt and can perform the correct case + // insensitive binary search in different string lists. if (u1 != u2) return u1 < u2 ? -1 : 1; if (*s1==0) @@ -449,8 +720,10 @@ int wcsnicomp(const wchar *s1,const wchar *s2,size_t n) // If we specify 'n' exceeding the actual string length, CompareString goes // beyond the trailing zero and compares garbage. So we need to limit 'n' // to real string length. - size_t l1=Min(wcslen(s1)+1,n); - size_t l2=Min(wcslen(s2)+1,n); + size_t sl1=wcslen(s1); // Pre-compute to not call wcslen() in Min() twice. + size_t l1=Min(sl1+1,n); + size_t sl2=wcslen(s2); // Pre-compute to not call wcslen() in Min() twice. + size_t l2=Min(sl2+1,n); return CompareStringW(LOCALE_USER_DEFAULT,NORM_IGNORECASE|SORT_STRINGSORT,s1,(int)l1,s2,(int)l2)-2; #else if (n==0) @@ -482,13 +755,36 @@ const wchar_t* wcscasestr(const wchar_t *str, const wchar_t *search) if (tolowerw(str[i+j])!=tolowerw(search[j])) break; } - return NULL; + return nullptr; +} + + +// Case insensitive std::wstring substring search. +std::wstring::size_type wcscasestr(const std::wstring &str, const std::wstring &search) +{ + const wchar *Found=wcscasestr(str.c_str(),search.c_str()); + return Found==nullptr ? std::wstring::npos : Found-str.c_str(); } #ifndef SFX_MODULE wchar* wcslower(wchar *s) { + // If string doesn't contain non-English or uppercase English characters, + // we can return immediately and avoid costly system calls. + bool AlreadyLower=true; + for (wchar *c=s;*c!=0;c++) + { + uint u=(uint)*c; + if (u>=128 || (u>='A' && u<='Z')) + { + AlreadyLower=false; + break; + } + } + if (AlreadyLower) + return s; + #ifdef _WIN_ALL // _wcslwr requires setlocale and we do not want to depend on setlocale // in Windows. Also CharLower involves less overhead. @@ -499,10 +795,14 @@ wchar* wcslower(wchar *s) #endif return s; } -#endif -#ifndef SFX_MODULE +void wcslower(std::wstring &s) +{ + wcslower(&s[0]); +} + + wchar* wcsupper(wchar *s) { #ifdef _WIN_ALL @@ -515,6 +815,12 @@ wchar* wcsupper(wchar *s) #endif return s; } + + +void wcsupper(std::wstring &s) +{ + wcsupper(&s[0]); +} #endif @@ -547,27 +853,28 @@ int tolowerw(int ch) } -int atoiw(const wchar *s) +int atoiw(const std::wstring &s) { return (int)atoilw(s); } -int64 atoilw(const wchar *s) +int64 atoilw(const std::wstring &s) { bool sign=false; - if (*s=='-') // We do use signed integers here, for example, in GUI SFX. + size_t Pos=0; + if (s[Pos]=='-') // We do use signed integers here, for example, in GUI SFX. { - s++; + Pos++; sign=true; } // Use unsigned type here, since long string can overflow the variable // and signed integer overflow is undefined behavior in C++. uint64 n=0; - while (*s>='0' && *s<='9') + while (s[Pos]>='0' && s[Pos]<='9') { - n=n*10+(*s-'0'); - s++; + n=n*10+(s[Pos]-'0'); + Pos++; } // Check int64(n)>=0 to avoid the signed overflow with undefined behavior // when negating 0x8000000000000000. @@ -601,59 +908,6 @@ char* SupportDBCS::charnext(const char *s) // to break string processing loops. return (char *)(IsLeadByte[(byte)*s] && s[1]!=0 ? s+2:s+1); } - - -size_t SupportDBCS::strlend(const char *s) -{ - size_t Length=0; - while (*s!=0) - { - if (IsLeadByte[(byte)*s]) - s+=2; - else - s++; - Length++; - } - return(Length); -} - - -char* SupportDBCS::strchrd(const char *s, int c) -{ - while (*s!=0) - if (IsLeadByte[(byte)*s]) - s+=2; - else - if (*s==c) - return((char *)s); - else - s++; - return(NULL); -} - - -void SupportDBCS::copychrd(char *dest,const char *src) -{ - dest[0]=src[0]; - if (IsLeadByte[(byte)src[0]]) - dest[1]=src[1]; -} - - -char* SupportDBCS::strrchrd(const char *s, int c) -{ - const char *found=NULL; - while (*s!=0) - if (IsLeadByte[(byte)*s]) - s+=2; - else - { - if (*s==c) - found=s; - s++; - } - return((char *)found); -} #endif diff --git a/unrar/unicode.hpp b/unrar/unicode.hpp index 031ac09a..2d867b3a 100644 --- a/unrar/unicode.hpp +++ b/unrar/unicode.hpp @@ -7,25 +7,37 @@ bool WideToChar(const wchar *Src,char *Dest,size_t DestSize); bool CharToWide(const char *Src,wchar *Dest,size_t DestSize); -byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize); +bool WideToChar(const std::wstring &Src,std::string &Dest); +bool CharToWide(const std::string &Src,std::wstring &Dest); +byte* WideToRaw(const wchar *Src,size_t SrcSize,byte *Dest,size_t DestSize); +void WideToRaw(const std::wstring &Src,std::vector &Dest); wchar* RawToWide(const byte *Src,wchar *Dest,size_t DestSize); +std::wstring RawToWide(const std::vector &Src); void WideToUtf(const wchar *Src,char *Dest,size_t DestSize); +void WideToUtf(const std::wstring &Src,std::string &Dest); size_t WideToUtfSize(const wchar *Src); bool UtfToWide(const char *Src,wchar *Dest,size_t DestSize); +bool UtfToWide(const char *Src,std::wstring &Dest); +//bool UtfToWide(const std::vector &Src,std::wstring &Dest); bool IsTextUtf8(const byte *Src); bool IsTextUtf8(const byte *Src,size_t SrcSize); int wcsicomp(const wchar *s1,const wchar *s2); +inline int wcsicomp(const std::wstring &s1,const std::wstring &s2) {return wcsicomp(s1.c_str(),s2.c_str());} int wcsnicomp(const wchar *s1,const wchar *s2,size_t n); +inline int wcsnicomp(const std::wstring &s1,const std::wstring &s2,size_t n) {return wcsnicomp(s1.c_str(),s2.c_str(),n);} const wchar_t* wcscasestr(const wchar_t *str, const wchar_t *search); +std::wstring::size_type wcscasestr(const std::wstring &str, const std::wstring &search); #ifndef SFX_MODULE wchar* wcslower(wchar *s); +void wcslower(std::wstring &s); wchar* wcsupper(wchar *s); +void wcsupper(std::wstring &s); #endif int toupperw(int ch); int tolowerw(int ch); -int atoiw(const wchar *s); -int64 atoilw(const wchar *s); +int atoiw(const std::wstring &s); +int64 atoilw(const std::wstring &s); #ifdef DBCS_SUPPORTED class SupportDBCS @@ -33,34 +45,19 @@ class SupportDBCS public: SupportDBCS(); void Init(); - char* charnext(const char *s); - size_t strlend(const char *s); - char *strchrd(const char *s, int c); - char *strrchrd(const char *s, int c); - void copychrd(char *dest,const char *src); bool IsLeadByte[256]; bool DBCSMode; }; - extern SupportDBCS gdbcs; inline char* charnext(const char *s) {return (char *)(gdbcs.DBCSMode ? gdbcs.charnext(s):s+1);} -inline size_t strlend(const char *s) {return (uint)(gdbcs.DBCSMode ? gdbcs.strlend(s):strlen(s));} -inline char* strchrd(const char *s, int c) {return (char *)(gdbcs.DBCSMode ? gdbcs.strchrd(s,c):strchr(s,c));} -inline char* strrchrd(const char *s, int c) {return (char *)(gdbcs.DBCSMode ? gdbcs.strrchrd(s,c):strrchr(s,c));} -inline void copychrd(char *dest,const char *src) {if (gdbcs.DBCSMode) gdbcs.copychrd(dest,src); else *dest=*src;} -inline bool IsDBCSMode() {return(gdbcs.DBCSMode);} -inline void InitDBCS() {gdbcs.Init();} +inline bool IsDBCSMode() {return gdbcs.DBCSMode;} #else #define charnext(s) ((s)+1) -#define strlend strlen -#define strchrd strchr -#define strrchrd strrchr -#define IsDBCSMode() (true) -inline void copychrd(char *dest,const char *src) {*dest=*src;} +#define IsDBCSMode() (false) #endif diff --git a/unrar/unpack.cpp b/unrar/unpack.cpp index 39c0f134..5b3cde3d 100644 --- a/unrar/unpack.cpp +++ b/unrar/unpack.cpp @@ -10,8 +10,8 @@ #ifndef SFX_MODULE #include "unpack15.cpp" #include "unpack20.cpp" -#endif #include "unpack30.cpp" +#endif #include "unpack50.cpp" #include "unpack50frag.cpp" @@ -22,20 +22,21 @@ Unpack::Unpack(ComprDataIO *DataIO) Window=NULL; Fragmented=false; Suspended=false; - UnpAllBuf=false; UnpSomeRead=false; + ExtraDist=false; #ifdef RAR_SMP MaxUserThreads=1; UnpThreadPool=NULL; ReadBufMT=NULL; UnpThreadData=NULL; #endif + AllocWinSize=0; MaxWinSize=0; MaxWinMask=0; // Perform initialization, which should be done only once for all files. - // It prevents crash if first DoUnpack call is later made with wrong - // (true) 'Solid' value. + // It prevents crash if first unpacked file has the wrong "true" Solid flag, + // so first DoUnpack call is made with the wrong "true" Solid value later. UnpInitData(false); #ifndef SFX_MODULE // RAR 1.5 decompression initialization @@ -47,10 +48,11 @@ Unpack::Unpack(ComprDataIO *DataIO) Unpack::~Unpack() { +#ifndef SFX_MODULE InitFilters30(false); +#endif - if (Window!=NULL) - free(Window); + Alloc.delete_l(Window); // delete Window; #ifdef RAR_SMP delete UnpThreadPool; delete[] ReadBufMT; @@ -70,13 +72,11 @@ void Unpack::SetThreads(uint Threads) #endif -void Unpack::Init(size_t WinSize,bool Solid) +// We get 64-bit WinSize, so we still can check and quit for dictionaries +// exceeding a threshold in 32-bit builds. Then we convert WinSize to size_t +// MaxWinSize. +void Unpack::Init(uint64 WinSize,bool Solid) { - // If 32-bit RAR unpacks an archive with 4 GB dictionary, the window size - // will be 0 because of size_t overflow. Let's issue the memory error. - if (WinSize==0) - ErrHandler.MemoryError(); - // Minimum window size must be at least twice more than maximum possible // size of filter block, which is 0x10000 in RAR now. If window size is // smaller, we can have a block with never cleared flt->NextWindow flag @@ -86,39 +86,61 @@ void Unpack::Init(size_t WinSize,bool Solid) if (WinSize>16)>0x10000) // Window size must not exceed 4 GB. + if (WinSize>Min(0x10000000000ULL,UNPACK_MAX_DICT)) // Window size must not exceed 1 TB. + throw std::bad_alloc(); + + // 32-bit build can't unpack dictionaries exceeding 32-bit even in theory. + // Also we've not verified if WrapUp and WrapDown work properly in 32-bit + // version and >2GB dictionary and if 32-bit version can handle >2GB + // distances. Since such version is unlikely to allocate >2GB anyway, + // we prohibit >2GB dictionaries for 32-bit build here. + if (WinSize>0x80000000 && sizeof(size_t)<=4) + throw std::bad_alloc(); + + // Solid block shall use the same window size for all files. + // But if Window isn't initialized when Solid is set, it means that + // first file in solid block doesn't have the solid flag. We initialize + // the window anyway for such malformed archive. + // Non-solid files shall use their specific window sizes, + // so current window size and unpack routine behavior doesn't depend on + // previously unpacked files and their extraction order. + if (!Solid || Window==nullptr) + { + MaxWinSize=(size_t)WinSize; + MaxWinMask=MaxWinSize-1; + } + + // Use the already allocated window when processing non-solid files + // with reducing dictionary sizes. + if (WinSize<=AllocWinSize) return; // Archiving code guarantees that window size does not grow in the same // solid stream. So if we are here, we are either creating a new window // or increasing the size of non-solid window. So we could safely reject - // current window data without copying them to a new window, though being - // extra cautious, we still handle the solid window grow case below. - bool Grow=Solid && (Window!=NULL || Fragmented); - - // We do not handle growth for existing fragmented window. - if (Grow && Fragmented) + // current window data without copying them to a new window. + if (Solid && (Window!=NULL || Fragmented && WinSize>FragWindow.GetWinSize())) throw std::bad_alloc(); - byte *NewWindow=Fragmented ? NULL : (byte *)malloc(WinSize); + Alloc.delete_l(Window); // delete Window; + Window=nullptr; + + try + { + if (!Fragmented) + Window=Alloc.new_l((size_t)WinSize,false); // Window=new byte[(size_t)WinSize]; + } + catch (std::bad_alloc) // Use the fragmented window in this case. + { + } - if (NewWindow==NULL) - if (Grow || WinSize<0x1000000) - { - // We do not support growth for new fragmented window. - // Also exclude RAR4 and small dictionaries. - throw std::bad_alloc(); - } + if (Window==nullptr) + if (WinSize<0x1000000 || sizeof(size_t)>4) + throw std::bad_alloc(); // Exclude RAR4, small dictionaries and 64-bit. else { - if (Window!=NULL) // If allocated by preceding files. - { - free(Window); - Window=NULL; - } - FragWindow.Init(WinSize); + if (WinSize>FragWindow.GetWinSize()) + FragWindow.Init((size_t)WinSize); Fragmented=true; } @@ -126,23 +148,12 @@ void Unpack::Init(size_t WinSize,bool Solid) { // Clean the window to generate the same output when unpacking corrupt // RAR files, which may access unused areas of sliding dictionary. - memset(NewWindow,0,WinSize); - - // If Window is not NULL, it means that window size has grown. - // In solid streams we need to copy data to a new window in such case. - // RAR archiving code does not allow it in solid streams now, - // but let's implement it anyway just in case we'll change it sometimes. - if (Grow) - for (size_t I=1;I<=MaxWinSize;I++) - NewWindow[(UnpPtr-I)&(WinSize-1)]=Window[(UnpPtr-I)&(MaxWinSize-1)]; - - if (Window!=NULL) - free(Window); - Window=NewWindow; - } + // 2023.10.31: We've added FirstWinDone based unused area access check + // in Unpack::CopyString(), so this memset might be unnecessary now. +// memset(Window,0,(size_t)WinSize); - MaxWinSize=WinSize; - MaxWinMask=MaxWinSize-1; + AllocWinSize=WinSize; + } } @@ -154,21 +165,23 @@ void Unpack::DoUnpack(uint Method,bool Solid,bool suspendAfterInit) switch(Method) { #ifndef SFX_MODULE - case 15: // rar 1.5 compression + case 15: // RAR 1.5 compression. if (!Fragmented) Unpack15(Solid, suspendAfterInit); break; - case 20: // rar 2.x compression - case 26: // files larger than 2GB + case 20: // RAR 2.x compression. + case 26: // Files larger than 2GB. if (!Fragmented) Unpack20(Solid,suspendAfterInit); break; -#endif - case 29: // rar 3.x compression + case 29: // RAR 3.x compression. if (!Fragmented) Unpack29(Solid,suspendAfterInit); break; - case 50: // RAR 5.0 compression algorithm. +#endif + case VER_PACK5: // 50. RAR 5.0 and 7.0 compression algorithms. + case VER_PACK7: // 70. + ExtraDist=(Method==VER_PACK7); #ifdef RAR_SMP if (MaxUserThreads>1) { @@ -177,7 +190,7 @@ void Unpack::DoUnpack(uint Method,bool Solid,bool suspendAfterInit) // write more than one dictionary for same loop pass. So we would need // larger buffers of unknown size. Also we do not support multithreading // in fragmented window mode. - if (!Fragmented) + if (!Fragmented && !suspendAfterInit) { Unpack5MT(Solid); break; @@ -194,13 +207,19 @@ void Unpack::UnpInitData(bool Solid) { if (!Solid) { - memset(OldDist,0,sizeof(OldDist)); + OldDist[0]=OldDist[1]=OldDist[2]=OldDist[3]=(size_t)-1; + OldDistPtr=0; - LastDist=LastLength=0; + + LastDist=(uint)-1; // Initialize it to -1 like LastDist. + LastLength=0; + // memset(Window,0,MaxWinSize); memset(&BlockTables,0,sizeof(BlockTables)); UnpPtr=WrPtr=0; - WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; + PrevPtr=0; + FirstWinDone=false; + WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE); } // Filters never share several solid files, so we can safely reset them // even in solid archive. @@ -215,8 +234,8 @@ void Unpack::UnpInitData(bool Solid) BlockHeader.BlockSize=-1; // '-1' means not defined yet. #ifndef SFX_MODULE UnpInitData20(Solid); -#endif UnpInitData30(Solid); +#endif UnpInitData50(Solid); } @@ -309,7 +328,7 @@ void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) Dec->QuickBits=MAX_QUICK_DECODE_BITS; break; default: - Dec->QuickBits=MAX_QUICK_DECODE_BITS-3; + Dec->QuickBits=MAX_QUICK_DECODE_BITS>3 ? MAX_QUICK_DECODE_BITS-3 : 0; break; } diff --git a/unrar/unpack.hpp b/unrar/unpack.hpp index 1a6c0db7..c680935c 100644 --- a/unrar/unpack.hpp +++ b/unrar/unpack.hpp @@ -2,7 +2,7 @@ #define _RAR_UNPACK_ // Maximum allowed number of compressed bits processed in quick mode. -#define MAX_QUICK_DECODE_BITS 10 +#define MAX_QUICK_DECODE_BITS 9 // Maximum number of filters per entire data block. Must be at least // twice more than MAX_PACK_FILTERS to store filters from two data blocks. @@ -23,8 +23,8 @@ // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. #define MAX_FILTER_BLOCK_SIZE 0x400000 -// Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, -// so we keep a number of buffered filters in unpacker reasonable. +// Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_READ, +// so we keep the number of buffered filters in unpacker reasonable. #define UNPACK_MAX_WRITE 0x400000 // Decode compressed bit fields to alphabet numbers. @@ -55,7 +55,7 @@ struct DecodeTable:PackDef // Translates compressed bits (up to QuickBits length) // to position in alphabet in quick mode. // 'ushort' saves some memory and even provides a little speed gain - // comparting to 'uint' here. + // comparing to 'uint' here. ushort QuickNum[1< FilterSrcMemory; - Array FilterDstMemory; + LargePageAlloc Alloc; + + std::vector FilterSrcMemory; + std::vector FilterDstMemory; // Filters code, one entry per filter. - Array Filters; + std::vector Filters; - uint OldDist[4],OldDistPtr; + size_t OldDist[4],OldDistPtr; uint LastLength; // LastDist is necessary only for RAR2 and older with circular OldDist // array. In RAR3 last distance is always stored in OldDist[0]. uint LastDist; - size_t UnpPtr,WrPtr; + size_t UnpPtr; // Current position in window. + + size_t PrevPtr; // UnpPtr value for previous loop iteration. + bool FirstWinDone; // At least one dictionary was processed. + + size_t WrPtr; // Last written unpacked data position. // Top border of read packed data. int ReadTop; @@ -266,7 +274,7 @@ class Unpack:PackDef UnpackBlockHeader BlockHeader; UnpackBlockTables BlockTables; - size_t WriteBorder; + size_t WriteBorder; // Perform write when reaching this border. byte *Window; @@ -277,7 +285,6 @@ class Unpack:PackDef int64 DestUnpSize; bool Suspended; - bool UnpAllBuf; bool UnpSomeRead; int64 WrittenFileSize; bool FileExtracted; @@ -289,7 +296,7 @@ class Unpack:PackDef void LongLZ(); void HuffDecode(); void GetFlagsBuf(); - void UnpInitData15(int Solid); + void UnpInitData15(bool Solid); void InitHuff(); void CorrHuff(ushort *CharSet,byte *NumToPlace); void CopyString15(uint Distance,uint Length); @@ -359,15 +366,15 @@ class Unpack:PackDef BitInput VMCodeInp; // Filters code, one entry per filter. - Array Filters30; + std::vector Filters30; // Filters stack, several entrances of same filter are possible. - Array PrgStack; + std::vector PrgStack; // Lengths of preceding data blocks, one length of one last block // for every filter. Used to reduce the size required to write // the data block length if lengths are repeating. - Array OldFilterLengths; + std::vector OldFilterLengths; int LastFilter; /***************************** Unpack v 3.0 *********************************/ @@ -375,9 +382,10 @@ class Unpack:PackDef public: Unpack(ComprDataIO *DataIO); ~Unpack(); - void Init(size_t WinSize,bool Solid); + void Init(uint64 WinSize,bool Solid); + void AllowLargePages(bool Allow) {Alloc.AllowLargePages(Allow);} void DoUnpack(uint Method,bool Solid,bool SuspendAfterInit=false); - bool IsFileExtracted() {return(FileExtracted);} + bool IsFileExtracted() {return FileExtracted;} void SetDestSize(int64 DestSize) {DestUnpSize=DestSize;FileExtracted=false;} void SetSuspended(bool Suspended) {Unpack::Suspended=Suspended;} @@ -386,10 +394,13 @@ class Unpack:PackDef void UnpackDecode(UnpackThreadData &D); #endif + uint64 AllocWinSize; size_t MaxWinSize; size_t MaxWinMask; - uint GetChar() + bool ExtraDist; // Allow distances up to 1 TB. + + byte GetChar() { if (Inp.InAddr>BitInput::MAX_SIZE-30) { @@ -399,6 +410,30 @@ class Unpack:PackDef } return Inp.InBuf[Inp.InAddr++]; } + + + // If window position crosses the window beginning, wrap it to window end. + // Replaces &MaxWinMask for non-power 2 window sizes. + // We can't use %WinSize everywhere not only for performance reason, + // but also because C++ % is reminder instead of modulo. + // We need additional checks in the code if WinPos distance from 0 + // can exceed MaxWinSize. Alternatively we could add such check here. + inline size_t WrapDown(size_t WinPos) + { + return WinPos >= MaxWinSize ? WinPos + MaxWinSize : WinPos; + } + + // If window position crosses the window end, wrap it to window beginning. + // Replaces &MaxWinMask for non-power 2 window sizes. + // Unlike WrapDown, we can use %WinSize here if there was no size_t + // overflow when calculating WinPos. + // We need additional checks in the code if WinPos distance from MaxWinSize + // can be MaxWinSize or more. Alternatively we could add such check here + // or use %WinSize. + inline size_t WrapUp(size_t WinPos) + { + return WinPos >= MaxWinSize ? WinPos - MaxWinSize : WinPos; + } }; #endif diff --git a/unrar/unpack15.cpp b/unrar/unpack15.cpp index 2dffe5e4..6365d047 100644 --- a/unrar/unpack15.cpp +++ b/unrar/unpack15.cpp @@ -1,40 +1,40 @@ #define STARTL1 2 -static unsigned int DecL1[]={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, +static uint DecL1[]={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, 0xee00,0xf000,0xf200,0xf200,0xffff}; -static unsigned int PosL1[]={0,0,0,2,3,5,7,11,16,20,24,32,32}; +static uint PosL1[]={0,0,0,2,3,5,7,11,16,20,24,32,32}; #define STARTL2 3 -static unsigned int DecL2[]={0xa000,0xc000,0xd000,0xe000,0xea00,0xee00, +static uint DecL2[]={0xa000,0xc000,0xd000,0xe000,0xea00,0xee00, 0xf000,0xf200,0xf240,0xffff}; -static unsigned int PosL2[]={0,0,0,0,5,7,9,13,18,22,26,34,36}; +static uint PosL2[]={0,0,0,0,5,7,9,13,18,22,26,34,36}; #define STARTHF0 4 -static unsigned int DecHf0[]={0x8000,0xc000,0xe000,0xf200,0xf200,0xf200, +static uint DecHf0[]={0x8000,0xc000,0xe000,0xf200,0xf200,0xf200, 0xf200,0xf200,0xffff}; -static unsigned int PosHf0[]={0,0,0,0,0,8,16,24,33,33,33,33,33}; +static uint PosHf0[]={0,0,0,0,0,8,16,24,33,33,33,33,33}; #define STARTHF1 5 -static unsigned int DecHf1[]={0x2000,0xc000,0xe000,0xf000,0xf200,0xf200, +static uint DecHf1[]={0x2000,0xc000,0xe000,0xf000,0xf200,0xf200, 0xf7e0,0xffff}; -static unsigned int PosHf1[]={0,0,0,0,0,0,4,44,60,76,80,80,127}; +static uint PosHf1[]={0,0,0,0,0,0,4,44,60,76,80,80,127}; #define STARTHF2 5 -static unsigned int DecHf2[]={0x1000,0x2400,0x8000,0xc000,0xfa00,0xffff, +static uint DecHf2[]={0x1000,0x2400,0x8000,0xc000,0xfa00,0xffff, 0xffff,0xffff}; -static unsigned int PosHf2[]={0,0,0,0,0,0,2,7,53,117,233,0,0}; +static uint PosHf2[]={0,0,0,0,0,0,2,7,53,117,233,0,0}; #define STARTHF3 6 -static unsigned int DecHf3[]={0x800,0x2400,0xee00,0xfe80,0xffff,0xffff, +static uint DecHf3[]={0x800,0x2400,0xee00,0xfe80,0xffff,0xffff, 0xffff}; -static unsigned int PosHf3[]={0,0,0,0,0,0,0,2,16,218,251,0,0}; +static uint PosHf3[]={0,0,0,0,0,0,0,2,16,218,251,0,0}; #define STARTHF4 8 -static unsigned int DecHf4[]={0xff00,0xffff,0xffff,0xffff,0xffff,0xffff}; -static unsigned int PosHf4[]={0,0,0,0,0,0,0,0,0,255,0,0,0}; +static uint DecHf4[]={0xff00,0xffff,0xffff,0xffff,0xffff,0xffff}; +static uint PosHf4[]={0,0,0,0,0,0,0,0,0,255,0,0,0}; void Unpack::Unpack15(bool Solid,bool SuspendAfterInit) @@ -64,6 +64,9 @@ void Unpack::Unpack15(bool Solid,bool SuspendAfterInit) { UnpPtr&=MaxWinMask; + FirstWinDone|=(PrevPtr>UnpPtr); + PrevPtr=UnpPtr; + if (Inp.InAddr>ReadTop-30 && !UnpReadBuf()) break; if (((WrPtr-UnpPtr) & MaxWinMask)<270 && WrPtr!=UnpPtr) @@ -120,27 +123,27 @@ void Unpack::Unpack15(bool Solid,bool SuspendAfterInit) void Unpack::ShortLZ() { - static unsigned int ShortLen1[]={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0}; - static unsigned int ShortXor1[]={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, + static uint ShortLen1[]={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0}; + static uint ShortXor1[]={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, 0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0}; - static unsigned int ShortLen2[]={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0}; - static unsigned int ShortXor2[]={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, + static uint ShortLen2[]={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0}; + static uint ShortXor2[]={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, 0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0}; - unsigned int Length,SaveLength; - unsigned int LastDistance; - unsigned int Distance; + uint Length,SaveLength; + uint LastDistance; + uint Distance; int DistancePlace; NumHuf=0; - unsigned int BitField=Inp.fgetbits(); + uint BitField=Inp.fgetbits(); if (LCount==2) { Inp.faddbits(1); if (BitField >= 0x8000) { - CopyString15((unsigned int)LastDist,LastLength); + CopyString15(LastDist,LastLength); return; } BitField <<= 1; @@ -172,7 +175,7 @@ void Unpack::ShortLZ() if (Length == 9) { LCount++; - CopyString15((unsigned int)LastDist,LastLength); + CopyString15(LastDist,LastLength); return; } if (Length == 14) @@ -189,7 +192,7 @@ void Unpack::ShortLZ() LCount=0; SaveLength=Length; - Distance=OldDist[(OldDistPtr-(Length-9)) & 3]; + Distance=(uint)OldDist[(OldDistPtr-(Length-9)) & 3]; Length=DecodeNum(Inp.fgetbits(),STARTL1,DecL1,PosL1)+2; if (Length==0x101 && SaveLength==10) { @@ -218,8 +221,8 @@ void Unpack::ShortLZ() if (--DistancePlace != -1) { LastDistance=ChSetA[DistancePlace]; - ChSetA[DistancePlace+1]=LastDistance; - ChSetA[DistancePlace]=Distance; + ChSetA[DistancePlace+1]=(ushort)LastDistance; + ChSetA[DistancePlace]=(ushort)Distance; } Length+=2; OldDist[OldDistPtr++] = ++Distance; @@ -232,10 +235,10 @@ void Unpack::ShortLZ() void Unpack::LongLZ() { - unsigned int Length; - unsigned int Distance; - unsigned int DistancePlace,NewDistancePlace; - unsigned int OldAvr2,OldAvr3; + uint Length; + uint Distance; + uint DistancePlace,NewDistancePlace; + uint OldAvr2,OldAvr3; NumHuf=0; Nlzb+=16; @@ -246,7 +249,7 @@ void Unpack::LongLZ() } OldAvr2=AvrLn2; - unsigned int BitField=Inp.fgetbits(); + uint BitField=Inp.fgetbits(); if (AvrLn2 >= 122) Length=DecodeNum(BitField,STARTL2,DecL2,PosL2); else @@ -290,7 +293,7 @@ void Unpack::LongLZ() } ChSetB[DistancePlace & 0xff]=ChSetB[NewDistancePlace]; - ChSetB[NewDistancePlace]=Distance; + ChSetB[NewDistancePlace]=(ushort)Distance; Distance=((Distance & 0xff00) | (Inp.fgetbits() >> 8)) >> 1; Inp.faddbits(7); @@ -324,12 +327,12 @@ void Unpack::LongLZ() void Unpack::HuffDecode() { - unsigned int CurByte,NewBytePlace; - unsigned int Length; - unsigned int Distance; + uint CurByte,NewBytePlace; + uint Length; + uint Distance; int BytePlace; - unsigned int BitField=Inp.fgetbits(); + uint BitField=Inp.fgetbits(); if (AvrPlc > 0x75ff) BytePlace=DecodeNum(BitField,STARTHF4,DecHf4,PosHf4); @@ -396,14 +399,14 @@ void Unpack::HuffDecode() } ChSet[BytePlace]=ChSet[NewBytePlace]; - ChSet[NewBytePlace]=CurByte; + ChSet[NewBytePlace]=(ushort)CurByte; } void Unpack::GetFlagsBuf() { - unsigned int Flags,NewFlagsPlace; - unsigned int FlagsPlace=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2); + uint Flags,NewFlagsPlace; + uint FlagsPlace=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2); // Our Huffman table stores 257 items and needs all them in other parts // of code such as when StMode is on, so the first item is control item. @@ -424,11 +427,11 @@ void Unpack::GetFlagsBuf() } ChSetC[FlagsPlace]=ChSetC[NewFlagsPlace]; - ChSetC[NewFlagsPlace]=Flags; + ChSetC[NewFlagsPlace]=(ushort)Flags; } -void Unpack::UnpInitData15(int Solid) +void Unpack::UnpInitData15(bool Solid) { if (!Solid) { @@ -447,7 +450,7 @@ void Unpack::UnpInitData15(int Solid) void Unpack::InitHuff() { - for (unsigned int I=0;I<256;I++) + for (ushort I=0;I<256;I++) { ChSet[I]=ChSetB[I]=I<<8; ChSetA[I]=I; @@ -475,11 +478,19 @@ void Unpack::CorrHuff(ushort *CharSet,byte *NumToPlace) void Unpack::CopyString15(uint Distance,uint Length) { DestUnpSize-=Length; - while (Length--) - { - Window[UnpPtr]=Window[(UnpPtr-Distance) & MaxWinMask]; - UnpPtr=(UnpPtr+1) & MaxWinMask; - } + // 2024.04.18: Distance can be 0 in corrupt RAR 1.5 archives. + if (!FirstWinDone && Distance>UnpPtr || Distance>MaxWinSize || Distance==0) + while (Length-- > 0) + { + Window[UnpPtr]=0; + UnpPtr=(UnpPtr+1) & MaxWinMask; + } + else + while (Length-- > 0) + { + Window[UnpPtr]=Window[(UnpPtr-Distance) & MaxWinMask]; + UnpPtr=(UnpPtr+1) & MaxWinMask; + } } diff --git a/unrar/unpack20.cpp b/unrar/unpack20.cpp index e1af3bf9..5079d0dc 100644 --- a/unrar/unpack20.cpp +++ b/unrar/unpack20.cpp @@ -2,7 +2,8 @@ void Unpack::CopyString20(uint Length,uint Distance) { - LastDist=OldDist[OldDistPtr++]=Distance; + LastDist=Distance; + OldDist[OldDistPtr++]=Distance; OldDistPtr = OldDistPtr & 3; // Needed if RAR 1.5 file is called after RAR 2.0. LastLength=Length; DestUnpSize-=Length; @@ -39,6 +40,9 @@ void Unpack::Unpack20(bool Solid,bool SuspendAfterInit) { UnpPtr&=MaxWinMask; + FirstWinDone|=(PrevPtr>UnpPtr); + PrevPtr=UnpPtr; + if (Inp.InAddr>ReadTop-30) if (!UnpReadBuf()) break; @@ -112,7 +116,7 @@ void Unpack::Unpack20(bool Solid,bool SuspendAfterInit) } if (Number<261) { - uint Distance=OldDist[(OldDistPtr-(Number-256)) & 3]; + uint Distance=(uint)OldDist[(OldDistPtr-(Number-256)) & 3]; uint LengthNumber=DecodeNumber(Inp,&BlockTables.RD); uint Length=LDecode[LengthNumber]+2; if ((Bits=LBits[LengthNumber])>0) @@ -158,7 +162,11 @@ void Unpack::UnpWriteBuf20() { UnpIO->UnpWrite(&Window[WrPtr],-(int)WrPtr & MaxWinMask); UnpIO->UnpWrite(Window,UnpPtr); - UnpAllBuf=true; + + // 2024.12.24: Before 7.10 we set "UnpAllBuf=true" here. It was needed for + // Pack::PrepareSolidAppend(). Since both UnpAllBuf and FirstWinDone + // variables indicate the same thing and we set FirstWinDone in other place + // anyway, we replaced UnpAllBuf with FirstWinDone and removed this code. } else UnpIO->UnpWrite(&Window[WrPtr],UnpPtr-WrPtr); diff --git a/unrar/unpack30.cpp b/unrar/unpack30.cpp index 0ed08396..cb4a0b36 100644 --- a/unrar/unpack30.cpp +++ b/unrar/unpack30.cpp @@ -19,8 +19,8 @@ void Unpack::Unpack29(bool Solid,bool SuspendAfterInit) { static unsigned char LDecode[]={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; static unsigned char LBits[]= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; - static int DDecode[DC]; - static byte DBits[DC]; + static int DDecode[DC30]; + static byte DBits[DC30]; static int DBitLengthCounts[]= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12}; static unsigned char SDDecode[]={0,4,8,16,32,64,128,192}; static unsigned char SDBits[]= {2,2,3, 4, 5, 6, 6, 6}; @@ -56,12 +56,15 @@ void Unpack::Unpack29(bool Solid,bool SuspendAfterInit) { UnpPtr&=MaxWinMask; + FirstWinDone|=(PrevPtr>UnpPtr); + PrevPtr=UnpPtr; + if (Inp.InAddr>ReadBorder) { if (!UnpReadBuf30()) break; } - if (((WrPtr-UnpPtr) & MaxWinMask)<260 && WrPtr!=UnpPtr) + if (((WrPtr-UnpPtr) & MaxWinMask)<=MAX3_INC_LZ_MATCH && WrPtr!=UnpPtr) { UnpWriteBuf30(); if (WrittenFileSize>DestUnpSize) @@ -225,7 +228,7 @@ void Unpack::Unpack29(bool Solid,bool SuspendAfterInit) if (Number<263) { uint DistNum=Number-259; - uint Distance=OldDist[DistNum]; + uint Distance=(uint)OldDist[DistNum]; for (uint I=DistNum;I>0;I--) OldDist[I]=OldDist[I-1]; OldDist[0]=Distance; @@ -312,7 +315,7 @@ bool Unpack::ReadVMCode() } if (Length==0) return false; - Array VMCode(Length); + std::vector VMCode(Length); for (uint I=0;I>8; Inp.addbits(8); } - return AddVMCode(FirstByte,&VMCode[0],Length); + return AddVMCode(FirstByte,VMCode.data(),Length); } @@ -352,7 +355,7 @@ bool Unpack::ReadVMCodePPM() } if (Length==0) return false; - Array VMCode(Length); + std::vector VMCode(Length); for (uint I=0;IFilters30.Size() || FiltPos>OldFilterLengths.Size()) + if (FiltPos>Filters30.size() || FiltPos>OldFilterLengths.size()) return false; LastFilter=FiltPos; - bool NewFilter=(FiltPos==Filters30.Size()); + bool NewFilter=(FiltPos==Filters30.size()); UnpackFilter30 *StackFilter=new UnpackFilter30; // New filter for PrgStack. @@ -399,15 +402,15 @@ bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize) return false; } - Filters30.Add(1); - Filters30[Filters30.Size()-1]=Filter=new UnpackFilter30; - StackFilter->ParentFilter=(uint)(Filters30.Size()-1); + StackFilter->ParentFilter=(uint)Filters30.size(); + Filter=new UnpackFilter30; + Filters30.push_back(Filter); // Reserve one item to store the data block length of our new filter // entry. We'll set it to real block length below, after reading it. // But we need to initialize it now, because when processing corrupt // data, we can access this item even before we set it to real value. - OldFilterLengths.Push(0); + OldFilterLengths.push_back(0); } else // Filter was used in the past. { @@ -416,7 +419,7 @@ bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize) } uint EmptyCount=0; - for (uint I=0;IMAX3_UNPACK_FILTERS) + if (PrgStack.size()>MAX3_UNPACK_FILTERS) { delete StackFilter; return false; } - PrgStack.Add(1); + PrgStack.resize(PrgStack.size()+1); EmptyCount=1; } - size_t StackPos=PrgStack.Size()-EmptyCount; + size_t StackPos=PrgStack.size()-EmptyCount; PrgStack[StackPos]=StackFilter; uint BlockStart=RarVM::ReadData(VMCodeInp); @@ -454,7 +457,7 @@ bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize) // for same filter. It is possible for corrupt data to access a new // and not filled yet item of OldFilterLengths array here. This is why // we set new OldFilterLengths items to zero above. - StackFilter->BlockLength=FiltPosBlockLength=FiltPosNextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=BlockStart; @@ -478,7 +481,7 @@ bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize) uint VMCodeSize=RarVM::ReadData(VMCodeInp); if (VMCodeSize>=0x10000 || VMCodeSize==0 || VMCodeInp.InAddr+VMCodeSize>CodeSize) return false; - Array VMCode(VMCodeSize); + std::vector VMCode(VMCodeSize); for (uint I=0;I>8; VMCodeInp.faddbits(8); } - VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); + VM.Prepare(VMCode.data(),VMCodeSize,&Filter->Prg); } StackFilter->Prg.Type=Filter->Prg.Type; @@ -526,7 +529,7 @@ void Unpack::UnpWriteBuf30() { uint WrittenBorder=(uint)WrPtr; uint WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask); - for (size_t I=0;IFilteredDataSize; delete PrgStack[I]; - PrgStack[I]=NULL; - while (I+1FilteredDataSize; I++; delete PrgStack[I]; - PrgStack[I]=NULL; + PrgStack[I]=nullptr; } UnpIO->UnpWrite(FilteredData,FilteredDataSize); UnpSomeRead=true; @@ -607,10 +610,10 @@ void Unpack::UnpWriteBuf30() { // Current filter intersects the window write border, so we adjust // the window border to process this filter next time, not now. - for (size_t J=I;JNextWindow) + if (flt!=nullptr && flt->NextWindow) flt->NextWindow=false; } WrPtr=WrittenBorder; @@ -758,14 +761,14 @@ void Unpack::InitFilters30(bool Solid) { if (!Solid) { - OldFilterLengths.SoftReset(); + OldFilterLengths.clear(); LastFilter=0; - for (size_t I=0;IUnpPtr); + PrevPtr=UnpPtr; if (Inp.InAddr>=ReadBorder) { @@ -46,7 +51,8 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) break; } - if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) @@ -71,7 +77,8 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) { uint Length=SlotToLength(Inp,MainSlot-262); - uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); + size_t Distance=1; + uint DBits,DistSlot=DecodeNumber(Inp,&BlockTables.DD); if (DistSlot<4) { DBits=0; @@ -80,7 +87,7 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) else { DBits=DistSlot/2 - 1; - Distance+=(2 | (DistSlot & 1)) << DBits; + Distance+=size_t(2 | (DistSlot & 1)) << DBits; } if (DBits>0) @@ -89,15 +96,27 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) { if (DBits>4) { - Distance+=((Inp.getbits32()>>(36-DBits))<<4); + // It is also possible to always use getbits64() here. + if (DBits>36) + Distance+=( ( size_t(Inp.getbits64() ) >> (68-DBits) ) << 4 ); + else + Distance+=( ( size_t(Inp.getbits32() ) >> (36-DBits) ) << 4 ); Inp.addbits(DBits-4); } uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); Distance+=LowDist; + + // Distance can be 0 for multiples of 4 GB as result of size_t + // overflow in 32-bit build. Its lower 32-bit can also erroneously + // fit into dictionary after truncating upper 32-bits. Replace such + // invalid distances with -1, so CopyString sets 0 data for them. + // DBits>=30 also as DistSlot>=62 indicate distances >=0x80000001. + if (sizeof(Distance)==4 && DBits>=30) + Distance=(size_t)-1; } else { - Distance+=Inp.getbits32()>>(32-DBits); + Distance+=Inp.getbits()>>(16-DBits); Inp.addbits(DBits); } } @@ -116,7 +135,7 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) InsertOldDist(Distance); LastLength=Length; if (Fragmented) - FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); + FragWindow.CopyString(Length,Distance,UnpPtr,FirstWinDone,MaxWinSize); else CopyString(Length,Distance); continue; @@ -132,7 +151,7 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) { if (LastLength!=0) if (Fragmented) - FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); + FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,FirstWinDone,MaxWinSize); else CopyString(LastLength,OldDist[0]); continue; @@ -140,7 +159,7 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) if (MainSlot<262) { uint DistNum=MainSlot-258; - uint Distance=OldDist[DistNum]; + size_t Distance=OldDist[DistNum]; for (uint I=DistNum;I>0;I--) OldDist[I]=OldDist[I-1]; OldDist[0]=Distance; @@ -149,7 +168,7 @@ void Unpack::Unpack5(bool Solid,bool SuspendAfterInit) uint Length=SlotToLength(Inp,LengthSlot); LastLength=Length; if (Fragmented) - FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); + FragWindow.CopyString(Length,Distance,UnpPtr,FirstWinDone,MaxWinSize); else CopyString(Length,Distance); continue; @@ -200,20 +219,25 @@ bool Unpack::ReadFilter(BitInput &Inp,UnpackFilter &Filter) bool Unpack::AddFilter(UnpackFilter &Filter) { - if (Filters.Size()>=MAX_UNPACK_FILTERS) + if (Filters.size()>=MAX_UNPACK_FILTERS) { UnpWriteBuf(); // Write data, apply and flush filters. - if (Filters.Size()>=MAX_UNPACK_FILTERS) + if (Filters.size()>=MAX_UNPACK_FILTERS) InitFilters(); // Still too many filters, prevent excessive memory use. } // If distance to filter start is that large that due to circular dictionary // mode now it points to old not written yet data, then we set 'NextWindow' // flag and process this filter only after processing that older data. - Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; - - Filter.BlockStart=uint((Filter.BlockStart+UnpPtr)&MaxWinMask); - Filters.Push(Filter); + Filter.NextWindow=WrPtr!=UnpPtr && WrapDown(WrPtr-UnpPtr)<=Filter.BlockStart; + + // In malformed archive Filter.BlockStart can be many times larger + // than window size, so here we must use the reminder instead of + // subtracting the single window size as WrapUp can do. So the result + // is always within the window. Since we add and not subtract here, + // reminder always provides the valid result in valid archives. + Filter.BlockStart=(Filter.BlockStart+UnpPtr)%MaxWinSize; + Filters.push_back(Filter); return true; } @@ -259,10 +283,10 @@ bool Unpack::UnpReadBuf() void Unpack::UnpWriteBuf() { size_t WrittenBorder=WrPtr; - size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; + size_t FullWriteSize=WrapDown(UnpPtr-WrittenBorder); size_t WriteSizeLeft=FullWriteSize; bool NotAllFiltersProcessed=false; - for (size_t I=0;IBlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) + // to next block and no further wrap arounds is possible. + if (WrapDown(flt->BlockStart-WrPtr)<=FullWriteSize) flt->NextWindow=false; continue; } - uint BlockStart=flt->BlockStart; + size_t BlockStart=flt->BlockStart; uint BlockLength=flt->BlockLength; - if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. { - uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; + size_t BlockEnd=WrapUp(BlockStart+BlockLength); - FilterSrcMemory.Alloc(BlockLength); - byte *Mem=&FilterSrcMemory[0]; + FilterSrcMemory.resize(BlockLength); + byte *Mem=FilterSrcMemory.data(); if (BlockStartType!=FILTER_NONE) @@ -369,7 +393,7 @@ void Unpack::UnpWriteBuf() // Remove processed filters from queue. size_t EmptyCount=0; - for (size_t I=0;I0) Filters[I-EmptyCount]=Filters[I]; @@ -377,7 +401,7 @@ void Unpack::UnpWriteBuf() EmptyCount++; } if (EmptyCount>0) - Filters.Alloc(Filters.Size()-EmptyCount); + Filters.resize(Filters.size()-EmptyCount); if (!NotAllFiltersProcessed) // Only if all filters are processed. { @@ -389,12 +413,12 @@ void Unpack::UnpWriteBuf() // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE // instead of potentially huge MaxWinSize blocks. It also allows us // to keep the size of Filters array reasonable. - WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; + WriteBorder=WrapUp(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE)); // Choose the nearest among WriteBorder and WrPtr actual written border. // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. if (WriteBorder==UnpPtr || - WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) + WrPtr!=UnpPtr && WrapDown(WrPtr-UnpPtr)Channels,SrcPos=0; - FilterDstMemory.Alloc(DataSize); - byte *DstData=&FilterDstMemory[0]; + FilterDstMemory.resize(DataSize); + byte *DstData=FilterDstMemory.data(); // Bytes from same channels are grouped to continual data blocks, // so we need to place them back to their interleaving positions. @@ -491,18 +515,17 @@ void Unpack::UnpWriteArea(size_t StartPtr,size_t EndPtr) { if (EndPtr!=StartPtr) UnpSomeRead=true; - if (EndPtr0) { size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); UnpWriteData(&FragWindow[StartPtr],BlockSize); SizeToWrite-=BlockSize; - StartPtr=(StartPtr+BlockSize) & MaxWinMask; + StartPtr=WrapUp(StartPtr+BlockSize); } } else @@ -545,7 +568,7 @@ bool Unpack::ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header) return false; Inp.faddbits((8-Inp.InBit)&7); - byte BlockFlags=Inp.fgetbits()>>8; + byte BlockFlags=byte(Inp.fgetbits()>>8); Inp.faddbits(8); uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. @@ -568,14 +591,24 @@ bool Unpack::ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header) Header.BlockSize=BlockSize; byte CheckSum=byte(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); + + // 2024.01.04: In theory the valid block can have Header.BlockSize == 0 + // and Header.TablePresent == false in case the only block purpose is to + // store Header.LastBlockInFile flag if it didn't fit into previous block. + // So we do not reject Header.BlockSize == 0. Though currently RAR doesn't + // seem to issue such zero length blocks. if (CheckSum!=SavedCheckSum) return false; Header.BlockStart=Inp.InAddr; + + // We called Inp.faddbits(8) above, thus Header.BlockStart can't be 0 here. + // So there is no overflow even if Header.BlockSize is 0. ReadBorder=Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); Header.LastBlockInFile=(BlockFlags & 0x40)!=0; Header.TablePresent=(BlockFlags & 0x80)!=0; + return true; } @@ -614,8 +647,8 @@ bool Unpack::ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTable MakeDecodeTables(BitLength,&Tables.BD,BC); - byte Table[HUFF_TABLE_SIZE]; - const uint TableSize=HUFF_TABLE_SIZE; + byte Table[HUFF_TABLE_SIZEX]; + const uint TableSize=ExtraDist ? HUFF_TABLE_SIZEX:HUFF_TABLE_SIZEB; for (uint I=0;IReadTop-5) @@ -678,14 +711,15 @@ bool Unpack::ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTable if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop) return false; MakeDecodeTables(&Table[0],&Tables.LD,NC); - MakeDecodeTables(&Table[NC],&Tables.DD,DC); - MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); - MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); + uint DCodes=ExtraDist ? DCX : DCB; + MakeDecodeTables(&Table[NC],&Tables.DD,DCodes); + MakeDecodeTables(&Table[NC+DCodes],&Tables.LDD,LDC); + MakeDecodeTables(&Table[NC+DCodes+LDC],&Tables.RD,RC); return true; } void Unpack::InitFilters() { - Filters.SoftReset(); + Filters.clear(); } diff --git a/unrar/unpack50frag.cpp b/unrar/unpack50frag.cpp index 3c008ff2..9208405e 100644 --- a/unrar/unpack50frag.cpp +++ b/unrar/unpack50frag.cpp @@ -2,6 +2,7 @@ FragmentedWindow::FragmentedWindow() { memset(Mem,0,sizeof(Mem)); memset(MemSize,0,sizeof(MemSize)); + LastAllocated=0; } @@ -13,6 +14,7 @@ FragmentedWindow::~FragmentedWindow() void FragmentedWindow::Reset() { + LastAllocated=0; for (uint I=0;IUnpPtr) + { + SrcPtr+=MaxWinSize; + + if (Distance>MaxWinSize || !FirstWinDone) + { + while (Length-- > 0) + { + (*this)[UnpPtr]=0; + if (++UnpPtr>=MaxWinSize) + UnpPtr-=MaxWinSize; + } + return; + } + } + while (Length-- > 0) { - (*this)[UnpPtr]=(*this)[SrcPtr++ & MaxWinMask]; - // We need to have masked UnpPtr after quit from loop, so it must not - // be replaced with '(*this)[UnpPtr++ & MaxWinMask]' - UnpPtr=(UnpPtr+1) & MaxWinMask; + (*this)[UnpPtr]=(*this)[SrcPtr]; + if (++SrcPtr>=MaxWinSize) + SrcPtr-=MaxWinSize; + if (++UnpPtr>=MaxWinSize) + UnpPtr-=MaxWinSize; } } diff --git a/unrar/unpack50mt.cpp b/unrar/unpack50mt.cpp index 691ac8e9..3d3fde59 100644 --- a/unrar/unpack50mt.cpp +++ b/unrar/unpack50mt.cpp @@ -1,3 +1,5 @@ +// 2023.09.09: 0x400000 and 2 are optimal for i9-12900K. +// Further increasing the buffer size reduced the extraction speed. #define UNP_READ_SIZE_MT 0x400000 #define UNP_BLOCKS_PER_THREAD 2 @@ -277,7 +279,7 @@ void Unpack::Unpack5MT(bool Solid) } } } - UnpPtr&=MaxWinMask; // ProcessDecoded and maybe others can leave UnpPtr > MaxWinMask here. + UnpPtr=WrapUp(UnpPtr); // ProcessDecoded and maybe others can leave UnpPtr >= MaxWinSize here. UnpWriteBuf(); BlockHeader=UnpThreadData[LastBlockNum].BlockHeader; @@ -345,7 +347,7 @@ void Unpack::UnpackDecode(UnpackThreadData &D) if (D.DecodedSize>1) { UnpackDecodedItem *PrevItem=CurItem-1; - if (PrevItem->Type==UNPDT_LITERAL && PrevItem->Length<3) + if (PrevItem->Type==UNPDT_LITERAL && PrevItem->LengthLiteral)-1) { PrevItem->Length++; PrevItem->Literal[PrevItem->Length]=(byte)MainSlot; @@ -362,7 +364,8 @@ void Unpack::UnpackDecode(UnpackThreadData &D) { uint Length=SlotToLength(D.Inp,MainSlot-262); - uint DBits,Distance=1,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD); + size_t Distance=1; + uint DBits,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD); if (DistSlot<4) { DBits=0; @@ -371,7 +374,7 @@ void Unpack::UnpackDecode(UnpackThreadData &D) else { DBits=DistSlot/2 - 1; - Distance+=(2 | (DistSlot & 1)) << DBits; + Distance+=size_t(2 | (DistSlot & 1)) << DBits; } if (DBits>0) @@ -380,15 +383,27 @@ void Unpack::UnpackDecode(UnpackThreadData &D) { if (DBits>4) { - Distance+=((D.Inp.getbits32()>>(36-DBits))<<4); + // It is also possible to always use getbits64() here. + if (DBits>36) + Distance+=( ( size_t(D.Inp.getbits64() ) >> (68-DBits) ) << 4 ); + else + Distance+=( ( size_t(D.Inp.getbits32() ) >> (36-DBits) ) << 4 ); D.Inp.addbits(DBits-4); } uint LowDist=DecodeNumber(D.Inp,&D.BlockTables.LDD); Distance+=LowDist; + + // Distance can be 0 for multiples of 4 GB as result of size_t + // overflow in 32-bit build. Its lower 32-bit can also erroneously + // fit into dictionary after truncating upper 32-bits. Replace such + // invalid distances with -1, so CopyString sets 0 data for them. + // DBits>=30 also as DistSlot>=62 indicate distances >=0x80000001. + if (sizeof(Distance)==4 && DBits>=30) + Distance=(size_t)-1; } else { - Distance+=D.Inp.getbits32()>>(32-DBits); + Distance+=D.Inp.getbits()>>(16-DBits); D.Inp.addbits(DBits); } } @@ -450,8 +465,12 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D) UnpackDecodedItem *Item=D.Decoded,*Border=D.Decoded+D.DecodedSize; while (ItemUnpPtr); + PrevPtr=UnpPtr; + + if (WrapDown(WriteBorder-UnpPtr)<=MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr) { UnpWriteBuf(); if (WrittenFileSize>DestUnpSize) @@ -461,15 +480,15 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D) if (Item->Type==UNPDT_LITERAL) { #if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) - if (Item->Length==3 && UnpPtrLength==7 && UnpPtrLiteral; - UnpPtr+=4; + *(uint64 *)(Window+UnpPtr)=*(uint64 *)(Item->Literal); + UnpPtr+=8; } else #endif for (uint I=0;I<=Item->Length;I++) - Window[UnpPtr++ & MaxWinMask]=Item->Literal[I]; + Window[WrapUp(UnpPtr++)]=Item->Literal[I]; } else if (Item->Type==UNPDT_MATCH) @@ -481,8 +500,8 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D) else if (Item->Type==UNPDT_REP) { - uint Distance=OldDist[Item->Distance]; - for (uint I=Item->Distance;I>0;I--) + size_t Distance=OldDist[Item->Distance]; + for (size_t I=Item->Distance;I>0;I--) OldDist[I]=OldDist[I-1]; OldDist[0]=Distance; LastLength=Item->Length; @@ -505,7 +524,7 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D) Item++; Filter.Channels=(byte)Item->Length; - Filter.BlockLength=Item->Distance; + Filter.BlockLength=(uint)Item->Distance; AddFilter(Filter); } @@ -543,7 +562,11 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) while (true) { - UnpPtr&=MaxWinMask; + UnpPtr=WrapUp(UnpPtr); + + FirstWinDone|=(PrevPtr>UnpPtr); + PrevPtr=UnpPtr; + if (D.Inp.InAddr>=ReadBorder) { if (D.Inp.InAddr>BlockBorder || D.Inp.InAddr==BlockBorder && @@ -559,7 +582,7 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) break; } } - if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) @@ -576,7 +599,8 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) { uint Length=SlotToLength(D.Inp,MainSlot-262); - uint DBits,Distance=1,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD); + size_t Distance=1; + uint DBits,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD); if (DistSlot<4) { DBits=0; @@ -585,7 +609,7 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) else { DBits=DistSlot/2 - 1; - Distance+=(2 | (DistSlot & 1)) << DBits; + Distance+=size_t(2 | (DistSlot & 1)) << DBits; } if (DBits>0) @@ -594,11 +618,23 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) { if (DBits>4) { - Distance+=((D.Inp.getbits32()>>(36-DBits))<<4); + // It is also possible to always use getbits64() here. + if (DBits>36) + Distance+=( ( size_t(D.Inp.getbits64() ) >> (68-DBits) ) << 4 ); + else + Distance+=( ( size_t(D.Inp.getbits32() ) >> (36-DBits) ) << 4 ); D.Inp.addbits(DBits-4); } uint LowDist=DecodeNumber(D.Inp,&D.BlockTables.LDD); Distance+=LowDist; + + // Distance can be 0 for multiples of 4 GB as result of size_t + // overflow in 32-bit build. Its lower 32-bit can also erroneously + // fit into dictionary after truncating upper 32-bits. Replace such + // invalid distances with -1, so CopyString sets 0 data for them. + // DBits>=30 also as DistSlot>=62 indicate distances >=0x80000001. + if (sizeof(Distance)==4 && DBits>=30) + Distance=(size_t)-1; } else { @@ -639,7 +675,7 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) if (MainSlot<262) { uint DistNum=MainSlot-258; - uint Distance=OldDist[DistNum]; + size_t Distance=OldDist[DistNum]; for (uint I=DistNum;I>0;I--) OldDist[I]=OldDist[I-1]; OldDist[0]=Distance; diff --git a/unrar/unpackinline.cpp b/unrar/unpackinline.cpp index 04c3d1f7..68aec917 100644 --- a/unrar/unpackinline.cpp +++ b/unrar/unpackinline.cpp @@ -1,4 +1,4 @@ -_forceinline void Unpack::InsertOldDist(uint Distance) +_forceinline void Unpack::InsertOldDist(size_t Distance) { OldDist[3]=OldDist[2]; OldDist[2]=OldDist[1]; @@ -6,23 +6,58 @@ _forceinline void Unpack::InsertOldDist(uint Distance) OldDist[0]=Distance; } -#ifdef _MSC_VER -#define FAST_MEMCPY +#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) +#define UNPACK_COPY8 // We can copy 8 bytes at any position as uint64. #endif -_forceinline void Unpack::CopyString(uint Length,uint Distance) +_forceinline void Unpack::CopyString(uint Length,size_t Distance) { size_t SrcPtr=UnpPtr-Distance; + + // Perform the correction here instead of "else", so matches crossing + // the window beginning can also be processed by first "if" part. + if (Distance>UnpPtr) // Unlike SrcPtr>=MaxWinSize, it catches invalid distances like 0xfffffff0 in 32-bit build. + { + // Same as WrapDown(SrcPtr), needed because of UnpPtr-Distance above. + // We need the same condition below, so we expanded WrapDown() here. + SrcPtr+=MaxWinSize; + + // About Distance>MaxWinSize check. + // SrcPtr can be >=MaxWinSize if distance exceeds MaxWinSize + // in a malformed archive. Our WrapDown replacement above might not + // correct it, so to prevent out of bound Window read we check it here. + // Unlike SrcPtr>=MaxWinSize check, it allows MaxWinSize>0x80000000 + // in 32-bit build, which could cause overflow in SrcPtr. + // About !FirstWinDone check. + // If first window hasn't filled yet and it points outside of window, + // set data to 0 instead of copying preceding file data, so result doesn't + // depend on previously extracted files in non-solid archive. + if (Distance>MaxWinSize || !FirstWinDone) + { + // Fill area of specified length with 0 instead of returning. + // So if only the distance is broken and rest of packed data is valid, + // it preserves offsets and allows to continue extraction. + // If we set SrcPtr to random offset instead, let's say, 0, + // we still will be copying preceding file data if UnpPtr is also 0. + while (Length-- > 0) + { + Window[UnpPtr]=0; + UnpPtr=WrapUp(UnpPtr+1); + } + return; + } + } + if (SrcPtr=8) @@ -40,7 +75,7 @@ _forceinline void Unpack::CopyString(uint Length,uint Distance) Dest+=8; Length-=8; } -#ifdef FAST_MEMCPY +#ifdef UNPACK_COPY8 else while (Length>=8) { @@ -49,9 +84,7 @@ _forceinline void Unpack::CopyString(uint Length,uint Distance) // But for real RAR archives Distance <= MaxWinSize - MAX_INC_LZ_MATCH // always, so overlap here is impossible. - // This memcpy expanded inline by MSVC. We could also use uint64 - // assignment, which seems to provide about the same speed. - memcpy(Dest,Src,8); + RawPut8(RawGet8(Src),Dest); Src+=8; Dest+=8; @@ -71,10 +104,10 @@ _forceinline void Unpack::CopyString(uint Length,uint Distance) else while (Length-- > 0) // Slow copying with all possible precautions. { - Window[UnpPtr]=Window[SrcPtr++ & MaxWinMask]; + Window[UnpPtr]=Window[WrapUp(SrcPtr++)]; // We need to have masked UnpPtr after quit from loop, so it must not - // be replaced with 'Window[UnpPtr++ & MaxWinMask]' - UnpPtr=(UnpPtr+1) & MaxWinMask; + // be replaced with 'Window[WrapUp(UnpPtr++)]' + UnpPtr=WrapUp(UnpPtr+1); } } diff --git a/unrar/uowners.cpp b/unrar/uowners.cpp index c0190a92..cbe2f404 100644 --- a/unrar/uowners.cpp +++ b/unrar/uowners.cpp @@ -1,61 +1,17 @@ -void ExtractUnixOwner20(Archive &Arc,const wchar *FileName) -{ - char NameA[NM]; - WideToChar(FileName,NameA,ASIZE(NameA)); - - if (Arc.BrokenHeader) - { - uiMsg(UIERROR_UOWNERBROKEN,Arc.FileName,FileName); - ErrHandler.SetErrorCode(RARX_CRC); - return; - } - - struct passwd *pw; - errno=0; // Required by getpwnam specification if we need to check errno. - if ((pw=getpwnam(Arc.UOHead.OwnerName))==NULL) - { - uiMsg(UIERROR_UOWNERGETOWNERID,Arc.FileName,GetWide(Arc.UOHead.OwnerName)); - ErrHandler.SysErrMsg(); - ErrHandler.SetErrorCode(RARX_WARNING); - return; - } - uid_t OwnerID=pw->pw_uid; - - struct group *gr; - errno=0; // Required by getgrnam specification if we need to check errno. - if ((gr=getgrnam(Arc.UOHead.GroupName))==NULL) - { - uiMsg(UIERROR_UOWNERGETGROUPID,Arc.FileName,GetWide(Arc.UOHead.GroupName)); - ErrHandler.SysErrMsg(); - ErrHandler.SetErrorCode(RARX_CRC); - return; - } - uint Attr=GetFileAttr(FileName); - gid_t GroupID=gr->gr_gid; -#if defined(SAVE_LINKS) && !defined(_APPLE) - if (lchown(NameA,OwnerID,GroupID)!=0) -#else - if (chown(NameA,OwnerID,GroupID)!=0) -#endif - { - uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName); - ErrHandler.SetErrorCode(RARX_CREATE); - } - SetFileAttr(FileName,Attr); -} void ExtractUnixOwner30(Archive &Arc,const wchar *FileName) { - char NameA[NM]; - WideToChar(FileName,NameA,ASIZE(NameA)); + // There must be 0 byte between owner and group strings. + // Otherwise strlen call below wouldn't be safe. + if (memchr(Arc.SubHead.SubData.data(),0,Arc.SubHead.SubData.size())==NULL) + return; - char *OwnerName=(char *)&Arc.SubHead.SubData[0]; + char *OwnerName=(char *)Arc.SubHead.SubData.data(); int OwnerSize=strlen(OwnerName)+1; - int GroupSize=Arc.SubHead.SubData.Size()-OwnerSize; - char GroupName[NM]; - strncpy(GroupName,(char *)&Arc.SubHead.SubData[OwnerSize],GroupSize); - GroupName[GroupSize]=0; + int GroupSize=Arc.SubHead.SubData.size()-OwnerSize; + char *GroupName=(char *)&Arc.SubHead.SubData[OwnerSize]; + std::string GroupStr(GroupName,GroupName+GroupSize); struct passwd *pw; if ((pw=getpwnam(OwnerName))==NULL) @@ -67,7 +23,7 @@ void ExtractUnixOwner30(Archive &Arc,const wchar *FileName) uid_t OwnerID=pw->pw_uid; struct group *gr; - if ((gr=getgrnam(GroupName))==NULL) + if ((gr=getgrnam(GroupStr.c_str()))==NULL) { uiMsg(UIERROR_UOWNERGETGROUPID,Arc.FileName,GetWide(GroupName)); ErrHandler.SetErrorCode(RARX_WARNING); @@ -75,10 +31,14 @@ void ExtractUnixOwner30(Archive &Arc,const wchar *FileName) } uint Attr=GetFileAttr(FileName); gid_t GroupID=gr->gr_gid; + + std::string NameA; + WideToChar(FileName,NameA); + #if defined(SAVE_LINKS) && !defined(_APPLE) - if (lchown(NameA,OwnerID,GroupID)!=0) + if (lchown(NameA.c_str(),OwnerID,GroupID)!=0) #else - if (chown(NameA,OwnerID,GroupID)!=0) + if (chown(NameA.c_str(),OwnerID,GroupID)!=0) #endif { uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName); @@ -88,11 +48,8 @@ void ExtractUnixOwner30(Archive &Arc,const wchar *FileName) } -void SetUnixOwner(Archive &Arc,const wchar *FileName) +void SetUnixOwner(Archive &Arc,const std::wstring &FileName) { - char NameA[NM]; - WideToChar(FileName,NameA,ASIZE(NameA)); - // First, we try to resolve symbolic names. If they are missing or cannot // be resolved, we try to use numeric values if any. If numeric values // are missing too, function fails. @@ -127,10 +84,14 @@ void SetUnixOwner(Archive &Arc,const wchar *FileName) else hd.UnixGroupID=gr->gr_gid; } + + std::string NameA; + WideToChar(FileName,NameA); + #if defined(SAVE_LINKS) && !defined(_APPLE) - if (lchown(NameA,hd.UnixOwnerID,hd.UnixGroupID)!=0) + if (lchown(NameA.c_str(),hd.UnixOwnerID,hd.UnixGroupID)!=0) #else - if (chown(NameA,hd.UnixOwnerID,hd.UnixGroupID)!=0) + if (chown(NameA.c_str(),hd.UnixOwnerID,hd.UnixGroupID)!=0) #endif { uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName); diff --git a/unrar/version.hpp b/unrar/version.hpp index db320e84..e95614b2 100644 --- a/unrar/version.hpp +++ b/unrar/version.hpp @@ -1,7 +1,7 @@ -#define RARVER_MAJOR 6 -#define RARVER_MINOR 0 -#define RARVER_BETA 2 +#define RARVER_MAJOR 7 +#define RARVER_MINOR 20 +#define RARVER_BETA 0 #define RARVER_PATCH 1 -#define RARVER_DAY 12 -#define RARVER_MONTH 11 -#define RARVER_YEAR 2020 +#define RARVER_DAY 1 +#define RARVER_MONTH 2 +#define RARVER_YEAR 2026 diff --git a/unrar/volume.cpp b/unrar/volume.cpp index 3e46b24e..e12324e0 100644 --- a/unrar/volume.cpp +++ b/unrar/volume.cpp @@ -1,15 +1,15 @@ #include "rar.hpp" #ifdef RARDLL -static bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize); -static bool DllVolNotify(RAROptions *Cmd,wchar *NextName); +bool DllVolChange(CommandData *Cmd,std::wstring &NextName); +static bool DllVolNotify(CommandData *Cmd,const std::wstring &NextName); #endif bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Command) { - RAROptions *Cmd=Arc.GetRAROptions(); + CommandData *Cmd=Arc.GetCommandData(); HEADER_TYPE HeaderType=Arc.GetHeaderType(); FileHeader *hd=HeaderType==HEAD_SERVICE ? &Arc.SubHead:&Arc.FileHead; @@ -25,28 +25,46 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma uiMsg(UIERROR_CHECKSUMPACKED, Arc.FileName, hd->FileName); } + bool PrevVolEncrypted=Arc.Encrypted; + int64 PosBeforeClose=Arc.Tell(); if (DataIO!=NULL) - DataIO->ProcessedArcSize+=Arc.FileLength(); + DataIO->ProcessedArcSize+=DataIO->LastArcSize; Arc.Close(); - wchar NextName[NM]; - wcsncpyz(NextName,Arc.FileName,ASIZE(NextName)); - NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); + std::wstring NextName=Arc.FileName; + NextVolumeName(NextName,!Arc.NewNumbering); #if !defined(SFX_MODULE) && !defined(RARDLL) bool RecoveryDone=false; #endif - bool FailedOpen=false,OldSchemeTested=false; + bool OldSchemeTested=false; + bool FailedOpen=false; // No more next volume open attempts if true. #if !defined(SILENT) // In -vp mode we force the pause before next volume even if it is present // and even if we are on the hard disk. It is important when user does not // want to process partially downloaded volumes preliminary. - if (Cmd->VolumePause && !uiAskNextVolume(NextName,ASIZE(NextName))) + // 2022.01.11: In WinRAR 6.10 beta versions we tried to ignore VolumePause + // if we could open the next volume with FMF_OPENEXCLUSIVE. But another + // developer asked us to return the previous behavior and always prompt + // for confirmation. They want to control when unrar continues, because + // the next file might not be fully decoded yet. They write chunks of data + // and then close the file again until the next chunk comes in. + + if (Cmd->VolumePause && !uiAskNextVolume(NextName)) + FailedOpen=true; +#endif + +#ifdef _UNIX + // open() function in Unix can open a directory. But if directory has a name + // of next volume, it would result in read error and Retry/Quit prompt. + // So we skip directories in advance here. This check isn't needed + // in Windows, where opening directories fails here. + if (FileExist(NextName) && IsDir(GetFileAttr(NextName))) FailedOpen=true; #endif @@ -66,23 +84,21 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma { // Checking for new style volumes renamed by user to old style // name format. Some users did it for unknown reason. - wchar AltNextName[NM]; - wcsncpyz(AltNextName,Arc.FileName,ASIZE(AltNextName)); - NextVolumeName(AltNextName,ASIZE(AltNextName),true); + std::wstring AltNextName=Arc.FileName; + NextVolumeName(AltNextName,true); OldSchemeTested=true; if (Arc.Open(AltNextName,OpenMode)) { - wcsncpyz(NextName,AltNextName,ASIZE(NextName)); + NextName=AltNextName; break; } } #ifdef RARDLL - //rar extension depends on NextName having size NM - if (!DllVolChange(Cmd,NextName,ASIZE(NextName))) - { - FailedOpen=true; - break; - } + if (!DllVolChange(Cmd,NextName)) + { + FailedOpen=true; + break; + } #else // !RARDLL #ifndef SFX_MODULE @@ -100,7 +116,7 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma break; } #ifndef SILENT - if (Cmd->AllYes || !uiAskNextVolume(NextName,ASIZE(NextName))) + if (Cmd->AllYes || !uiAskNextVolume(NextName)) #endif { FailedOpen=true; @@ -112,6 +128,7 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma if (FailedOpen) { + ErrHandler.SetErrorCode(RARX_OPEN); uiMsg(UIERROR_MISSINGVOL,NextName); Arc.Open(Arc.FileName,OpenMode); Arc.Seek(PosBeforeClose,SEEK_SET); @@ -119,7 +136,7 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma } if (Command=='T' || Command=='X' || Command=='E') - mprintf(St(Command=='T' ? MTestVol:MExtrVol),Arc.FileName); + mprintf(St(Command=='T' ? MTestVol:MExtrVol),Arc.FileName.c_str()); Arc.CheckArc(true); @@ -128,6 +145,16 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma return false; #endif + if (Arc.Encrypted!=PrevVolEncrypted) + { + // There is no legitimate reason for encrypted header state to be + // changed in the middle of volume sequence. So we abort here to prevent + // replacing an encrypted header volume to unencrypted and adding + // unexpected files by third party to encrypted extraction. + uiMsg(UIERROR_BADARCHIVE,Arc.FileName); + ErrHandler.Exit(RARX_BADARC); + } + if (SplitHeader) Arc.SearchBlock(HeaderType); else @@ -137,9 +164,9 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma Arc.ConvertAttributes(); Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET); } - if (ShowFileName) + if (ShowFileName && !Cmd->DisableNames) { - mprintf(St(MExtrPoints),Arc.FileHead.FileName); + mprintf(St(MExtrPoints),Arc.FileHead.FileName.c_str()); if (!Cmd->DisablePercentage) mprintf(L" "); } @@ -152,10 +179,9 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma DataIO->UnpVolume=hd->SplitAfter; DataIO->SetPackedSizeToRead(hd->PackSize); } -#ifdef SFX_MODULE - DataIO->UnpArcSize=Arc.FileLength(); -#endif - + + DataIO->AdjustTotalArcSize(&Arc); + // Reset the size of packed data read from current volume. It is used // to display the total progress and preceding volumes are already // compensated with ProcessedArcSize, so we need to reset this variable. @@ -172,73 +198,65 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma #ifdef RARDLL -#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64) -// Disable the run time stack check for unrar.dll, so we can manipulate -// with ChangeVolProc call type below. Run time check would intercept -// a wrong ESP before we restore it. -#pragma runtime_checks( "s", off ) -#endif - -bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize) +bool DllVolChange(CommandData *Cmd,std::wstring &NextName) { bool DllVolChanged=false,DllVolAborted=false; if (Cmd->Callback!=NULL) { - wchar OrgNextName[NM]; - wcsncpyz(OrgNextName,NextName,ASIZE(OrgNextName)); - if (Cmd->Callback(UCM_CHANGEVOLUMEW,Cmd->UserData,(LPARAM)NextName,RAR_VOL_ASK)==-1) + std::wstring OrgNextName=NextName; + + std::vector NameBuf(MAXPATHSIZE); + std::copy(NextName.data(), NextName.data() + NextName.size() + 1, NameBuf.begin()); + + if (Cmd->Callback(UCM_CHANGEVOLUMEW,Cmd->UserData,(LPARAM)NameBuf.data(),RAR_VOL_ASK)==-1) DllVolAborted=true; else - if (wcscmp(OrgNextName,NextName)!=0) + { + NextName=NameBuf.data(); + if (OrgNextName!=NextName) DllVolChanged=true; else { - char NextNameA[NM],OrgNextNameA[NM]; - WideToChar(NextName,NextNameA,ASIZE(NextNameA)); - strncpyz(OrgNextNameA,NextNameA,ASIZE(OrgNextNameA)); - if (Cmd->Callback(UCM_CHANGEVOLUME,Cmd->UserData,(LPARAM)NextNameA,RAR_VOL_ASK)==-1) + std::string NextNameA; + WideToChar(NextName,NextNameA); + std::string OrgNextNameA=NextNameA; + + std::vector NameBufA(MAXPATHSIZE); + std::copy(NextNameA.data(), NextNameA.data() + NextNameA.size() + 1, NameBufA.begin()); + + if (Cmd->Callback(UCM_CHANGEVOLUME,Cmd->UserData,(LPARAM)NameBufA.data(),RAR_VOL_ASK)==-1) DllVolAborted=true; else - if (strcmp(OrgNextNameA,NextNameA)!=0) + { + NextNameA=NameBufA.data(); + if (OrgNextNameA!=NextNameA) { // We can damage some Unicode characters by U->A->U conversion, // so set Unicode name only if we see that ANSI name is changed. - CharToWide(NextNameA,NextName,NameSize); + CharToWide(NextNameA,NextName); DllVolChanged=true; } + } } + } } if (!DllVolChanged && Cmd->ChangeVolProc!=NULL) { - char NextNameA[NM]; - WideToChar(NextName,NextNameA,ASIZE(NextNameA)); - // Here we preserve ESP value. It is necessary for those developers, - // who still define ChangeVolProc callback as "C" type function, - // even though in year 2001 we announced in unrar.dll whatsnew.txt - // that it will be PASCAL type (for compatibility with Visual Basic). -#if defined(_MSC_VER) -#ifndef _WIN_64 - __asm mov ebx,esp -#endif -#elif defined(_WIN_ALL) && defined(__BORLANDC__) - _EBX=_ESP; -#endif - int RetCode=Cmd->ChangeVolProc(NextNameA,RAR_VOL_ASK); + std::string NextNameA; + WideToChar(NextName,NextNameA); - // Restore ESP after ChangeVolProc with wrongly defined calling - // convention broken it. -#if defined(_MSC_VER) -#ifndef _WIN_64 - __asm mov esp,ebx -#endif -#elif defined(_WIN_ALL) && defined(__BORLANDC__) - _ESP=_EBX; -#endif + std::vector NameBufA(MAXPATHSIZE); + std::copy(NextNameA.data(), NextNameA.data() + NextNameA.size() + 1, NameBufA.begin()); + + int RetCode=Cmd->ChangeVolProc(NameBufA.data(),RAR_VOL_ASK); if (RetCode==0) DllVolAborted=true; else - CharToWide(NextNameA,NextName,NameSize); + { + NextNameA=NameBufA.data(); + CharToWide(NextNameA,NextName); + } } // We quit only on 'abort' condition, but not on 'name not changed'. @@ -256,34 +274,24 @@ bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize) #ifdef RARDLL -bool DllVolNotify(RAROptions *Cmd,wchar *NextName) +static bool DllVolNotify(CommandData *Cmd,const std::wstring &NextName) { - char NextNameA[NM]; - WideToChar(NextName,NextNameA,ASIZE(NextNameA)); + std::string NextNameA; + WideToChar(NextName,NextNameA); + if (Cmd->Callback!=NULL) { - if (Cmd->Callback(UCM_CHANGEVOLUMEW,Cmd->UserData,(LPARAM)NextName,RAR_VOL_NOTIFY)==-1) + if (Cmd->Callback(UCM_CHANGEVOLUMEW,Cmd->UserData,(LPARAM)NextName.data(),RAR_VOL_NOTIFY)==-1) return false; - if (Cmd->Callback(UCM_CHANGEVOLUME,Cmd->UserData,(LPARAM)NextNameA,RAR_VOL_NOTIFY)==-1) + if (Cmd->Callback(UCM_CHANGEVOLUME,Cmd->UserData,(LPARAM)NextNameA.data(),RAR_VOL_NOTIFY)==-1) return false; } if (Cmd->ChangeVolProc!=NULL) { -#if defined(_WIN_ALL) && !defined(_MSC_VER) && !defined(__MINGW32__) - _EBX=_ESP; -#endif - int RetCode=Cmd->ChangeVolProc(NextNameA,RAR_VOL_NOTIFY); -#if defined(_WIN_ALL) && !defined(_MSC_VER) && !defined(__MINGW32__) - _ESP=_EBX; -#endif + int RetCode=Cmd->ChangeVolProc((char *)NextNameA.data(),RAR_VOL_NOTIFY); if (RetCode==0) return false; } return true; } - -#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64) -// Restore the run time stack check for unrar.dll. -#pragma runtime_checks( "s", restore ) -#endif #endif diff --git a/unrar/volume.hpp b/unrar/volume.hpp index 2d6a6d5c..4ada1091 100644 --- a/unrar/volume.hpp +++ b/unrar/volume.hpp @@ -1,10 +1,7 @@ #ifndef _RAR_VOLUME_ #define _RAR_VOLUME_ -void SplitArchive(Archive &Arc,FileHeader *fh,int64 *HeaderPos, - ComprDataIO *DataIO); bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName, wchar Command); -void SetVolWrite(Archive &Dest,int64 VolSize); #endif diff --git a/unrar/win32acl.cpp b/unrar/win32acl.cpp index d4797bde..091838fe 100644 --- a/unrar/win32acl.cpp +++ b/unrar/win32acl.cpp @@ -5,7 +5,7 @@ static bool ReadSacl=false; #ifndef SFX_MODULE -void ExtractACL20(Archive &Arc,const wchar *FileName) +void ExtractACL20(Archive &Arc,const std::wstring &FileName) { SetACLPrivileges(); @@ -27,7 +27,7 @@ void ExtractACL20(Archive &Arc,const wchar *FileName) Unpack Unpack(&DataIO); Unpack.Init(0x10000,false); - Array UnpData(Arc.EAHead.UnpSize); + std::vector UnpData(Arc.EAHead.UnpSize); DataIO.SetUnpackToMemory(&UnpData[0],Arc.EAHead.UnpSize); DataIO.SetPackedSizeToRead(Arc.EAHead.DataSize); DataIO.EnableShowProgress(false); @@ -49,7 +49,7 @@ void ExtractACL20(Archive &Arc,const wchar *FileName) si|=SACL_SECURITY_INFORMATION; SECURITY_DESCRIPTOR *sd=(SECURITY_DESCRIPTOR *)&UnpData[0]; - int SetCode=SetFileSecurity(FileName,si,sd); + int SetCode=SetFileSecurity(FileName.c_str(),si,sd); if (!SetCode) { @@ -64,9 +64,9 @@ void ExtractACL20(Archive &Arc,const wchar *FileName) #endif -void ExtractACL(Archive &Arc,const wchar *FileName) +void ExtractACL(Archive &Arc,const std::wstring &FileName) { - Array SubData; + std::vector SubData; if (!Arc.ReadSubData(&SubData,NULL,false)) return; @@ -78,12 +78,12 @@ void ExtractACL(Archive &Arc,const wchar *FileName) si|=SACL_SECURITY_INFORMATION; SECURITY_DESCRIPTOR *sd=(SECURITY_DESCRIPTOR *)&SubData[0]; - int SetCode=SetFileSecurity(FileName,si,sd); + int SetCode=SetFileSecurity(FileName.c_str(),si,sd); if (!SetCode) { - wchar LongName[NM]; - if (GetWinLongPath(FileName,LongName,ASIZE(LongName))) - SetCode=SetFileSecurity(LongName,si,sd); + std::wstring LongName; + if (GetWinLongPath(FileName,LongName)) + SetCode=SetFileSecurity(LongName.c_str(),si,sd); } if (!SetCode) @@ -110,26 +110,3 @@ void SetACLPrivileges() InitDone=true; } - - -bool SetPrivilege(LPCTSTR PrivName) -{ - bool Success=false; - - HANDLE hToken; - if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) - { - TOKEN_PRIVILEGES tp; - tp.PrivilegeCount = 1; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - if (LookupPrivilegeValue(NULL,PrivName,&tp.Privileges[0].Luid) && - AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) && - GetLastError() == ERROR_SUCCESS) - Success=true; - - CloseHandle(hToken); - } - - return Success; -} diff --git a/unrar/win32lnk.cpp b/unrar/win32lnk.cpp index a68ed75a..65a38c27 100644 --- a/unrar/win32lnk.cpp +++ b/unrar/win32lnk.cpp @@ -40,26 +40,24 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) PrivSet=true; } - const DWORD BufSize=sizeof(REPARSE_DATA_BUFFER)+2*NM+1024; - Array Buf(BufSize); - REPARSE_DATA_BUFFER *rdb=(REPARSE_DATA_BUFFER *)&Buf[0]; - - wchar SubstName[NM]; - wcsncpyz(SubstName,hd->RedirName,ASIZE(SubstName)); - size_t SubstLength=wcslen(SubstName); - - wchar PrintName[NM],*PrintNameSrc=SubstName,*PrintNameDst=PrintName; - bool WinPrefix=wcsncmp(PrintNameSrc,L"\\??\\",4)==0; - if (WinPrefix) - PrintNameSrc+=4; - if (WinPrefix && wcsncmp(PrintNameSrc,L"UNC\\",4)==0) - { - *(PrintNameDst++)='\\'; // Insert second \ in beginning of share name. - PrintNameSrc+=3; - } - wcscpy(PrintNameDst,PrintNameSrc); + const std::wstring &SubstName=hd->RedirName; + size_t SubstLength=SubstName.size(); + + // REPARSE_DATA_BUFFER receives both SubstName and PrintName strings, + // thus "*2" below. PrintName is either shorter or same length as SubstName. + const DWORD BufSize=sizeof(REPARSE_DATA_BUFFER)+((DWORD)SubstLength+1)*2*sizeof(wchar); + + std::vector Buf(BufSize); + REPARSE_DATA_BUFFER *rdb=(REPARSE_DATA_BUFFER *)Buf.data(); + + // Remove \??\ NTFS junction prefix of present. + bool WinPrefix=starts_with(SubstName,L"\\??\\"); + std::wstring PrintName=WinPrefix ? SubstName.substr(4):SubstName; - size_t PrintLength=wcslen(PrintName); + if (WinPrefix && starts_with(PrintName,L"UNC\\")) + PrintName=L"\\"+PrintName.substr(3); // Convert UNC\server\share to \\server\share. + + size_t PrintLength=PrintName.size(); bool AbsPath=WinPrefix; // IsFullPath is not really needed here, AbsPath check is enough. @@ -69,22 +67,42 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) // path as a prefix, which can confuse IsRelativeSymlinkSafe algorithm. if (!Cmd->AbsoluteLinks && (AbsPath || IsFullPath(hd->RedirName) || !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,hd->RedirName))) + { + uiMsg(UIERROR_SKIPUNSAFELINK,hd->FileName,hd->RedirName); + ErrHandler.SetErrorCode(RARX_WARNING); return false; + } - CreatePath(Name,true); + CreatePath(Name,true,Cmd->DisableNames); + + // Overwrite prompt was already issued and confirmed earlier, so we can + // remove existing symlink or regular file here. PrepareToDelete was also + // called earlier inside of uiAskReplaceEx. + if (FileExist(Name)) + if (IsDir(GetFileAttr(Name))) + DelDir(Name); + else + DelFile(Name); // 'DirTarget' check is important for Unix symlinks to directories. // Unix symlinks do not have their own 'directory' attribute. if (hd->Dir || hd->DirTarget) { - if (!CreateDirectory(Name,NULL)) + if (!CreateDir(Name)) + { + uiMsg(UIERROR_DIRCREATE,L"",Name); + ErrHandler.SetErrorCode(RARX_CREATE); return false; + } } else { HANDLE hFile=CreateFile(Name,GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL); if (hFile == INVALID_HANDLE_VALUE) + { + ErrHandler.CreateErrorMsg(Name); return false; + } CloseHandle(hFile); } @@ -102,11 +120,11 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) rdb->MountPointReparseBuffer.SubstituteNameOffset=0; rdb->MountPointReparseBuffer.SubstituteNameLength=USHORT(SubstLength*sizeof(WCHAR)); - wcscpy(rdb->MountPointReparseBuffer.PathBuffer,SubstName); + wcscpy(rdb->MountPointReparseBuffer.PathBuffer,SubstName.data()); rdb->MountPointReparseBuffer.PrintNameOffset=USHORT((SubstLength+1)*sizeof(WCHAR)); rdb->MountPointReparseBuffer.PrintNameLength=USHORT(PrintLength*sizeof(WCHAR)); - wcscpy(rdb->MountPointReparseBuffer.PathBuffer+SubstLength+1,PrintName); + wcscpy(rdb->MountPointReparseBuffer.PathBuffer+SubstLength+1,PrintName.data()); } else if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_UNIXSYMLINK) @@ -123,11 +141,11 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset=0; rdb->SymbolicLinkReparseBuffer.SubstituteNameLength=USHORT(SubstLength*sizeof(WCHAR)); - wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer,SubstName); + wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer,SubstName.data()); rdb->SymbolicLinkReparseBuffer.PrintNameOffset=USHORT((SubstLength+1)*sizeof(WCHAR)); rdb->SymbolicLinkReparseBuffer.PrintNameLength=USHORT(PrintLength*sizeof(WCHAR)); - wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer+SubstLength+1,PrintName); + wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer+SubstLength+1,PrintName.data()); rdb->SymbolicLinkReparseBuffer.Flags=AbsPath ? 0:SYMLINK_FLAG_RELATIVE; } @@ -138,7 +156,11 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) OPEN_EXISTING,FILE_FLAG_OPEN_REPARSE_POINT| FILE_FLAG_BACKUP_SEMANTICS,NULL); if (hFile==INVALID_HANDLE_VALUE) + { + ErrHandler.CreateErrorMsg(Name); + ErrHandler.SetErrorCode(RARX_CREATE); return false; + } DWORD Returned; if (!DeviceIoControl(hFile,FSCTL_SET_REPARSE_POINT,rdb, @@ -146,7 +168,7 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) rdb->ReparseDataLength,NULL,0,&Returned,NULL)) { CloseHandle(hFile); - uiMsg(UIERROR_SLINKCREATE,UINULL,Name); + uiMsg(UIERROR_SLINKCREATE,L"",Name); DWORD LastError=GetLastError(); if ((LastError==ERROR_ACCESS_DENIED || LastError==ERROR_PRIVILEGE_NOT_HELD) && diff --git a/unrar/win32stm.cpp b/unrar/win32stm.cpp index eaa43be2..296eaf13 100644 --- a/unrar/win32stm.cpp +++ b/unrar/win32stm.cpp @@ -1,7 +1,39 @@ +#ifdef _WIN_ALL +// StreamName must include the leading ':'. +static bool IsNtfsProhibitedStream(const std::wstring &StreamName) +{ + // 2024.03.14: We replaced the predefined names check with simpler + // "no more than a single colon" check. Second colon could be used to + // define the type of alternate stream, but RAR archives work only with + // data streams and do not store :$DATA type in archive. It is assumed. + // So there is no legitimate use for stream type inside of archive, + // but it can be abused to hide the actual file data in file::$DATA + // or hide the actual MOTW data in Zone.Identifier:$DATA. + uint ColonCount=0; + for (wchar Ch:StreamName) + if (Ch==':' && ++ColonCount>1) + return true; + return false; +/* + const wchar *Reserved[]{ + L"::$ATTRIBUTE_LIST",L"::$BITMAP",L"::$DATA",L"::$EA",L"::$EA_INFORMATION", + L"::$FILE_NAME",L"::$INDEX_ALLOCATION",L":$I30:$INDEX_ALLOCATION", + L"::$INDEX_ROOT",L"::$LOGGED_UTILITY_STREAM",L":$EFS:$LOGGED_UTILITY_STREAM", + L":$TXF_DATA:$LOGGED_UTILITY_STREAM",L"::$OBJECT_ID",L"::$REPARSE_POINT" + }; + for (const wchar *Name : Reserved) + if (wcsicomp(StreamName,Name)==0) + return true; + return false; +*/ +} +#endif + + #if !defined(SFX_MODULE) && defined(_WIN_ALL) -void ExtractStreams20(Archive &Arc,const wchar *FileName) +void ExtractStreams20(Archive &Arc,const std::wstring &FileName) { if (Arc.BrokenHeader) { @@ -17,38 +49,46 @@ void ExtractStreams20(Archive &Arc,const wchar *FileName) return; } - wchar StreamName[NM+2]; - if (FileName[0]!=0 && FileName[1]==0) - { - // Convert single character names like f:stream to .\f:stream to - // resolve the ambiguity with drive letters. - wcsncpyz(StreamName,L".\\",ASIZE(StreamName)); - wcsncatz(StreamName,FileName,ASIZE(StreamName)); - } - else - wcsncpyz(StreamName,FileName,ASIZE(StreamName)); - if (wcslen(StreamName)+strlen(Arc.StreamHead.StreamName)>=ASIZE(StreamName) || - Arc.StreamHead.StreamName[0]!=':') + std::wstring StreamName; + CharToWide(Arc.StreamHead.StreamName,StreamName); + + if (StreamName[0]!=':' || StreamName.find_first_of(L"\\/")!=std::wstring::npos) { uiMsg(UIERROR_STREAMBROKEN,Arc.FileName,FileName); ErrHandler.SetErrorCode(RARX_CRC); return; } - wchar StoredName[NM]; - CharToWide(Arc.StreamHead.StreamName,StoredName,ASIZE(StoredName)); - ConvertPath(StoredName+1,StoredName+1,ASIZE(StoredName)-1); + // Convert single character names like f:stream to .\f:stream to + // resolve the ambiguity with drive letters. + std::wstring FullName=FileName.size()==1 ? L".\\"+FileName:FileName; + FullName+=StreamName; - wcsncatz(StreamName,StoredName,ASIZE(StreamName)); +#ifdef PROPAGATE_MOTW + // 2022.10.31: Can't easily read RAR 2.0 stream data here, so if we already + // propagated the archive Zone.Identifier stream, also known as Mark of + // the Web, to extracted file, we do not overwrite it here. + if (Arc.Motw.IsNameConflicting(StreamName)) + return; + + // 2024.02.03: Prevent using Zone.Identifier:$DATA to overwrite Zone.Identifier + // according to ZDI-CAN-23156 Trend Micro report. + // 2024.03.14: Not needed after adding check for 2+ ':' in IsNtfsProhibitedStream((). + // if (wcsnicomp(StreamName,L":Zone.Identifier:",17)==0) + // return; +#endif + + if (IsNtfsProhibitedStream(StreamName)) + return; - FindData fd; - bool Found=FindFile::FastFind(FileName,&fd); + FindData FD; + bool HostFound=FindFile::FastFind(FileName,&FD); - if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) - SetFileAttr(FileName,fd.FileAttr & ~FILE_ATTRIBUTE_READONLY); + if ((FD.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) + SetFileAttr(FileName,FD.FileAttr & ~FILE_ATTRIBUTE_READONLY); File CurFile; - if (CurFile.WCreate(StreamName)) + if (CurFile.WCreate(FullName)) { ComprDataIO DataIO; Unpack Unpack(&DataIO); @@ -69,33 +109,27 @@ void ExtractStreams20(Archive &Arc,const wchar *FileName) else CurFile.Close(); } + + // Restoring original file timestamps. File HostFile; - if (Found && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) - SetFileTime(HostFile.GetHandle(),&fd.ftCreationTime,&fd.ftLastAccessTime, - &fd.ftLastWriteTime); - if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) - SetFileAttr(FileName,fd.FileAttr); + if (HostFound && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) + SetFileTime(HostFile.GetHandle(),&FD.ftCreationTime,&FD.ftLastAccessTime, + &FD.ftLastWriteTime); + + // Restoring original file attributes. + // Important if file was read only or did not have "Archive" attribute. + if ((FD.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) + SetFileAttr(FileName,FD.FileAttr); } #endif #ifdef _WIN_ALL -void ExtractStreams(Archive &Arc,const wchar *FileName,bool TestMode) +void ExtractStreams(Archive &Arc,const std::wstring &FileName,bool TestMode) { - wchar FullName[NM+2]; - if (FileName[0]!=0 && FileName[1]==0) - { - // Convert single character names like f:stream to .\f:stream to - // resolve the ambiguity with drive letters. - wcsncpyz(FullName,L".\\",ASIZE(FullName)); - wcsncatz(FullName,FileName,ASIZE(FullName)); - } - else - wcsncpyz(FullName,FileName,ASIZE(FullName)); + std::wstring StreamName=GetStreamNameNTFS(Arc); - wchar StreamName[NM]; - GetStreamNameNTFS(Arc,StreamName,ASIZE(StreamName)); - if (*StreamName!=':') + if (StreamName[0]!=':' || StreamName.find_first_of(L"\\/")!=std::wstring::npos) { uiMsg(UIERROR_STREAMBROKEN,Arc.FileName,FileName); ErrHandler.SetErrorCode(RARX_CRC); @@ -105,48 +139,91 @@ void ExtractStreams(Archive &Arc,const wchar *FileName,bool TestMode) if (TestMode) { File CurFile; - Arc.ReadSubData(NULL,&CurFile,true); + Arc.ReadSubData(nullptr,&CurFile,true); return; } - wcsncatz(FullName,StreamName,ASIZE(FullName)); + // Convert single character names like f:stream to .\f:stream to + // resolve the ambiguity with drive letters. + std::wstring FullName=FileName.size()==1 ? L".\\"+FileName:FileName; + FullName+=StreamName; + +#ifdef PROPAGATE_MOTW + // 2022.10.31: If we already propagated the archive Zone.Identifier stream, + // also known as Mark of the Web, to extracted file, we overwrite it here + // only if file zone is stricter. Received a user request for such behavior. + + std::string ParsedMotw; + if (Arc.Motw.IsNameConflicting(StreamName)) + { + // Do not worry about excessive memory allocation, ReadSubData prevents it. + std::vector FileMotw; + if (!Arc.ReadSubData(&FileMotw,nullptr,false)) + return; + ParsedMotw.assign(FileMotw.begin(),FileMotw.end()); + + // We already set the archive stream. If file stream value isn't more + // restricted, we do not want to write it over the existing archive stream. + if (!Arc.Motw.IsFileStreamMoreSecure(ParsedMotw)) + return; + } + + // 2024.02.03: Prevent using :Zone.Identifier:$DATA to overwrite :Zone.Identifier + // according to ZDI-CAN-23156 Trend Micro report. + // 2024.03.14: Not needed after adding check for 2+ ':' in IsNtfsProhibitedStream((). + // if (wcsnicomp(StreamName,L":Zone.Identifier:",17)==0) + // return; +#endif + + if (IsNtfsProhibitedStream(StreamName)) + return; - FindData fd; - bool Found=FindFile::FastFind(FileName,&fd); + FindData FD; + bool HostFound=FindFile::FastFind(FileName,&FD); - if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) - SetFileAttr(FileName,fd.FileAttr & ~FILE_ATTRIBUTE_READONLY); + if ((FD.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) + SetFileAttr(FileName,FD.FileAttr & ~FILE_ATTRIBUTE_READONLY); File CurFile; - if (CurFile.WCreate(FullName) && Arc.ReadSubData(NULL,&CurFile,false)) - CurFile.Close(); - File HostFile; - if (Found && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) - SetFileTime(HostFile.GetHandle(),&fd.ftCreationTime,&fd.ftLastAccessTime, - &fd.ftLastWriteTime); - // Restoring original file attributes. Important if file was read only - // or did not have "Archive" attribute - SetFileAttr(FileName,fd.FileAttr); + if (CurFile.WCreate(FullName)) + { +#ifdef PROPAGATE_MOTW + if (!ParsedMotw.empty()) + { + // The archive propagated security zone is either missing + // or less strict than file one. Write the file security zone here. + CurFile.Write(ParsedMotw.data(),ParsedMotw.size()); + CurFile.Close(); + } + else +#endif + if (Arc.ReadSubData(nullptr,&CurFile,false)) + CurFile.Close(); + } + + // Restoring original file timestamps. + File HostFile; + if (HostFound && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) + SetFileTime(HostFile.GetHandle(),&FD.ftCreationTime,&FD.ftLastAccessTime, + &FD.ftLastWriteTime); + + // Restoring original file attributes. + // Important if file was read only or did not have "Archive" attribute. + if ((FD.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) + SetFileAttr(FileName,FD.FileAttr); } #endif -void GetStreamNameNTFS(Archive &Arc,wchar *StreamName,size_t MaxSize) +std::wstring GetStreamNameNTFS(Archive &Arc) { - byte *Data=&Arc.SubHead.SubData[0]; - size_t DataSize=Arc.SubHead.SubData.Size(); + std::wstring Dest; if (Arc.Format==RARFMT15) - { - size_t DestSize=Min(DataSize/2,MaxSize-1); - RawToWide(Data,StreamName,DestSize); - StreamName[DestSize]=0; - } + Dest=RawToWide(Arc.SubHead.SubData); else { - char UtfString[NM*4]; - size_t DestSize=Min(DataSize,ASIZE(UtfString)-1); - memcpy(UtfString,Data,DestSize); - UtfString[DestSize]=0; - UtfToWide(UtfString,StreamName,MaxSize); + std::string Src(Arc.SubHead.SubData.begin(),Arc.SubHead.SubData.end()); + UtfToWide(Src.data(),Dest); } + return Dest; } diff --git a/unrar_update.md b/unrar_update.md new file mode 100644 index 00000000..51c9573b --- /dev/null +++ b/unrar_update.md @@ -0,0 +1,49 @@ +This file contains the procedure to update to a new version of unrar. + +## Obtaining new versions of unrar + +There is a separate branch, `unrar` with the unaltered source code of unrar. +Each commit is a new version of `unrar`. They are committed sequentially by +version number, as `unrar` doesn't generally have minor updates for older +versions. + +The commits have messages like "Added unrar 5.9.4". + +This branch is then merged to master, with the subtree strategy. + +To update to new versions of unrar, follow this procedure: + +1. Check the latest version of unrar from https://www.rarlab.com/rar_add.htm. + Look for the link with the name "UnRAR source". Not the url pattern of the + link, as this will be needed for step 3. +2. Setup a worktree with the unrar branch. Check the latest commit. This will + indicate what the last unrar version that was handled. +3. Attempt to download the next patch version. If you get a 404, attempt the + next minor version, otherwise the next major version. +4. Extract the contents into the `unrar` directory of the repository, + completely replacing the previous contents. +5. Commit with the correct message. +6. If you handled the latest version, you are done. Otherwise, go to step 3. + +## Updating the extension + +After the unrar branch has the latest version of unrar, it's time to merge the +unrar branch. + +1. First, use `git merge-base HEAD unrar` to determine the last unrar version + that was merged. +2. Determine how the unrar extension was modified in the `master` branch. + Compare the contents of the `unrar` subdirectory in `master` branch to the + original content in the `unrar` branch. This will inform your conflict + resolution afterwards. Note in a file what these changes are, so you can + refer to them later. +3. Upgrade to next minor (not patch, not major) version that hasn't been merged + yet. +4. Resolve any conflicts that may have arisen, preserving the functionality that + was added to the unrar library and the associated extension functionality. +5. Test. Inspect the `Justfile`. Run the tests for 7.0 and the latest supported + PHP version (debug and release-zts). +7. Continue with the next minor version on step 3, until we're synced with the + `unrar` branch. + +