├── .Rbuildignore ├── .Rprofile ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ ├── rhub.yaml │ └── test-coverage.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── Makefile ├── NAMESPACE ├── NEWS.md ├── R ├── assertions.R ├── compat-vctrs.R ├── inflate.R ├── process.R ├── utils.R ├── zip-package.R └── zip.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── air.toml ├── codecov.yml ├── inst └── example.zip ├── man ├── deflate.Rd ├── inflate.Rd ├── unzip.Rd ├── unzip_process.Rd ├── zip-package.Rd ├── zip.Rd ├── zip_list.Rd └── zip_process.Rd ├── src ├── Makevars ├── Makevars.win ├── init.c ├── install.libs.R ├── miniz.c ├── miniz.h ├── rzip.c ├── tools │ ├── cmdunzip.c │ └── cmdzip.c ├── unixutils.c ├── winutils.c ├── zip.c └── zip.h ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── ascii │ │ └── inflate.md │ ├── errors.md │ ├── inflate.md │ ├── unzip.md │ ├── utf8 │ │ └── inflate.md │ ├── weird-paths.md │ └── zip-list.md │ ├── fixtures │ ├── abs.zip │ ├── msdos.zip │ └── symlink.zip │ ├── helper.R │ ├── test-errors.R │ ├── test-get-zip-data-path.R │ ├── test-get-zip-data.R │ ├── test-inflate.R │ ├── test-large-files.R │ ├── test-paths.R │ ├── test-special-dot.R │ ├── test-unzip-process.R │ ├── test-unzip.R │ ├── test-weird-paths.R │ ├── test-zip-list.R │ ├── test-zip-process.R │ ├── test-zip.R │ └── test-zipr.R ├── tools └── getzipexe.R └── zip.Rproj /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^Makefile$ 4 | ^README.Rmd$ 5 | ^.travis.yml$ 6 | ^appveyor.yml$ 7 | ^revdep$ 8 | ^[^/]*[.]zip$ 9 | ^README\.html$ 10 | ^src/.*\.o$ 11 | ^src/zip.dll$ 12 | ^src/tools/cmdunzip$ 13 | ^src/tools/cmdunzip\.exe$ 14 | ^src/tools/cmdunzip\.dSYM$ 15 | ^src/tools/cmdzip$ 16 | ^src/tools/cmdzip\.exe$ 17 | ^src/tools/cmdzip\.dSYM$ 18 | ^src/tools/zip.exe$ 19 | ^\.Rprofile$ 20 | ^r-packages$ 21 | ^LICENSE\.md$ 22 | ^\.github$ 23 | ^codecov\.yml$ 24 | ^_pkgdown\.yml$ 25 | ^docs$ 26 | ^pkgdown$ 27 | ^[\.]?air\.toml$ 28 | ^\.vscode$ 29 | -------------------------------------------------------------------------------- /.Rprofile: -------------------------------------------------------------------------------- 1 | # This file is created automatically by `pak:::proj_create()` 2 | # Please do not edit this file 3 | .libPaths(unique(c(file.path('r-packages', R.version$platform, getRversion()[,1:2]),.libPaths()))) 4 | if (file.exists('~/.Rprofile')) source('~/.Rprofile') 5 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at codeofconduct@posit.co. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | . 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | . Translations are available at . 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | 12 | name: R-CMD-check.yaml 13 | 14 | permissions: read-all 15 | 16 | jobs: 17 | R-CMD-check: 18 | runs-on: ${{ matrix.config.os }} 19 | 20 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | config: 26 | - {os: macos-latest, r: 'release'} 27 | 28 | - {os: windows-latest, r: 'release'} 29 | # use 4.0 or 4.1 to check with rtools40's older compiler 30 | - {os: windows-latest, r: 'oldrel-4'} 31 | 32 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 33 | - {os: ubuntu-latest, r: 'release'} 34 | - {os: ubuntu-latest, r: 'oldrel-1'} 35 | - {os: ubuntu-latest, r: 'oldrel-2'} 36 | - {os: ubuntu-latest, r: 'oldrel-3'} 37 | - {os: ubuntu-latest, r: 'oldrel-4'} 38 | 39 | env: 40 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 41 | R_KEEP_PKG_SOURCE: yes 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - uses: r-lib/actions/setup-pandoc@v2 47 | 48 | - uses: r-lib/actions/setup-r@v2 49 | with: 50 | r-version: ${{ matrix.config.r }} 51 | http-user-agent: ${{ matrix.config.http-user-agent }} 52 | use-public-rspm: true 53 | 54 | - uses: r-lib/actions/setup-r-dependencies@v2 55 | with: 56 | extra-packages: any::rcmdcheck 57 | needs: check 58 | 59 | - uses: r-lib/actions/check-r-package@v2 60 | with: 61 | upload-snapshots: true 62 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 63 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: pr-commands.yaml 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | permissions: 19 | contents: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: r-lib/actions/pr-fetch@v2 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::roxygen2 34 | needs: pr-document 35 | 36 | - name: Document 37 | run: roxygen2::roxygenise() 38 | shell: Rscript {0} 39 | 40 | - name: commit 41 | run: | 42 | git config --local user.name "$GITHUB_ACTOR" 43 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 44 | git add man/\* NAMESPACE 45 | git commit -m 'Document' 46 | 47 | - uses: r-lib/actions/pr-push@v2 48 | with: 49 | repo-token: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | style: 52 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 53 | name: style 54 | runs-on: ubuntu-latest 55 | env: 56 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 57 | permissions: 58 | contents: write 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: r-lib/actions/pr-fetch@v2 63 | with: 64 | repo-token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | - uses: r-lib/actions/setup-r@v2 67 | 68 | - name: Install dependencies 69 | run: install.packages("styler") 70 | shell: Rscript {0} 71 | 72 | - name: Style 73 | run: styler::style_pkg() 74 | shell: Rscript {0} 75 | 76 | - name: commit 77 | run: | 78 | git config --local user.name "$GITHUB_ACTOR" 79 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 80 | git add \*.R 81 | git commit -m 'Style' 82 | 83 | - uses: r-lib/actions/pr-push@v2 84 | with: 85 | repo-token: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /.github/workflows/rhub.yaml: -------------------------------------------------------------------------------- 1 | # R-hub's generic GitHub Actions workflow file. It's canonical location is at 2 | # https://github.com/r-hub/actions/blob/v1/workflows/rhub.yaml 3 | # You can update this file to a newer version using the rhub2 package: 4 | # 5 | # rhub::rhub_setup() 6 | # 7 | # It is unlikely that you need to modify this file manually. 8 | 9 | name: R-hub 10 | run-name: "${{ github.event.inputs.id }}: ${{ github.event.inputs.name || format('Manually run by {0}', github.triggering_actor) }}" 11 | 12 | on: 13 | workflow_dispatch: 14 | inputs: 15 | config: 16 | description: 'A comma separated list of R-hub platforms to use.' 17 | type: string 18 | default: 'linux,windows,macos' 19 | name: 20 | description: 'Run name. You can leave this empty now.' 21 | type: string 22 | id: 23 | description: 'Unique ID. You can leave this empty now.' 24 | type: string 25 | 26 | jobs: 27 | 28 | setup: 29 | runs-on: ubuntu-latest 30 | outputs: 31 | containers: ${{ steps.rhub-setup.outputs.containers }} 32 | platforms: ${{ steps.rhub-setup.outputs.platforms }} 33 | 34 | steps: 35 | # NO NEED TO CHECKOUT HERE 36 | - uses: r-hub/actions/setup@feature/m1-san 37 | with: 38 | config: ${{ github.event.inputs.config }} 39 | id: rhub-setup 40 | 41 | linux-containers: 42 | needs: setup 43 | if: ${{ needs.setup.outputs.containers != '[]' }} 44 | runs-on: ubuntu-latest 45 | name: ${{ matrix.config.label }} 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | config: ${{ fromJson(needs.setup.outputs.containers) }} 50 | container: 51 | image: ${{ matrix.config.container }} 52 | 53 | steps: 54 | - uses: r-hub/actions/checkout@feature/m1-san 55 | - uses: r-hub/actions/platform-info@feature/m1-san 56 | with: 57 | token: ${{ secrets.RHUB_TOKEN }} 58 | job-config: ${{ matrix.config.job-config }} 59 | - uses: r-hub/actions/setup-deps@feature/m1-san 60 | with: 61 | token: ${{ secrets.RHUB_TOKEN }} 62 | job-config: ${{ matrix.config.job-config }} 63 | - uses: r-hub/actions/run-check@feature/m1-san 64 | with: 65 | token: ${{ secrets.RHUB_TOKEN }} 66 | job-config: ${{ matrix.config.job-config }} 67 | 68 | other-platforms: 69 | needs: setup 70 | if: ${{ needs.setup.outputs.platforms != '[]' }} 71 | runs-on: ${{ matrix.config.os }} 72 | name: ${{ matrix.config.label }} 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | config: ${{ fromJson(needs.setup.outputs.platforms) }} 77 | 78 | steps: 79 | - uses: r-hub/actions/checkout@feature/m1-san 80 | - uses: r-hub/actions/setup-r@feature/m1-san 81 | with: 82 | job-config: ${{ matrix.config.job-config }} 83 | token: ${{ secrets.RHUB_TOKEN }} 84 | - uses: r-hub/actions/platform-info@feature/m1-san 85 | with: 86 | token: ${{ secrets.RHUB_TOKEN }} 87 | job-config: ${{ matrix.config.job-config }} 88 | - uses: r-hub/actions/setup-deps@feature/m1-san 89 | with: 90 | job-config: ${{ matrix.config.job-config }} 91 | token: ${{ secrets.RHUB_TOKEN }} 92 | - uses: r-hub/actions/run-check@feature/m1-san 93 | with: 94 | job-config: ${{ matrix.config.job-config }} 95 | token: ${{ secrets.RHUB_TOKEN }} 96 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | print(cov) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v5 42 | with: 43 | # Fail if error if not on PR, or if on PR and token is given 44 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 45 | files: ./cobertura.xml 46 | plugins: noop 47 | disable_search: true 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | 50 | - name: Show testthat output 51 | if: always() 52 | run: | 53 | ## -------------------------------------------------------------------- 54 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 55 | shell: bash 56 | 57 | - name: Upload test results 58 | if: failure() 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: coverage-test-failures 62 | path: ${{ runner.temp }}/package 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | /src/*.o 5 | /src/zip.so 6 | /revdep 7 | /README.html 8 | /sources.zip 9 | /src/tools/cmdunzip 10 | /src/tools/cmdunzip.exe 11 | /src/tools/cmdunzip.dSYM 12 | /src/tools/cmdzip 13 | /src/tools/cmdzip.exe 14 | /src/tools/cmdzip.dSYM 15 | /r-packages 16 | /src/tools/zip.exe 17 | /tools/zip.exe 18 | /src/zip.dll 19 | /src-* 20 | docs 21 | /*.zip 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Posit.air-vscode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[r]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "Posit.air-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: zip 2 | Title: Cross-Platform 'zip' Compression 3 | Version: 2.3.3.9000 4 | Authors@R: c( 5 | person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre")), 6 | person("Kuba", "Podgórski", role = "ctb"), 7 | person("Rich", "Geldreich", role = "ctb"), 8 | person("Posit Software, PBC", role = c("cph", "fnd"), 9 | comment = c(ROR = "03wc8by49")) 10 | ) 11 | Description: Cross-Platform 'zip' Compression Library. A replacement for 12 | the 'zip' function, that does not require any additional external 13 | tools on any platform. 14 | License: MIT + file LICENSE 15 | URL: https://github.com/r-lib/zip, https://r-lib.github.io/zip/ 16 | BugReports: https://github.com/r-lib/zip/issues 17 | Suggests: 18 | covr, 19 | pillar, 20 | processx, 21 | R6, 22 | testthat, 23 | withr 24 | Config/Needs/website: tidyverse/tidytemplate 25 | Config/testthat/edition: 3 26 | Config/usethis/last-upkeep: 2025-05-07 27 | Encoding: UTF-8 28 | Roxygen: list(markdown = TRUE) 29 | RoxygenNote: 7.3.2.9000 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: zip authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 zip authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: README.md 3 | 4 | README.md: README.Rmd 5 | R -q -e 'rmarkdown::render("README.Rmd")' 6 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(deflate) 4 | export(inflate) 5 | export(unzip) 6 | export(unzip_process) 7 | export(zip) 8 | export(zip_append) 9 | export(zip_list) 10 | export(zip_process) 11 | export(zipr) 12 | export(zipr_append) 13 | useDynLib(zip, .registration = TRUE, .fixes = "c_") 14 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # zip (development version) 2 | 3 | # zip 2.3.3 4 | 5 | * `zip_list()` now has a `type` column, for the file type. 6 | 7 | * `unzip()` now correctly creates symbolic links on Unix (#127). 8 | 9 | # zip 2.3.2 10 | 11 | * `zip_list()` now returns a `tbl` object, and loads the pillar package, 12 | if installed, to produce the nicer output for long data frames. 13 | 14 | # zip 2.3.1 15 | 16 | * The zip shared library now hides its symbols (on platforms that support 17 | this), to avoid name clashes with other libraries (#98). 18 | 19 | # zip 2.3.0 20 | 21 | * zip now handles large zip files on Windows (#65, #75, #79, @weshinsley). 22 | 23 | * zip now behaves better for absolute paths in mirror mode, and when the 24 | paths contain a `:` character (#69, #70). 25 | 26 | * `zip::unzip()` now uses the process's umask value (see `umask(2)`) on Unix 27 | if the zip file does not contain Unix permissions (#67). 28 | 29 | * Fix segmentation fault when zip file can't be created (#91, @zeehio) 30 | 31 | * Fix delayed evaluation error on zipfile when `zip::zip()` 32 | is used (#92, @zeehio) 33 | 34 | * New `deflate()` and `inflate()` functions to compress and uncompress 35 | GZIP streams in memory. 36 | 37 | # zip 2.2.2 38 | 39 | * No user visible changes. 40 | 41 | # zip 2.2.1 42 | 43 | * No user visible changes. 44 | 45 | # 2.2.0 46 | 47 | * Header values (of version made by and external attributes) are now 48 | correctly read and written on big-endian systems (#68). 49 | 50 | * `zip_list()` now also returns `crc32` and `offset` (#74, @jefferis). 51 | 52 | # 2.1.1 53 | 54 | This version has no user visible changes. 55 | 56 | # 2.1.0 57 | 58 | * `unzip_process()` now does not fail randomly on Windows (#60). 59 | 60 | * Now all functions handle Unicode paths correctly, on Windows 61 | as well (#42, #53). 62 | 63 | * `unzip_process()` now works when R library is on different drive 64 | than `exdir` on Windows (#45) 65 | 66 | * zip functions now have a `mode` argument to choose how files and 67 | directories are assembled into the archive. See the docs for 68 | details. 69 | 70 | * zip functions now have a `root` argument, zip changes the working 71 | directory to this before creating the archive, so all files are 72 | relative to `root`. 73 | 74 | * `zip()` and `zip_append()` are not deprecated any more, as it was 75 | hard to achieve the same functionality with the other zip functions. 76 | 77 | # 2.0.4 78 | 79 | * `unzip_process()` prints better error messages to the standard error, 80 | and exits with a non-zero status, on error. 81 | 82 | # 2.0.3 83 | 84 | * `zipr()` and `zipr_append()` get an `include_directories = TRUE` 85 | argument, that can be used to omit directory entries from the zip 86 | archive. These entries may cause problems in MS Office docx files (#34). 87 | 88 | # 2.0.2 89 | 90 | * `zip_process()` and `unzip_process()` can now pass extra arguments to 91 | `processx::process` (#32). 92 | 93 | * `unzip_process()` now makes sure the `exdir` path is created with 94 | forward slashes on Windows, mixing forward and backward slashes can 95 | cause errors. 96 | 97 | # 2.0.1 98 | 99 | * `zip()` and `zip_append()` are now soft-deprecated, please use 100 | `zipr()` and `zipr_append()` instead. 101 | 102 | # 2.0.0 103 | 104 | * New `zipr()` and `zipr_append()`, they always store relative file names 105 | in the archive. 106 | 107 | * New `unzip()` function for uncompressing zip archives. 108 | 109 | * New `zip_process()` and `unzip_process()` functions to create or 110 | uncompress an archive in a background process. 111 | 112 | * `zip()`, `zipr()`, `zip_append()` and `zipr_append()` all include 113 | directories in the archives, empty ones as well. 114 | 115 | * `zip()`, `zipr()`, `zip_append()` and `zipr_append()` all add time stamps 116 | to the archive and `zip_list()` returns then in the `timestamp` column. 117 | 118 | * `zip()`, `zipr()`, `zip_append()` and `zipr_append()` all add file 119 | and directory permissions to the archive on Unix systems, and 120 | `zip_list()` returns them in the `permissions` column. 121 | 122 | * `zip_list()` now correctly reports the size of large files in the archive. 123 | 124 | * Use miniz 2.0.8 internally. 125 | 126 | # 1.0.0 127 | 128 | First public release. 129 | -------------------------------------------------------------------------------- /R/assertions.R: -------------------------------------------------------------------------------- 1 | is_string <- function(x) { 2 | is.character(x) && length(x) == 1 && !is.na(x) 3 | } 4 | 5 | is_character <- function(x) { 6 | is.character(x) && !anyNA(x) 7 | } 8 | 9 | is_character_or_null <- function(x) { 10 | is.null(x) || is_character(x) 11 | } 12 | 13 | is_flag <- function(x) { 14 | is.logical(x) && length(x) == 1 && !is.na(x) 15 | } 16 | 17 | is_count <- function(x) { 18 | is.numeric(x) && length(x) == 1 && !is.na(x) && as.integer(x) == x 19 | } 20 | -------------------------------------------------------------------------------- /R/compat-vctrs.R: -------------------------------------------------------------------------------- 1 | compat_vctrs <- local({ 2 | # Modified from https://github.com/r-lib/rlang/blob/master/R/compat-vctrs.R 3 | 4 | # Construction ------------------------------------------------------------ 5 | 6 | # Constructs data frames inheriting from `"tbl"`. This allows the 7 | # pillar package to take over printing as soon as it is loaded. 8 | # The data frame otherwise behaves like a base data frame. 9 | data_frame <- function(...) { 10 | new_data_frame(df_list(...), .class = "tbl") 11 | } 12 | 13 | new_data_frame <- function(.x = list(), ..., .size = NULL, .class = NULL) { 14 | n_cols <- length(.x) 15 | if (n_cols != 0 && is.null(names(.x))) { 16 | stop("Columns must be named.", call. = FALSE) 17 | } 18 | 19 | if (is.null(.size)) { 20 | if (n_cols == 0) { 21 | .size <- 0 22 | } else { 23 | .size <- vec_size(.x[[1]]) 24 | } 25 | } 26 | 27 | structure( 28 | .x, 29 | class = c(.class, "data.frame"), 30 | row.names = .set_row_names(.size), 31 | ... 32 | ) 33 | } 34 | 35 | df_list <- function(..., .size = NULL) { 36 | vec_recycle_common(list(...), size = .size) 37 | } 38 | 39 | # Binding ----------------------------------------------------------------- 40 | 41 | vec_rbind <- function(...) { 42 | xs <- vec_cast_common(list(...)) 43 | do.call(base::rbind, xs) 44 | } 45 | 46 | vec_cbind <- function(...) { 47 | xs <- list(...) 48 | 49 | ptype <- vec_ptype_common(lapply(xs, `[`, 0)) 50 | class <- setdiff(class(ptype), "data.frame") 51 | 52 | xs <- vec_recycle_common(xs) 53 | out <- do.call(base::cbind, xs) 54 | new_data_frame(out, .class = class) 55 | } 56 | 57 | # Slicing ----------------------------------------------------------------- 58 | 59 | vec_size <- function(x) { 60 | if (is.data.frame(x)) { 61 | nrow(x) 62 | } else { 63 | length(x) 64 | } 65 | } 66 | 67 | vec_rep <- function(x, times) { 68 | i <- rep.int(seq_len(vec_size(x)), times) 69 | vec_slice(x, i) 70 | } 71 | 72 | vec_recycle_common <- function(xs, size = NULL) { 73 | sizes <- vapply(xs, vec_size, integer(1)) 74 | 75 | n <- unique(sizes) 76 | 77 | if (length(n) == 1 && is.null(size)) { 78 | return(xs) 79 | } 80 | n <- setdiff(n, 1L) 81 | 82 | ns <- length(n) 83 | 84 | if (ns == 0) { 85 | if (is.null(size)) { 86 | return(xs) 87 | } 88 | } else if (ns == 1) { 89 | if (is.null(size)) { 90 | size <- n 91 | } else if (ns != size) { 92 | stop("Inputs can't be recycled to `size`.", call. = FALSE) 93 | } 94 | } else { 95 | stop("Inputs can't be recycled to a common size.", call. = FALSE) 96 | } 97 | 98 | to_recycle <- sizes == 1L 99 | xs[to_recycle] <- lapply(xs[to_recycle], vec_rep, size) 100 | 101 | xs 102 | } 103 | 104 | vec_slice <- function(x, i) { 105 | if (is.logical(i)) { 106 | i <- which(i) 107 | } 108 | stopifnot(is.numeric(i) || is.character(i)) 109 | 110 | if (is.null(x)) { 111 | return(NULL) 112 | } 113 | 114 | if (is.data.frame(x)) { 115 | # We need to be a bit careful to be generic. First empty all 116 | # columns and expand the df to final size. 117 | out <- x[i, 0, drop = FALSE] 118 | 119 | # Then fill in with sliced columns 120 | out[seq_along(x)] <- lapply(x, vec_slice, i) 121 | 122 | # Reset automatic row names to work around `[` weirdness 123 | if (is.numeric(attr(x, "row.names"))) { 124 | row_names <- .set_row_names(nrow(out)) 125 | } else { 126 | row_names <- attr(out, "row.names") 127 | } 128 | 129 | return(out) 130 | } 131 | 132 | d <- vec_dims(x) 133 | if (d == 1) { 134 | if (is.object(x)) { 135 | out <- x[i] 136 | } else { 137 | out <- x[i, drop = FALSE] 138 | } 139 | } else if (d == 2) { 140 | out <- x[i, , drop = FALSE] 141 | } else { 142 | j <- rep(list(quote(expr = )), d - 1) 143 | out <- eval(as.call(list( 144 | quote(`[`), 145 | quote(x), 146 | quote(i), 147 | j, 148 | drop = FALSE 149 | ))) 150 | } 151 | 152 | out 153 | } 154 | vec_dims <- function(x) { 155 | d <- dim(x) 156 | if (is.null(d)) { 157 | 1L 158 | } else { 159 | length(d) 160 | } 161 | } 162 | 163 | vec_as_location <- function(i, n, names = NULL) { 164 | out <- seq_len(n) 165 | names(out) <- names 166 | 167 | # Special-case recycling to size 0 168 | if (is_logical(i, n = 1) && !length(out)) { 169 | return(out) 170 | } 171 | 172 | unname(out[i]) 173 | } 174 | 175 | vec_init <- function(x, n = 1L) { 176 | vec_slice(x, rep_len(NA_integer_, n)) 177 | } 178 | 179 | vec_assign <- function(x, i, value) { 180 | if (is.null(x)) { 181 | return(NULL) 182 | } 183 | 184 | if (is.logical(i)) { 185 | i <- which(i) 186 | } 187 | stopifnot( 188 | is.numeric(i) || is.character(i) 189 | ) 190 | 191 | value <- vec_recycle(value, vec_size(i)) 192 | value <- vec_cast(value, to = x) 193 | 194 | d <- vec_dims(x) 195 | 196 | if (d == 1) { 197 | x[i] <- value 198 | } else if (d == 2) { 199 | x[i, ] <- value 200 | } else { 201 | stop("Can't slice-assign arrays.", call. = FALSE) 202 | } 203 | 204 | x 205 | } 206 | 207 | vec_recycle <- function(x, size) { 208 | if (is.null(x) || is.null(size)) { 209 | return(NULL) 210 | } 211 | 212 | n_x <- vec_size(x) 213 | 214 | if (n_x == size) { 215 | x 216 | } else if (size == 0L) { 217 | vec_slice(x, 0L) 218 | } else if (n_x == 1L) { 219 | vec_slice(x, rep(1L, size)) 220 | } else { 221 | stop("Incompatible lengths: ", n_x, ", ", size, call. = FALSE) 222 | } 223 | } 224 | 225 | # Coercion ---------------------------------------------------------------- 226 | 227 | vec_cast_common <- function(xs, to = NULL) { 228 | ptype <- vec_ptype_common(xs, ptype = to) 229 | lapply(xs, vec_cast, to = ptype) 230 | } 231 | 232 | vec_cast <- function(x, to) { 233 | if (is.null(x)) { 234 | return(NULL) 235 | } 236 | if (is.null(to)) { 237 | return(x) 238 | } 239 | 240 | if (vec_is_unspecified(x)) { 241 | return(vec_init(to, vec_size(x))) 242 | } 243 | 244 | stop_incompatible_cast <- function(x, to) { 245 | stop( 246 | sprintf( 247 | "Can't convert <%s> to <%s>.", 248 | .rlang_vctrs_typeof(x), 249 | .rlang_vctrs_typeof(to) 250 | ), 251 | call. = FALSE 252 | ) 253 | } 254 | 255 | lgl_cast <- function(x, to) { 256 | lgl_cast_from_num <- function(x) { 257 | if (any(!x %in% c(0L, 1L))) { 258 | stop_incompatible_cast(x, to) 259 | } 260 | as.logical(x) 261 | } 262 | 263 | switch( 264 | .rlang_vctrs_typeof(x), 265 | logical = x, 266 | integer = , 267 | double = lgl_cast_from_num(x), 268 | stop_incompatible_cast(x, to) 269 | ) 270 | } 271 | 272 | int_cast <- function(x, to) { 273 | int_cast_from_dbl <- function(x) { 274 | out <- suppressWarnings(as.integer(x)) 275 | if (any((out != x) | xor(is.na(x), is.na(out)))) { 276 | stop_incompatible_cast(x, to) 277 | } else { 278 | out 279 | } 280 | } 281 | 282 | switch( 283 | .rlang_vctrs_typeof(x), 284 | logical = as.integer(x), 285 | integer = x, 286 | double = int_cast_from_dbl(x), 287 | stop_incompatible_cast(x, to) 288 | ) 289 | } 290 | 291 | dbl_cast <- function(x, to) { 292 | switch( 293 | .rlang_vctrs_typeof(x), 294 | logical = , 295 | integer = as.double(x), 296 | double = x, 297 | stop_incompatible_cast(x, to) 298 | ) 299 | } 300 | 301 | chr_cast <- function(x, to) { 302 | switch( 303 | .rlang_vctrs_typeof(x), 304 | character = x, 305 | stop_incompatible_cast(x, to) 306 | ) 307 | } 308 | 309 | list_cast <- function(x, to) { 310 | switch( 311 | .rlang_vctrs_typeof(x), 312 | list = x, 313 | stop_incompatible_cast(x, to) 314 | ) 315 | } 316 | 317 | df_cast <- function(x, to) { 318 | # Check for extra columns 319 | if (length(setdiff(names(x), names(to))) > 0) { 320 | stop( 321 | "Can't convert data frame because of missing columns.", 322 | call. = FALSE 323 | ) 324 | } 325 | 326 | # Avoid expensive [.data.frame method 327 | out <- as.list(x) 328 | 329 | # Coerce common columns 330 | common <- intersect(names(x), names(to)) 331 | out[common] <- Map(vec_cast, out[common], to[common]) 332 | 333 | # Add new columns 334 | from_type <- setdiff(names(to), names(x)) 335 | out[from_type] <- lapply(to[from_type], vec_init, n = vec_size(x)) 336 | 337 | # Ensure columns are ordered according to `to` 338 | out <- out[names(to)] 339 | 340 | new_data_frame(out) 341 | } 342 | 343 | rlib_df_cast <- function(x, to) { 344 | new_data_frame(df_cast(x, to), .class = "tbl") 345 | } 346 | tib_cast <- function(x, to) { 347 | new_data_frame(df_cast(x, to), .class = c("tbl_df", "tbl")) 348 | } 349 | 350 | switch( 351 | .rlang_vctrs_typeof(to), 352 | logical = lgl_cast(x, to), 353 | integer = int_cast(x, to), 354 | double = dbl_cast(x, to), 355 | character = chr_cast(x, to), 356 | list = list_cast(x, to), 357 | 358 | base_data_frame = df_cast(x, to), 359 | rlib_data_frame = rlib_df_cast(x, to), 360 | tibble = tib_cast(x, to), 361 | 362 | stop_incompatible_cast(x, to) 363 | ) 364 | } 365 | 366 | vec_ptype_common <- function(xs, ptype = NULL) { 367 | if (!is.null(ptype)) { 368 | return(vec_ptype(ptype)) 369 | } 370 | 371 | xs <- Filter(function(x) !is.null(x), xs) 372 | 373 | if (length(xs) == 0) { 374 | return(NULL) 375 | } 376 | 377 | if (length(xs) == 1) { 378 | out <- vec_ptype(xs[[1]]) 379 | } else { 380 | xs <- map(xs, vec_ptype) 381 | out <- Reduce(vec_ptype2, xs) 382 | } 383 | 384 | vec_ptype_finalise(out) 385 | } 386 | 387 | vec_ptype_finalise <- function(x) { 388 | if (is.data.frame(x)) { 389 | x[] <- lapply(x, vec_ptype_finalise) 390 | return(x) 391 | } 392 | 393 | if (inherits(x, "rlang_unspecified")) { 394 | logical() 395 | } else { 396 | x 397 | } 398 | } 399 | 400 | vec_ptype <- function(x) { 401 | if (vec_is_unspecified(x)) { 402 | return(.rlang_vctrs_unspecified()) 403 | } 404 | 405 | if (is.data.frame(x)) { 406 | out <- new_data_frame(lapply(x, vec_ptype)) 407 | 408 | attrib <- attributes(x) 409 | attrib$row.names <- attr(out, "row.names") 410 | attributes(out) <- attrib 411 | 412 | return(out) 413 | } 414 | 415 | vec_slice(x, 0) 416 | } 417 | 418 | vec_ptype2 <- function(x, y) { 419 | stop_incompatible_type <- function(x, y) { 420 | stop( 421 | sprintf( 422 | "Can't combine types <%s> and <%s>.", 423 | .rlang_vctrs_typeof(x), 424 | .rlang_vctrs_typeof(y) 425 | ), 426 | call. = FALSE 427 | ) 428 | } 429 | 430 | x_type <- .rlang_vctrs_typeof(x) 431 | y_type <- .rlang_vctrs_typeof(y) 432 | 433 | if (x_type == "unspecified" && y_type == "unspecified") { 434 | return(.rlang_vctrs_unspecified()) 435 | } 436 | if (x_type == "unspecified") { 437 | return(y) 438 | } 439 | if (y_type == "unspecified") { 440 | return(x) 441 | } 442 | 443 | df_ptype2 <- function(x, y) { 444 | set_partition <- function(x, y) { 445 | list( 446 | both = intersect(x, y), 447 | only_x = setdiff(x, y), 448 | only_y = setdiff(y, x) 449 | ) 450 | } 451 | 452 | # Avoid expensive [.data.frame 453 | x <- as.list(vec_slice(x, 0)) 454 | y <- as.list(vec_slice(y, 0)) 455 | 456 | # Find column types 457 | names <- set_partition(names(x), names(y)) 458 | if (length(names$both) > 0) { 459 | common_types <- Map(vec_ptype2, x[names$both], y[names$both]) 460 | } else { 461 | common_types <- list() 462 | } 463 | only_x_types <- x[names$only_x] 464 | only_y_types <- y[names$only_y] 465 | 466 | # Combine and construct 467 | out <- c(common_types, only_x_types, only_y_types) 468 | out <- out[c(names(x), names$only_y)] 469 | new_data_frame(out) 470 | } 471 | 472 | rlib_df_ptype2 <- function(x, y) { 473 | new_data_frame(df_ptype2(x, y), .class = "tbl") 474 | } 475 | tib_ptype2 <- function(x, y) { 476 | new_data_frame(df_ptype2(x, y), .class = c("tbl_df", "tbl")) 477 | } 478 | 479 | ptype <- switch( 480 | x_type, 481 | 482 | logical = switch( 483 | y_type, 484 | logical = x, 485 | integer = y, 486 | double = y, 487 | stop_incompatible_type(x, y) 488 | ), 489 | 490 | integer = switch( 491 | .rlang_vctrs_typeof(y), 492 | logical = x, 493 | integer = x, 494 | double = y, 495 | stop_incompatible_type(x, y) 496 | ), 497 | 498 | double = switch( 499 | .rlang_vctrs_typeof(y), 500 | logical = x, 501 | integer = x, 502 | double = x, 503 | stop_incompatible_type(x, y) 504 | ), 505 | 506 | character = switch( 507 | .rlang_vctrs_typeof(y), 508 | character = x, 509 | stop_incompatible_type(x, y) 510 | ), 511 | 512 | list = switch( 513 | .rlang_vctrs_typeof(y), 514 | list = x, 515 | stop_incompatible_type(x, y) 516 | ), 517 | 518 | base_data_frame = switch( 519 | .rlang_vctrs_typeof(y), 520 | base_data_frame = , 521 | s3_data_frame = df_ptype2(x, y), 522 | rlib_data_frame = rlib_df_ptype2(x, y), 523 | tibble = tib_ptype2(x, y), 524 | stop_incompatible_type(x, y) 525 | ), 526 | 527 | rlib_data_frame = switch( 528 | .rlang_vctrs_typeof(y), 529 | base_data_frame = , 530 | rlib_data_frame = , 531 | s3_data_frame = rlib_df_ptype2(x, y), 532 | tibble = tib_ptype2(x, y), 533 | stop_incompatible_type(x, y) 534 | ), 535 | 536 | tibble = switch( 537 | .rlang_vctrs_typeof(y), 538 | base_data_frame = , 539 | rlib_data_frame = , 540 | tibble = , 541 | s3_data_frame = tib_ptype2(x, y), 542 | stop_incompatible_type(x, y) 543 | ), 544 | 545 | stop_incompatible_type(x, y) 546 | ) 547 | 548 | vec_slice(ptype, 0) 549 | } 550 | 551 | .rlang_vctrs_typeof <- function(x) { 552 | if (is.object(x)) { 553 | class <- class(x) 554 | 555 | if (identical(class, "rlang_unspecified")) { 556 | return("unspecified") 557 | } 558 | if (identical(class, "data.frame")) { 559 | return("base_data_frame") 560 | } 561 | if (identical(class, c("tbl", "data.frame"))) { 562 | return("rlib_data_frame") 563 | } 564 | if (identical(class, c("tbl_df", "tbl", "data.frame"))) { 565 | return("tibble") 566 | } 567 | if (inherits(x, "data.frame")) { 568 | return("s3_data_frame") 569 | } 570 | 571 | class <- paste0(class, collapse = "/") 572 | stop(sprintf("Unimplemented class <%s>.", class), call. = FALSE) 573 | } 574 | 575 | type <- typeof(x) 576 | switch( 577 | type, 578 | NULL = return("null"), 579 | logical = if (vec_is_unspecified(x)) { 580 | return("unspecified") 581 | } else { 582 | return(type) 583 | }, 584 | integer = , 585 | double = , 586 | character = , 587 | raw = , 588 | list = return(type) 589 | ) 590 | 591 | stop(sprintf("Unimplemented type <%s>.", type), call. = FALSE) 592 | } 593 | 594 | vec_is_unspecified <- function(x) { 595 | !is.object(x) && 596 | typeof(x) == "logical" && 597 | length(x) && 598 | all(vapply(x, identical, logical(1), NA)) 599 | } 600 | 601 | .rlang_vctrs_unspecified <- function(x = NULL) { 602 | structure( 603 | rep(NA, length(x)), 604 | class = "rlang_unspecified" 605 | ) 606 | } 607 | 608 | .rlang_vctrs_s3_method <- function(generic, class, env = parent.frame()) { 609 | fn <- get(generic, envir = env) 610 | 611 | ns <- asNamespace(topenv(fn)) 612 | tbl <- ns$.__S3MethodsTable__. 613 | 614 | for (c in class) { 615 | name <- paste0(generic, ".", c) 616 | if (exists(name, envir = tbl, inherits = FALSE)) { 617 | return(get(name, envir = tbl)) 618 | } 619 | if (exists(name, envir = globalenv(), inherits = FALSE)) { 620 | return(get(name, envir = globalenv())) 621 | } 622 | } 623 | 624 | NULL 625 | } 626 | 627 | environment() 628 | }) 629 | 630 | data_frame <- compat_vctrs$data_frame 631 | 632 | as_data_frame <- function(x) { 633 | if (is.matrix(x)) { 634 | x <- as.data.frame(x, stringsAsFactors = FALSE) 635 | } else { 636 | x <- compat_vctrs$vec_recycle_common(x) 637 | } 638 | compat_vctrs$new_data_frame(x, .class = "tbl") 639 | } 640 | 641 | # nocov end 642 | -------------------------------------------------------------------------------- /R/inflate.R: -------------------------------------------------------------------------------- 1 | #' Uncompress a raw GZIP stream 2 | #' 3 | #' @param buffer Raw vector, containing the data to uncompress. 4 | #' @param pos Start position of data to uncompress in `buffer`. 5 | #' @param size Uncompressed size estimate, or `NULL`. If not given, or too 6 | #' small, the output buffer is resized multiple times. 7 | #' @return Named list with three entries: 8 | #' - `output`: raw vector, the uncompressed data, 9 | #' - `bytes_read`: number of bytes used from `buffer`, 10 | #' - `bytes_written`: number of bytes written to the output buffer. 11 | #' 12 | #' @seealso [base::memDecompress()] does the same with `type = "gzip"`, 13 | #' but it does not tell you the number of bytes read from the input. 14 | #' 15 | #' @export 16 | #' @examples 17 | #' data_gz <- deflate(charToRaw("Hello world!")) 18 | #' inflate(data_gz$output) 19 | 20 | inflate <- function(buffer, pos = 1L, size = NULL) { 21 | stopifnot( 22 | is.raw(buffer), 23 | is_count(pos), 24 | is.null(size) || is_count(size) 25 | ) 26 | if (!is.null(size)) size <- as.integer(size) 27 | .Call(c_R_inflate, buffer, as.integer(pos), size) 28 | } 29 | 30 | #' Compress a raw GZIP stream 31 | #' 32 | #' @param buffer Raw vector, containing the data to compress. 33 | #' @param level Compression level, integer between 1 (fatest) and 9 (best). 34 | #' @param pos Start position of data to compress in `buffer`. 35 | #' @param size Compressed size estimate, or `NULL`. If not given, or too 36 | #' small, the output buffer is resized multiple times. 37 | #' @return Named list with three entries: 38 | #' - `output`: raw vector, the compressed data, 39 | #' - `bytes_read`: number of bytes used from `buffer`, 40 | #' - `bytes_written`: number of bytes written to the output buffer. 41 | #' 42 | #' @seealso [base::memCompress()] does the same with `type = "gzip"`, 43 | #' but it does not tell you the number of bytes read from the input. 44 | #' 45 | #' @export 46 | #' @examples 47 | #' data_gz <- deflate(charToRaw("Hello world!")) 48 | #' inflate(data_gz$output) 49 | 50 | deflate <- function(buffer, level = 6L, pos = 1L, size = NULL) { 51 | stopifnot( 52 | is.raw(buffer), 53 | is_count(level), 54 | level >= 1L && level <= 9L, 55 | is_count(pos), 56 | is.null(size) || is_count(size) 57 | ) 58 | if (!is.null(size)) size <- as.integer(size) 59 | .Call(c_R_deflate, buffer, as.integer(level), as.integer(pos), size) 60 | } 61 | -------------------------------------------------------------------------------- /R/process.R: -------------------------------------------------------------------------------- 1 | os_type <- function() { 2 | .Platform$OS.type 3 | } 4 | 5 | get_tool <- function(prog) { 6 | if (os_type() == "windows") prog <- paste0(prog, ".exe") 7 | 8 | exe <- system.file(package = "zip", "bin", .Platform$r_arch, prog) 9 | if (exe == "") { 10 | pkgpath <- system.file(package = "zip") 11 | if (basename(pkgpath) == "inst") pkgpath <- dirname(pkgpath) 12 | exe <- file.path(pkgpath, "src", "tools", prog) 13 | if (!file.exists(exe)) return("") 14 | } 15 | exe 16 | } 17 | 18 | unzip_exe <- function() { 19 | get_tool("cmdunzip") 20 | } 21 | 22 | zip_exe <- function() { 23 | get_tool("cmdzip") 24 | } 25 | 26 | zip_data <- new.env(parent = emptyenv()) 27 | 28 | ## R CMD check fix 29 | super <- "" 30 | 31 | #' Class for an external unzip process 32 | #' 33 | #' `unzip_process()` returns an R6 class that represents an unzip process. 34 | #' It is implemented as a subclass of [processx::process]. 35 | #' 36 | #' @section Using the `unzip_process` class: 37 | #' 38 | #' ``` 39 | #' up <- unzip_process()$new(zipfile, exdir = ".", poll_connection = TRUE, 40 | #' stderr = tempfile(), ...) 41 | #' ``` 42 | #' 43 | #' See [processx::process] for the class methods. 44 | #' 45 | #' Arguments: 46 | #' * `zipfile`: Path to the zip file to uncompress. 47 | #' * `exdir`: Directory to uncompress the archive to. If it does not 48 | #' exist, it will be created. 49 | #' * `poll_connection`: passed to the `initialize` method of 50 | #' [processx::process], it allows using [processx::poll()] or the 51 | #' `poll_io()` method to poll for the completion of the process. 52 | #' * `stderr`: passed to the `initialize` method of [processx::process], 53 | #' by default the standard error is written to a temporary file. 54 | #' This file can be used to diagnose errors if the process failed. 55 | #' * `...` passed to the `initialize` method of [processx::process]. 56 | #' 57 | #' @return An `unzip_process` R6 class object, a subclass of 58 | #' [processx::process]. 59 | #' 60 | #' @export 61 | #' @examples 62 | #' ex <- system.file("example.zip", package = "zip") 63 | #' tmp <- tempfile() 64 | #' up <- unzip_process()$new(ex, exdir = tmp) 65 | #' up$wait() 66 | #' up$get_exit_status() 67 | #' dir(tmp) 68 | 69 | unzip_process <- function() { 70 | need_packages(c("processx", "R6"), "creating unzip processes") 71 | zip_data$unzip_class <- zip_data$unzip_class %||% 72 | R6::R6Class( 73 | "unzip_process", 74 | inherit = processx::process, 75 | public = list( 76 | initialize = function( 77 | zipfile, 78 | exdir = ".", 79 | poll_connection = TRUE, 80 | stderr = tempfile(), 81 | ... 82 | ) { 83 | stopifnot( 84 | is_string(zipfile), 85 | is_string(exdir) 86 | ) 87 | exdir <- normalizePath(exdir, winslash = "\\", mustWork = FALSE) 88 | super$initialize( 89 | unzip_exe(), 90 | enc2c(c(zipfile, exdir)), 91 | poll_connection = poll_connection, 92 | stderr = stderr, 93 | ... 94 | ) 95 | } 96 | ), 97 | private = list() 98 | ) 99 | 100 | zip_data$unzip_class 101 | } 102 | 103 | #' Class for an external zip process 104 | #' 105 | #' `zip_process()` returns an R6 class that represents a zip process. 106 | #' It is implemented as a subclass of [processx::process]. 107 | #' 108 | #' @section Using the `zip_process` class: 109 | #' 110 | #' ``` 111 | #' zp <- zip_process()$new(zipfile, files, recurse = TRUE, 112 | #' poll_connection = TRUE, 113 | #' stderr = tempfile(), ...) 114 | #' ``` 115 | #' 116 | #' See [processx::process] for the class methods. 117 | #' 118 | #' Arguments: 119 | #' * `zipfile`: Path to the zip file to create. 120 | #' * `files`: List of file to add to the archive. Each specified file 121 | #' or directory in is created as a top-level entry in the zip archive. 122 | #' * `recurse`: Whether to add the contents of directories recursively. 123 | #' * `include_directories`: Whether to explicitly include directories 124 | #' in the archive. Including directories might confuse MS Office when 125 | #' reading docx files, so set this to `FALSE` for creating them. 126 | #' * `poll_connection`: passed to the `initialize` method of 127 | #' [processx::process], it allows using [processx::poll()] or the 128 | #' `poll_io()` method to poll for the completion of the process. 129 | #' * `stderr`: passed to the `initialize` method of [processx::process], 130 | #' by default the standard error is written to a temporary file. 131 | #' This file can be used to diagnose errors if the process failed. 132 | #' * `...` passed to the `initialize` method of [processx::process]. 133 | #' 134 | #' @return A `zip_process` R6 class object, a subclass of 135 | #' [processx::process]. 136 | #' 137 | #' @export 138 | #' @examples 139 | #' dir.create(tmp <- tempfile()) 140 | #' write.table(iris, file = file.path(tmp, "iris.ssv")) 141 | #' zipfile <- tempfile(fileext = ".zip") 142 | #' zp <- zip_process()$new(zipfile, tmp) 143 | #' zp$wait() 144 | #' zp$get_exit_status() 145 | #' zip_list(zipfile) 146 | 147 | zip_process <- function() { 148 | need_packages(c("processx", "R6"), "creating zip processes") 149 | zip_data$zip_class <- zip_data$zip_class %||% 150 | R6::R6Class( 151 | "zip_process", 152 | inherit = processx::process, 153 | public = list( 154 | initialize = function( 155 | zipfile, 156 | files, 157 | recurse = TRUE, 158 | include_directories = TRUE, 159 | poll_connection = TRUE, 160 | stderr = tempfile(), 161 | ... 162 | ) { 163 | private$zipfile <- zipfile 164 | private$files <- files 165 | private$recurse <- recurse 166 | private$include_directories <- include_directories 167 | private$params_file <- tempfile() 168 | write_zip_params( 169 | files, 170 | recurse, 171 | include_directories, 172 | private$params_file 173 | ) 174 | super$initialize( 175 | zip_exe(), 176 | enc2c(c(zipfile, private$params_file)), 177 | poll_connection = poll_connection, 178 | stderr = stderr, 179 | ... 180 | ) 181 | } 182 | ), 183 | private = list( 184 | zipfile = NULL, 185 | files = NULL, 186 | recurse = NULL, 187 | include_directories = NULL, 188 | params_file = NULL 189 | ) 190 | ) 191 | 192 | zip_data$zip_class 193 | } 194 | 195 | write_zip_params <- function(files, recurse, include_directories, outfile) { 196 | data <- get_zip_data( 197 | files, 198 | recurse, 199 | keep_path = FALSE, 200 | include_directories = include_directories 201 | ) 202 | mtime <- as.double(file.info(data$file)$mtime) 203 | 204 | con <- file(outfile, open = "wb") 205 | on.exit(close(con)) 206 | 207 | ## Number of files 208 | writeBin(con = con, as.integer(nrow(data))) 209 | 210 | ## Key, first total length 211 | data$key <- data$key <- fix_absolute_paths(data$key) 212 | warn_for_colon(data$key) 213 | warn_for_dotdot(data$key) 214 | writeBin(con = con, as.integer(sum(nchar(data$key, type = "bytes") + 1L))) 215 | writeBin(con = con, data$key) 216 | 217 | ## Filenames 218 | writeBin(con = con, as.integer(sum(nchar(data$file, type = "bytes") + 1L))) 219 | writeBin(con = con, data$file) 220 | 221 | ## Is dir or not 222 | writeBin(con = con, as.integer(data$dir)) 223 | 224 | ## mtime 225 | writeBin(con = con, as.double(mtime)) 226 | } 227 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | `%||%` <- function(l, r) if (is.null(l)) r else l 2 | 3 | get_zip_data <- function(files, recurse, keep_path, include_directories) { 4 | list <- if (keep_path) { 5 | get_zip_data_path(files, recurse) 6 | } else { 7 | get_zip_data_nopath(files, recurse) 8 | } 9 | 10 | if (!include_directories) { 11 | list <- list[!list$dir, ] 12 | } 13 | 14 | list 15 | } 16 | 17 | get_zip_data_path <- function(files, recurse) { 18 | if (recurse && length(files)) { 19 | data <- do.call(rbind, lapply(files, get_zip_data_path_recursive)) 20 | dup <- duplicated(data$files) 21 | if (any(dup)) data <- data <- data[!dup, drop = FALSE] 22 | data 23 | } else { 24 | files <- ignore_dirs_with_warning(files) 25 | data_frame( 26 | key = files, 27 | files = files, 28 | dir = rep(FALSE, length(files)) 29 | ) 30 | } 31 | } 32 | 33 | warn_for_dotdot <- function(files) { 34 | if (any(grepl("^[.][/\\\\]", files))) { 35 | warning("Some paths start with `./`, creating non-portable zip file") 36 | } 37 | if (any(grepl("^[.][.][/\\\\]", files))) { 38 | warning( 39 | "Some paths reference parent directory, ", 40 | "creating non-portable zip file" 41 | ) 42 | } 43 | files 44 | } 45 | 46 | warn_for_colon <- function(files) { 47 | if (any(grepl(":", files, fixed = TRUE))) { 48 | warning( 49 | "Some paths include a `:` character, this might cause issues ", 50 | "when uncompressing the zip file on Windows." 51 | ) 52 | } 53 | } 54 | 55 | fix_absolute_paths <- function(files) { 56 | if (any(startsWith(files, "/"))) { 57 | warning( 58 | "Dropping leading `/` from paths, all paths in a zip file ", 59 | "must be relative paths." 60 | ) 61 | files <- sub("^/", "", files) 62 | } 63 | files 64 | } 65 | 66 | get_zip_data_nopath <- function(files, recurse) { 67 | if ("." %in% files) { 68 | files <- c(setdiff(files, "."), dir(all.files = TRUE, no.. = TRUE)) 69 | } 70 | if (recurse && length(files)) { 71 | data <- do.call(rbind, lapply(files, get_zip_data_nopath_recursive)) 72 | dup <- duplicated(data$files) 73 | if (any(dup)) data <- data[!dup, drop = FALSE] 74 | data 75 | } else { 76 | files <- ignore_dirs_with_warning(files) 77 | data_frame( 78 | key = basename(files), 79 | file = files, 80 | dir = rep(FALSE, length(files)) 81 | ) 82 | } 83 | } 84 | 85 | ignore_dirs_with_warning <- function(files) { 86 | info <- file.info(files) 87 | if (any(info$isdir)) { 88 | warning("directories ignored in zip file, specify recurse = TRUE") 89 | files <- files[!info$isdir] 90 | } 91 | files 92 | } 93 | 94 | get_zip_data_path_recursive <- function(x) { 95 | if (file.info(x)$isdir) { 96 | files <- c( 97 | x, 98 | dir( 99 | x, 100 | recursive = TRUE, 101 | full.names = TRUE, 102 | all.files = TRUE, 103 | include.dirs = TRUE, 104 | no.. = TRUE 105 | ) 106 | ) 107 | dir <- file.info(files)$isdir 108 | data_frame( 109 | key = ifelse(dir, paste0(files, "/"), files), 110 | file = normalizePath(files), 111 | dir = dir 112 | ) 113 | } else { 114 | data_frame( 115 | key = x, 116 | file = normalizePath(x), 117 | dir = FALSE 118 | ) 119 | } 120 | } 121 | 122 | get_zip_data_nopath_recursive <- function(x) { 123 | if ("." %in% x) { 124 | x <- c(setdiff(x, "."), dir(all.files = TRUE, no.. = TRUE)) 125 | } 126 | x <- normalizePath(x) 127 | wd <- getwd() 128 | on.exit(setwd(wd)) 129 | setwd(dirname(x)) 130 | bnx <- basename(x) 131 | 132 | files <- dir( 133 | bnx, 134 | recursive = TRUE, 135 | all.files = TRUE, 136 | include.dirs = TRUE, 137 | no.. = TRUE 138 | ) 139 | 140 | key <- c(bnx, file.path(bnx, files)) 141 | files <- c(x, file.path(dirname(x), bnx, files)) 142 | dir <- file.info(files)$isdir 143 | key <- ifelse(dir, paste0(key, "/"), key) 144 | 145 | data_frame( 146 | key = key, 147 | file = normalizePath(files), 148 | dir = dir 149 | ) 150 | } 151 | 152 | mkdirp <- function(x, ...) { 153 | dir.create(x, showWarnings = FALSE, recursive = TRUE, ...) 154 | } 155 | 156 | need_packages <- function(pkgs, what = "this function") { 157 | for (p in pkgs) { 158 | if (!requireNamespace(p, quietly = TRUE)) { 159 | stop(sprintf("The `%s` package is needed for %s", p, what)) 160 | } 161 | } 162 | } 163 | 164 | enc2c <- function(x) { 165 | if (.Platform$OS.type == "windows") { 166 | enc2utf8(x) 167 | } else { 168 | enc2native(x) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /R/zip-package.R: -------------------------------------------------------------------------------- 1 | #' @aliases zip-package NULL 2 | #' @keywords internal 3 | "_PACKAGE" 4 | 5 | ## usethis namespace: start 6 | ## usethis namespace: end 7 | NULL 8 | -------------------------------------------------------------------------------- /R/zip.R: -------------------------------------------------------------------------------- 1 | #' @useDynLib zip, .registration = TRUE, .fixes = "c_" 2 | NULL 3 | 4 | #' Compress Files into 'zip' Archives 5 | #' 6 | #' `zip()` creates a new zip archive file. 7 | #' 8 | #' `zip_append()` appends compressed files to an existing 'zip' file. 9 | #' 10 | #' ## Relative paths 11 | #' 12 | #' `zip()` and `zip_append()` can run in two different modes: mirror 13 | #' mode and cherry picking mode. They handle the specified `files` 14 | #' differently. 15 | #' 16 | #' ### Mirror mode 17 | #' 18 | #' Mirror mode is for creating the zip archive of a directory structure, 19 | #' exactly as it is on the disk. The current working directory will 20 | #' be the root of the archive, and the paths will be fully kept. 21 | #' zip changes the current directory to `root` before creating the 22 | #' archive. 23 | #' 24 | #' E.g. consider the following directory structure: 25 | #' 26 | #' ```{r echo = FALSE, comment = ""} 27 | #' dir.create(tmp <- tempfile()) 28 | #' oldwd <- getwd() 29 | #' setwd(tmp) 30 | #' dir.create("foo/bar", recursive = TRUE) 31 | #' dir.create("foo/bar2") 32 | #' dir.create("foo2") 33 | #' cat("this is file1", file = "foo/bar/file1") 34 | #' cat("this is file2", file = "foo/bar/file2") 35 | #' cat("this is file3", file = "foo2/file3") 36 | #' out <- processx::run("tree", c("--noreport", "--charset=ascii")) 37 | #' cat(crayon::strip_style(out$stdout)) 38 | #' setwd(oldwd) 39 | #' ``` 40 | #' 41 | #' Assuming the current working directory is `foo`, the following zip 42 | #' entries are created by `zip`: 43 | #' ```{r, echo = 2:4} 44 | #' setwd(tmp) 45 | #' setwd("foo") 46 | #' zip::zip("../test.zip", c("bar/file1", "bar2", "../foo2")) 47 | #' zip_list("../test.zip")[, "filename", drop = FALSE] 48 | #' setwd(oldwd) 49 | #' ``` 50 | #' 51 | #' Note that zip refuses to store files with absolute paths, and chops 52 | #' off the leading `/` character from these file names. This is because 53 | #' only relative paths are allowed in zip files. 54 | #' 55 | #' ### Cherry picking mode 56 | #' 57 | #' In cherry picking mode, the selected files and directories 58 | #' will be at the root of the archive. This mode is handy if you 59 | #' want to select a subset of files and directories, possibly from 60 | #' different paths and put all of the in the archive, at the top 61 | #' level. 62 | #' 63 | #' Here is an example with the same directory structure as above: 64 | #' 65 | #' ```{r, echo = 3:4} 66 | #' setwd(tmp) 67 | #' setwd("foo") 68 | #' zip::zip( 69 | #' "../test2.zip", 70 | #' c("bar/file1", "bar2", "../foo2"), 71 | #' mode = "cherry-pick" 72 | #') 73 | #' zip_list("../test2.zip")[, "filename", drop = FALSE] 74 | #' setwd(oldwd) 75 | #' ``` 76 | #' 77 | #' From zip version 2.3.0, `"."` has a special meaning in the `files` 78 | #' argument: it will include the files (and possibly directories) within 79 | #' the current working directory, but **not** the working directory itself. 80 | #' Note that this only applies to cherry picking mode. 81 | #' 82 | #' ## Permissions: 83 | #' 84 | #' `zip()` (and `zip_append()`, etc.) add the permissions of 85 | #' the archived files and directories to the ZIP archive, on Unix systems. 86 | #' Most zip and unzip implementations support these, so they will be 87 | #' recovered after extracting the archive. 88 | #' 89 | #' Note, however that the owner and group (uid and gid) are currently 90 | #' omitted, even on Unix. 91 | #' 92 | #' ## `zipr()` and `zipr_append()` 93 | #' 94 | #' These function exist for historical reasons. They are identical 95 | #' to `zip()` and `zipr_append()` with a different default for the 96 | #' `mode` argument. 97 | #' 98 | #' @param zipfile The zip file to create. If the file exists, `zip` 99 | #' overwrites it, but `zip_append` appends to it. If it is a directory 100 | #' an error is thrown. 101 | #' @param files List of file to add to the archive. See details below 102 | #' about absolute and relative path names. 103 | #' @param recurse Whether to add the contents of directories recursively. 104 | #' @param compression_level A number between 1 and 9. 9 compresses best, 105 | #' but it also takes the longest. 106 | #' @param include_directories Whether to explicitly include directories 107 | #' in the archive. Including directories might confuse MS Office when 108 | #' reading docx files, so set this to `FALSE` for creating them. 109 | #' @param root Change to this working directory before creating the 110 | #' archive. 111 | #' @param mode Selects how files and directories are stored in 112 | #' the archive. It can be `"mirror"` or `"cherry-pick"`. 113 | #' See "Relative Paths" below for details. 114 | #' @return The name of the created zip file, invisibly. 115 | #' 116 | #' @export 117 | #' @examples 118 | #' ## Some files to zip up. We will run all this in the R session's 119 | #' ## temporary directory, to avoid messing up the user's workspace. 120 | #' dir.create(tmp <- tempfile()) 121 | #' dir.create(file.path(tmp, "mydir")) 122 | #' cat("first file", file = file.path(tmp, "mydir", "file1")) 123 | #' cat("second file", file = file.path(tmp, "mydir", "file2")) 124 | #' 125 | #' zipfile <- tempfile(fileext = ".zip") 126 | #' zip::zip(zipfile, "mydir", root = tmp) 127 | #' 128 | #' ## List contents 129 | #' zip_list(zipfile) 130 | #' 131 | #' ## Add another file 132 | #' cat("third file", file = file.path(tmp, "mydir", "file3")) 133 | #' zip_append(zipfile, file.path("mydir", "file3"), root = tmp) 134 | #' zip_list(zipfile) 135 | 136 | zip <- function( 137 | zipfile, 138 | files, 139 | recurse = TRUE, 140 | compression_level = 9, 141 | include_directories = TRUE, 142 | root = ".", 143 | mode = c("mirror", "cherry-pick") 144 | ) { 145 | mode <- match.arg(mode) 146 | zip_internal( 147 | zipfile, 148 | files, 149 | recurse, 150 | compression_level, 151 | append = FALSE, 152 | root = root, 153 | keep_path = (mode == "mirror"), 154 | include_directories = include_directories 155 | ) 156 | } 157 | 158 | #' @rdname zip 159 | #' @export 160 | 161 | zipr <- function( 162 | zipfile, 163 | files, 164 | recurse = TRUE, 165 | compression_level = 9, 166 | include_directories = TRUE, 167 | root = ".", 168 | mode = c("cherry-pick", "mirror") 169 | ) { 170 | mode <- match.arg(mode) 171 | zip_internal( 172 | zipfile, 173 | files, 174 | recurse, 175 | compression_level, 176 | append = FALSE, 177 | root = root, 178 | keep_path = (mode == "mirror"), 179 | include_directories = include_directories 180 | ) 181 | } 182 | 183 | #' @rdname zip 184 | #' @export 185 | 186 | zip_append <- function( 187 | zipfile, 188 | files, 189 | recurse = TRUE, 190 | compression_level = 9, 191 | include_directories = TRUE, 192 | root = ".", 193 | mode = c("mirror", "cherry-pick") 194 | ) { 195 | mode <- match.arg(mode) 196 | zip_internal( 197 | zipfile, 198 | files, 199 | recurse, 200 | compression_level, 201 | append = TRUE, 202 | root = root, 203 | keep_path = (mode == "mirror"), 204 | include_directories = include_directories 205 | ) 206 | } 207 | 208 | #' @rdname zip 209 | #' @export 210 | 211 | zipr_append <- function( 212 | zipfile, 213 | files, 214 | recurse = TRUE, 215 | compression_level = 9, 216 | include_directories = TRUE, 217 | root = ".", 218 | mode = c("cherry-pick", "mirror") 219 | ) { 220 | mode <- match.arg(mode) 221 | zip_internal( 222 | zipfile, 223 | files, 224 | recurse, 225 | compression_level, 226 | append = TRUE, 227 | root = root, 228 | keep_path = (mode == "mirror"), 229 | include_directories = include_directories 230 | ) 231 | } 232 | 233 | zip_internal <- function( 234 | zipfile, 235 | files, 236 | recurse, 237 | compression_level, 238 | append, 239 | root, 240 | keep_path, 241 | include_directories 242 | ) { 243 | zipfile <- path.expand(zipfile) 244 | if (dir.exists(zipfile)) { 245 | stop("zip file at `", zipfile, "` already exists and it is a directory") 246 | } 247 | oldwd <- setwd(root) 248 | on.exit(setwd(oldwd), add = TRUE) 249 | 250 | if (!all(file.exists(files))) stop("Some files do not exist") 251 | 252 | data <- get_zip_data(files, recurse, keep_path, include_directories) 253 | data$key <- fix_absolute_paths(data$key) 254 | warn_for_colon(data$key) 255 | warn_for_dotdot(data$key) 256 | 257 | .Call( 258 | c_R_zip_zip, 259 | enc2c(zipfile), 260 | enc2c(data$key), 261 | enc2c(data$file), 262 | data$dir, 263 | file.info(data$file)$mtime, 264 | as.integer(compression_level), 265 | append 266 | ) 267 | 268 | invisible(zipfile) 269 | } 270 | 271 | #' List Files in a 'zip' Archive 272 | #' 273 | #' @details Note that `crc32` is formatted using `as.hexmode()`. `offset` refers 274 | #' to the start of the local zip header for each entry. Following the approach 275 | #' of `seek()` it is stored as a `numeric` rather than an `integer` vector and 276 | #' can therefore represent values up to `2^53-1` (9 PB). 277 | #' @param zipfile Path to an existing ZIP file. 278 | #' @return A data frame with columns: `filename`, `compressed_size`, 279 | #' `uncompressed_size`, `timestamp`, `permissions`, `crc32`, `offset` and 280 | #' `type`. `type` is one of `file`, `block_device`, `character_device`, 281 | #' `directory`, `FIFO`, `symlink` or `socket`. 282 | #' 283 | #' @family zip/unzip functions 284 | #' @export 285 | 286 | zip_list <- function(zipfile) { 287 | zipfile <- enc2c(normalizePath(zipfile)) 288 | res <- .Call(c_R_zip_list, zipfile) 289 | if (Sys.getenv("PKGCACHE_NO_PILLAR") == "") { 290 | requireNamespace("pillar", quietly = TRUE) 291 | } 292 | df <- data_frame( 293 | filename = res[[1]], 294 | compressed_size = res[[2]], 295 | uncompressed_size = res[[3]], 296 | timestamp = as.POSIXct(res[[4]], tz = "UTC", origin = "1970-01-01") 297 | ) 298 | Encoding(df$filename) <- "UTF-8" 299 | df$permissions <- as.octmode(res[[5]]) 300 | df$crc32 <- as.hexmode(res[[6]]) 301 | df$offset <- res[[7]] 302 | # names are the same as in `fs::file_info()` 303 | df$type <- file_types[res[[8]] + 1L] 304 | df 305 | } 306 | 307 | file_types <- c( 308 | "file", 309 | "block_device", 310 | "character_device", 311 | "directory", 312 | "FIFO", 313 | "symlink", 314 | "socket" 315 | ) 316 | 317 | #' Uncompress 'zip' Archives 318 | #' 319 | #' `unzip()` always restores modification times of the extracted files and 320 | #' directories. 321 | #' 322 | #' @section Permissions: 323 | #' 324 | #' If the zip archive stores permissions and was created on Unix, 325 | #' the permissions will be restored. 326 | #' 327 | #' @param zipfile Path to the zip file to uncompress. 328 | #' @param files Character vector of files to extract from the archive. 329 | #' Files within directories can be specified, but they must use a forward 330 | #' slash as path separator, as this is what zip files use internally. 331 | #' If `NULL`, all files will be extracted. 332 | #' @param overwrite Whether to overwrite existing files. If `FALSE` and 333 | #' a file already exists, then an error is thrown. 334 | #' @param junkpaths Whether to ignore all directory paths when creating 335 | #' files. If `TRUE`, all files will be created in `exdir`. 336 | #' @param exdir Directory to uncompress the archive to. If it does not 337 | #' exist, it will be created. 338 | #' 339 | #' @export 340 | #' @examples 341 | #' ## temporary directory, to avoid messing up the user's workspace. 342 | #' dir.create(tmp <- tempfile()) 343 | #' dir.create(file.path(tmp, "mydir")) 344 | #' cat("first file", file = file.path(tmp, "mydir", "file1")) 345 | #' cat("second file", file = file.path(tmp, "mydir", "file2")) 346 | #' 347 | #' zipfile <- tempfile(fileext = ".zip") 348 | #' zip::zip(zipfile, "mydir", root = tmp) 349 | #' 350 | #' ## List contents 351 | #' zip_list(zipfile) 352 | #' 353 | #' ## Extract 354 | #' tmp2 <- tempfile() 355 | #' unzip(zipfile, exdir = tmp2) 356 | #' dir(tmp2, recursive = TRUE) 357 | 358 | unzip <- function( 359 | zipfile, 360 | files = NULL, 361 | overwrite = TRUE, 362 | junkpaths = FALSE, 363 | exdir = "." 364 | ) { 365 | stopifnot( 366 | is_string(zipfile), 367 | is_character_or_null(files), 368 | is_flag(overwrite), 369 | is_flag(junkpaths), 370 | is_string(exdir) 371 | ) 372 | 373 | zipfile <- enc2c(normalizePath(zipfile)) 374 | if (!is.null(files)) files <- enc2c(files) 375 | exdir <- sub("/+$", "", exdir) 376 | mkdirp(exdir) 377 | exdir <- enc2c(normalizePath(exdir)) 378 | 379 | .Call(c_R_zip_unzip, zipfile, files, overwrite, junkpaths, exdir) 380 | 381 | invisible() 382 | } 383 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r} 8 | #| include: false 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | fig.path = "man/figures/README-", 13 | out.width = "100%" 14 | ) 15 | ``` 16 | 17 | # zip 18 | 19 | > Cross-Platform 'zip' Compression 20 | 21 | 22 | [![R-CMD-check](https://github.com/r-lib/zip/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/zip/actions/workflows/R-CMD-check.yaml) 23 | [![](https://www.r-pkg.org/badges/version/zip)](https://www.r-pkg.org/pkg/zip) 24 | [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/zip)](https://www.r-pkg.org/pkg/zip) 25 | [![Codecov test coverage](https://codecov.io/gh/r-lib/zip/graph/badge.svg)](https://app.codecov.io/gh/r-lib/zip) 26 | 27 | 28 | ## Installation 29 | 30 | Stable version: 31 | 32 | ```{r} 33 | #| eval: false 34 | install.packages("zip") 35 | ``` 36 | 37 | Development version: 38 | 39 | ```{r} 40 | #| eval: false 41 | pak::pak("r-lib/zip") 42 | ``` 43 | 44 | ## Usage 45 | 46 | ```{r} 47 | #| include: false 48 | #| echo: false 49 | #| results: hide 50 | library(zip) 51 | ``` 52 | 53 | ```{r} 54 | library(zip) 55 | ``` 56 | 57 | ### Creating ZIP files 58 | 59 | `zip()` creates a new ZIP archive. (It overwrites the output file if it 60 | exists.) Simply supply all directories and files that you want to include 61 | in the archive. 62 | 63 | It makes sense to change to the top-level directory of the files before 64 | archiving them, so that the files are stored using a relative path name. 65 | 66 | ```{r} 67 | zip("sources.zip", c("R", "src")) 68 | file.info("sources.zip") 69 | ``` 70 | 71 | Directories are added recursively by default. 72 | 73 | `zip_append()` is similar to `zip()`, but it appends files to an existing 74 | ZIP archive. 75 | 76 | ### Listing ZIP files 77 | 78 | `zip_list()` lists files in a ZIP archive. It returns a data frame: 79 | 80 | ```{r} 81 | zip_list("sources.zip") 82 | ``` 83 | 84 | ### Uncompressing ZIP files 85 | 86 | `unzip()` uncompresses a ZIP archive: 87 | 88 | ```{r} 89 | exdir <- tempfile() 90 | unzip("sources.zip", exdir = exdir) 91 | dir(exdir) 92 | ``` 93 | 94 | ### Compressing and uncompressing in background processes 95 | 96 | You can use the `zip_process()` and `unzip_process()` functions to 97 | create background zip / unzip processes. These processes were implemented 98 | on top of the `processx::process` class, so they are pollable. 99 | 100 | ## License 101 | 102 | MIT 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # zip 5 | 6 | > Cross-Platform ‘zip’ Compression 7 | 8 | 9 | 10 | [![R-CMD-check](https://github.com/r-lib/zip/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/zip/actions/workflows/R-CMD-check.yaml) 11 | [![](https://www.r-pkg.org/badges/version/zip)](https://www.r-pkg.org/pkg/zip) 12 | [![CRAN RStudio mirror 13 | downloads](https://cranlogs.r-pkg.org/badges/zip)](https://www.r-pkg.org/pkg/zip) 14 | [![Codecov test 15 | coverage](https://codecov.io/gh/r-lib/zip/graph/badge.svg)](https://app.codecov.io/gh/r-lib/zip) 16 | 17 | 18 | ## Installation 19 | 20 | Stable version: 21 | 22 | ``` r 23 | install.packages("zip") 24 | ``` 25 | 26 | Development version: 27 | 28 | ``` r 29 | pak::pak("r-lib/zip") 30 | ``` 31 | 32 | ## Usage 33 | 34 | ``` r 35 | library(zip) 36 | ``` 37 | 38 | ### Creating ZIP files 39 | 40 | `zip()` creates a new ZIP archive. (It overwrites the output file if it 41 | exists.) Simply supply all directories and files that you want to 42 | include in the archive. 43 | 44 | It makes sense to change to the top-level directory of the files before 45 | archiving them, so that the files are stored using a relative path name. 46 | 47 | ``` r 48 | zip("sources.zip", c("R", "src")) 49 | file.info("sources.zip") 50 | #> size isdir mode mtime ctime 51 | #> sources.zip 603127 FALSE 644 2025-01-07 10:40:54 2025-01-07 10:40:54 52 | #> atime uid gid uname grname 53 | #> sources.zip 2023-11-03 17:09:37 501 20 gaborcsardi staff 54 | ``` 55 | 56 | Directories are added recursively by default. 57 | 58 | `zip_append()` is similar to `zip()`, but it appends files to an 59 | existing ZIP archive. 60 | 61 | ### Listing ZIP files 62 | 63 | `zip_list()` lists files in a ZIP archive. It returns a data frame: 64 | 65 | ``` r 66 | zip_list("sources.zip") 67 | #> # A data frame: 49 × 7 68 | #> filename compressed_size uncompressed_size timestamp permissions 69 | #> 70 | #> 1 R/ 0 0 2025-01-07 09:36:04 755 71 | #> 2 R/assertio… 151 398 2023-04-17 10:20:40 644 72 | #> 3 R/compat-v… 3333 13294 2025-01-07 09:36:04 644 73 | #> 4 R/inflate.R 627 2174 2023-04-17 10:20:40 644 74 | #> 5 R/process.R 1793 6585 2023-04-17 10:20:40 644 75 | #> 6 R/utils.R 1184 3757 2025-01-07 09:36:50 644 76 | #> 7 R/zip-pack… 99 122 2023-11-07 01:18:44 644 77 | #> 8 R/zip.R 3290 10384 2025-01-07 09:39:50 644 78 | #> 9 src/ 0 0 2025-01-07 09:01:34 755 79 | #> 10 src/init.c 406 1043 2023-11-07 01:18:16 644 80 | #> # ℹ 39 more rows 81 | #> # ℹ 2 more variables: crc32 , offset 82 | ``` 83 | 84 | ### Uncompressing ZIP files 85 | 86 | `unzip()` uncompresses a ZIP archive: 87 | 88 | ``` r 89 | exdir <- tempfile() 90 | unzip("sources.zip", exdir = exdir) 91 | dir(exdir) 92 | #> [1] "R" "src" 93 | ``` 94 | 95 | ### Compressing and uncompressing in background processes 96 | 97 | You can use the `zip_process()` and `unzip_process()` functions to 98 | create background zip / unzip processes. These processes were 99 | implemented on top of the `processx::process` class, so they are 100 | pollable. 101 | 102 | ## License 103 | 104 | MIT 105 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://r-lib.github.io/zip/ 2 | template: 3 | bootstrap: 5 4 | 5 | includes: 6 | in_header: | 7 | 8 | 9 | development: 10 | mode: auto 11 | -------------------------------------------------------------------------------- /air.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/zip/14d8c7eb7a5abe4c60dbc718a2507b184115d8a4/air.toml -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /inst/example.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/zip/14d8c7eb7a5abe4c60dbc718a2507b184115d8a4/inst/example.zip -------------------------------------------------------------------------------- /man/deflate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/inflate.R 3 | \name{deflate} 4 | \alias{deflate} 5 | \title{Compress a raw GZIP stream} 6 | \usage{ 7 | deflate(buffer, level = 6L, pos = 1L, size = NULL) 8 | } 9 | \arguments{ 10 | \item{buffer}{Raw vector, containing the data to compress.} 11 | 12 | \item{level}{Compression level, integer between 1 (fatest) and 9 (best).} 13 | 14 | \item{pos}{Start position of data to compress in \code{buffer}.} 15 | 16 | \item{size}{Compressed size estimate, or \code{NULL}. If not given, or too 17 | small, the output buffer is resized multiple times.} 18 | } 19 | \value{ 20 | Named list with three entries: 21 | \itemize{ 22 | \item \code{output}: raw vector, the compressed data, 23 | \item \code{bytes_read}: number of bytes used from \code{buffer}, 24 | \item \code{bytes_written}: number of bytes written to the output buffer. 25 | } 26 | } 27 | \description{ 28 | Compress a raw GZIP stream 29 | } 30 | \examples{ 31 | data_gz <- deflate(charToRaw("Hello world!")) 32 | inflate(data_gz$output) 33 | } 34 | \seealso{ 35 | \code{\link[base:memCompress]{base::memCompress()}} does the same with \code{type = "gzip"}, 36 | but it does not tell you the number of bytes read from the input. 37 | } 38 | -------------------------------------------------------------------------------- /man/inflate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/inflate.R 3 | \name{inflate} 4 | \alias{inflate} 5 | \title{Uncompress a raw GZIP stream} 6 | \usage{ 7 | inflate(buffer, pos = 1L, size = NULL) 8 | } 9 | \arguments{ 10 | \item{buffer}{Raw vector, containing the data to uncompress.} 11 | 12 | \item{pos}{Start position of data to uncompress in \code{buffer}.} 13 | 14 | \item{size}{Uncompressed size estimate, or \code{NULL}. If not given, or too 15 | small, the output buffer is resized multiple times.} 16 | } 17 | \value{ 18 | Named list with three entries: 19 | \itemize{ 20 | \item \code{output}: raw vector, the uncompressed data, 21 | \item \code{bytes_read}: number of bytes used from \code{buffer}, 22 | \item \code{bytes_written}: number of bytes written to the output buffer. 23 | } 24 | } 25 | \description{ 26 | Uncompress a raw GZIP stream 27 | } 28 | \examples{ 29 | data_gz <- deflate(charToRaw("Hello world!")) 30 | inflate(data_gz$output) 31 | } 32 | \seealso{ 33 | \code{\link[base:memCompress]{base::memDecompress()}} does the same with \code{type = "gzip"}, 34 | but it does not tell you the number of bytes read from the input. 35 | } 36 | -------------------------------------------------------------------------------- /man/unzip.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/zip.R 3 | \name{unzip} 4 | \alias{unzip} 5 | \title{Uncompress 'zip' Archives} 6 | \usage{ 7 | unzip(zipfile, files = NULL, overwrite = TRUE, junkpaths = FALSE, exdir = ".") 8 | } 9 | \arguments{ 10 | \item{zipfile}{Path to the zip file to uncompress.} 11 | 12 | \item{files}{Character vector of files to extract from the archive. 13 | Files within directories can be specified, but they must use a forward 14 | slash as path separator, as this is what zip files use internally. 15 | If \code{NULL}, all files will be extracted.} 16 | 17 | \item{overwrite}{Whether to overwrite existing files. If \code{FALSE} and 18 | a file already exists, then an error is thrown.} 19 | 20 | \item{junkpaths}{Whether to ignore all directory paths when creating 21 | files. If \code{TRUE}, all files will be created in \code{exdir}.} 22 | 23 | \item{exdir}{Directory to uncompress the archive to. If it does not 24 | exist, it will be created.} 25 | } 26 | \description{ 27 | \code{unzip()} always restores modification times of the extracted files and 28 | directories. 29 | } 30 | \section{Permissions}{ 31 | 32 | 33 | If the zip archive stores permissions and was created on Unix, 34 | the permissions will be restored. 35 | } 36 | 37 | \examples{ 38 | ## temporary directory, to avoid messing up the user's workspace. 39 | dir.create(tmp <- tempfile()) 40 | dir.create(file.path(tmp, "mydir")) 41 | cat("first file", file = file.path(tmp, "mydir", "file1")) 42 | cat("second file", file = file.path(tmp, "mydir", "file2")) 43 | 44 | zipfile <- tempfile(fileext = ".zip") 45 | zip::zip(zipfile, "mydir", root = tmp) 46 | 47 | ## List contents 48 | zip_list(zipfile) 49 | 50 | ## Extract 51 | tmp2 <- tempfile() 52 | unzip(zipfile, exdir = tmp2) 53 | dir(tmp2, recursive = TRUE) 54 | } 55 | -------------------------------------------------------------------------------- /man/unzip_process.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/process.R 3 | \name{unzip_process} 4 | \alias{unzip_process} 5 | \title{Class for an external unzip process} 6 | \usage{ 7 | unzip_process() 8 | } 9 | \value{ 10 | An \code{unzip_process} R6 class object, a subclass of 11 | \link[processx:process]{processx::process}. 12 | } 13 | \description{ 14 | \code{unzip_process()} returns an R6 class that represents an unzip process. 15 | It is implemented as a subclass of \link[processx:process]{processx::process}. 16 | } 17 | \section{Using the \code{unzip_process} class}{ 18 | 19 | 20 | \if{html}{\out{
}}\preformatted{up <- unzip_process()$new(zipfile, exdir = ".", poll_connection = TRUE, 21 | stderr = tempfile(), ...) 22 | }\if{html}{\out{
}} 23 | 24 | See \link[processx:process]{processx::process} for the class methods. 25 | 26 | Arguments: 27 | \itemize{ 28 | \item \code{zipfile}: Path to the zip file to uncompress. 29 | \item \code{exdir}: Directory to uncompress the archive to. If it does not 30 | exist, it will be created. 31 | \item \code{poll_connection}: passed to the \code{initialize} method of 32 | \link[processx:process]{processx::process}, it allows using \code{\link[processx:poll]{processx::poll()}} or the 33 | \code{poll_io()} method to poll for the completion of the process. 34 | \item \code{stderr}: passed to the \code{initialize} method of \link[processx:process]{processx::process}, 35 | by default the standard error is written to a temporary file. 36 | This file can be used to diagnose errors if the process failed. 37 | \item \code{...} passed to the \code{initialize} method of \link[processx:process]{processx::process}. 38 | } 39 | } 40 | 41 | \examples{ 42 | ex <- system.file("example.zip", package = "zip") 43 | tmp <- tempfile() 44 | up <- unzip_process()$new(ex, exdir = tmp) 45 | up$wait() 46 | up$get_exit_status() 47 | dir(tmp) 48 | } 49 | -------------------------------------------------------------------------------- /man/zip-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/zip-package.R 3 | \docType{package} 4 | \name{zip-package} 5 | \alias{zip-package} 6 | \title{zip: Cross-Platform 'zip' Compression} 7 | \description{ 8 | Cross-Platform 'zip' Compression Library. A replacement for the 'zip' function, that does not require any additional external tools on any platform. 9 | } 10 | \seealso{ 11 | Useful links: 12 | \itemize{ 13 | \item \url{https://github.com/r-lib/zip} 14 | \item \url{https://r-lib.github.io/zip/} 15 | \item Report bugs at \url{https://github.com/r-lib/zip/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} 21 | 22 | Other contributors: 23 | \itemize{ 24 | \item Kuba Podgórski [contributor] 25 | \item Rich Geldreich [contributor] 26 | \item Posit Software, PBC (\href{https://ror.org/03wc8by49}{ROR}) [copyright holder, funder] 27 | } 28 | 29 | } 30 | \keyword{internal} 31 | -------------------------------------------------------------------------------- /man/zip.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/zip.R 3 | \name{zip} 4 | \alias{zip} 5 | \alias{zipr} 6 | \alias{zip_append} 7 | \alias{zipr_append} 8 | \title{Compress Files into 'zip' Archives} 9 | \usage{ 10 | zip( 11 | zipfile, 12 | files, 13 | recurse = TRUE, 14 | compression_level = 9, 15 | include_directories = TRUE, 16 | root = ".", 17 | mode = c("mirror", "cherry-pick") 18 | ) 19 | 20 | zipr( 21 | zipfile, 22 | files, 23 | recurse = TRUE, 24 | compression_level = 9, 25 | include_directories = TRUE, 26 | root = ".", 27 | mode = c("cherry-pick", "mirror") 28 | ) 29 | 30 | zip_append( 31 | zipfile, 32 | files, 33 | recurse = TRUE, 34 | compression_level = 9, 35 | include_directories = TRUE, 36 | root = ".", 37 | mode = c("mirror", "cherry-pick") 38 | ) 39 | 40 | zipr_append( 41 | zipfile, 42 | files, 43 | recurse = TRUE, 44 | compression_level = 9, 45 | include_directories = TRUE, 46 | root = ".", 47 | mode = c("cherry-pick", "mirror") 48 | ) 49 | } 50 | \arguments{ 51 | \item{zipfile}{The zip file to create. If the file exists, \code{zip} 52 | overwrites it, but \code{zip_append} appends to it. If it is a directory 53 | an error is thrown.} 54 | 55 | \item{files}{List of file to add to the archive. See details below 56 | about absolute and relative path names.} 57 | 58 | \item{recurse}{Whether to add the contents of directories recursively.} 59 | 60 | \item{compression_level}{A number between 1 and 9. 9 compresses best, 61 | but it also takes the longest.} 62 | 63 | \item{include_directories}{Whether to explicitly include directories 64 | in the archive. Including directories might confuse MS Office when 65 | reading docx files, so set this to \code{FALSE} for creating them.} 66 | 67 | \item{root}{Change to this working directory before creating the 68 | archive.} 69 | 70 | \item{mode}{Selects how files and directories are stored in 71 | the archive. It can be \code{"mirror"} or \code{"cherry-pick"}. 72 | See "Relative Paths" below for details.} 73 | } 74 | \value{ 75 | The name of the created zip file, invisibly. 76 | } 77 | \description{ 78 | \code{zip()} creates a new zip archive file. 79 | } 80 | \details{ 81 | \code{zip_append()} appends compressed files to an existing 'zip' file. 82 | \subsection{Relative paths}{ 83 | 84 | \code{zip()} and \code{zip_append()} can run in two different modes: mirror 85 | mode and cherry picking mode. They handle the specified \code{files} 86 | differently. 87 | \subsection{Mirror mode}{ 88 | 89 | Mirror mode is for creating the zip archive of a directory structure, 90 | exactly as it is on the disk. The current working directory will 91 | be the root of the archive, and the paths will be fully kept. 92 | zip changes the current directory to \code{root} before creating the 93 | archive. 94 | 95 | E.g. consider the following directory structure: 96 | 97 | \if{html}{\out{
}}\preformatted{. 98 | |-- foo 99 | | |-- bar 100 | | | |-- file1 101 | | | `-- file2 102 | | `-- bar2 103 | `-- foo2 104 | `-- file3 105 | }\if{html}{\out{
}} 106 | 107 | Assuming the current working directory is \code{foo}, the following zip 108 | entries are created by \code{zip}: 109 | 110 | \if{html}{\out{
}}\preformatted{setwd("foo") 111 | zip::zip("../test.zip", c("bar/file1", "bar2", "../foo2")) 112 | #> Warning in warn_for_dotdot(data$key): Some paths reference parent directory, 113 | #> creating non-portable zip file 114 | zip_list("../test.zip")[, "filename", drop = FALSE] 115 | #> # A data frame: 4 x 1 116 | #> filename 117 | #> 118 | #> 1 bar/file1 119 | #> 2 bar2/ 120 | #> 3 ../foo2/ 121 | #> 4 ../foo2/file3 122 | }\if{html}{\out{
}} 123 | 124 | Note that zip refuses to store files with absolute paths, and chops 125 | off the leading \code{/} character from these file names. This is because 126 | only relative paths are allowed in zip files. 127 | } 128 | 129 | \subsection{Cherry picking mode}{ 130 | 131 | In cherry picking mode, the selected files and directories 132 | will be at the root of the archive. This mode is handy if you 133 | want to select a subset of files and directories, possibly from 134 | different paths and put all of the in the archive, at the top 135 | level. 136 | 137 | Here is an example with the same directory structure as above: 138 | 139 | \if{html}{\out{
}}\preformatted{zip::zip( 140 | "../test2.zip", 141 | c("bar/file1", "bar2", "../foo2"), 142 | mode = "cherry-pick" 143 | ) 144 | zip_list("../test2.zip")[, "filename", drop = FALSE] 145 | #> # A data frame: 4 x 1 146 | #> filename 147 | #> 148 | #> 1 file1 149 | #> 2 bar2/ 150 | #> 3 foo2/ 151 | #> 4 foo2/file3 152 | }\if{html}{\out{
}} 153 | 154 | From zip version 2.3.0, \code{"."} has a special meaning in the \code{files} 155 | argument: it will include the files (and possibly directories) within 156 | the current working directory, but \strong{not} the working directory itself. 157 | Note that this only applies to cherry picking mode. 158 | } 159 | 160 | } 161 | 162 | \subsection{Permissions:}{ 163 | 164 | \code{zip()} (and \code{zip_append()}, etc.) add the permissions of 165 | the archived files and directories to the ZIP archive, on Unix systems. 166 | Most zip and unzip implementations support these, so they will be 167 | recovered after extracting the archive. 168 | 169 | Note, however that the owner and group (uid and gid) are currently 170 | omitted, even on Unix. 171 | } 172 | 173 | \subsection{\code{zipr()} and \code{zipr_append()}}{ 174 | 175 | These function exist for historical reasons. They are identical 176 | to \code{zip()} and \code{zipr_append()} with a different default for the 177 | \code{mode} argument. 178 | } 179 | } 180 | \examples{ 181 | ## Some files to zip up. We will run all this in the R session's 182 | ## temporary directory, to avoid messing up the user's workspace. 183 | dir.create(tmp <- tempfile()) 184 | dir.create(file.path(tmp, "mydir")) 185 | cat("first file", file = file.path(tmp, "mydir", "file1")) 186 | cat("second file", file = file.path(tmp, "mydir", "file2")) 187 | 188 | zipfile <- tempfile(fileext = ".zip") 189 | zip::zip(zipfile, "mydir", root = tmp) 190 | 191 | ## List contents 192 | zip_list(zipfile) 193 | 194 | ## Add another file 195 | cat("third file", file = file.path(tmp, "mydir", "file3")) 196 | zip_append(zipfile, file.path("mydir", "file3"), root = tmp) 197 | zip_list(zipfile) 198 | } 199 | -------------------------------------------------------------------------------- /man/zip_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/zip.R 3 | \name{zip_list} 4 | \alias{zip_list} 5 | \title{List Files in a 'zip' Archive} 6 | \usage{ 7 | zip_list(zipfile) 8 | } 9 | \arguments{ 10 | \item{zipfile}{Path to an existing ZIP file.} 11 | } 12 | \value{ 13 | A data frame with columns: \code{filename}, \code{compressed_size}, 14 | \code{uncompressed_size}, \code{timestamp}, \code{permissions}, \code{crc32}, \code{offset} and 15 | \code{type}. \code{type} is one of \code{file}, \code{block_device}, \code{character_device}, 16 | \code{directory}, \code{FIFO}, \code{symlink} or \code{socket}. 17 | } 18 | \description{ 19 | List Files in a 'zip' Archive 20 | } 21 | \details{ 22 | Note that \code{crc32} is formatted using \code{as.hexmode()}. \code{offset} refers 23 | to the start of the local zip header for each entry. Following the approach 24 | of \code{seek()} it is stored as a \code{numeric} rather than an \code{integer} vector and 25 | can therefore represent values up to \code{2^53-1} (9 PB). 26 | } 27 | \concept{zip/unzip functions} 28 | -------------------------------------------------------------------------------- /man/zip_process.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/process.R 3 | \name{zip_process} 4 | \alias{zip_process} 5 | \title{Class for an external zip process} 6 | \usage{ 7 | zip_process() 8 | } 9 | \value{ 10 | A \code{zip_process} R6 class object, a subclass of 11 | \link[processx:process]{processx::process}. 12 | } 13 | \description{ 14 | \code{zip_process()} returns an R6 class that represents a zip process. 15 | It is implemented as a subclass of \link[processx:process]{processx::process}. 16 | } 17 | \section{Using the \code{zip_process} class}{ 18 | 19 | 20 | \if{html}{\out{
}}\preformatted{zp <- zip_process()$new(zipfile, files, recurse = TRUE, 21 | poll_connection = TRUE, 22 | stderr = tempfile(), ...) 23 | }\if{html}{\out{
}} 24 | 25 | See \link[processx:process]{processx::process} for the class methods. 26 | 27 | Arguments: 28 | \itemize{ 29 | \item \code{zipfile}: Path to the zip file to create. 30 | \item \code{files}: List of file to add to the archive. Each specified file 31 | or directory in is created as a top-level entry in the zip archive. 32 | \item \code{recurse}: Whether to add the contents of directories recursively. 33 | \item \code{include_directories}: Whether to explicitly include directories 34 | in the archive. Including directories might confuse MS Office when 35 | reading docx files, so set this to \code{FALSE} for creating them. 36 | \item \code{poll_connection}: passed to the \code{initialize} method of 37 | \link[processx:process]{processx::process}, it allows using \code{\link[processx:poll]{processx::poll()}} or the 38 | \code{poll_io()} method to poll for the completion of the process. 39 | \item \code{stderr}: passed to the \code{initialize} method of \link[processx:process]{processx::process}, 40 | by default the standard error is written to a temporary file. 41 | This file can be used to diagnose errors if the process failed. 42 | \item \code{...} passed to the \code{initialize} method of \link[processx:process]{processx::process}. 43 | } 44 | } 45 | 46 | \examples{ 47 | dir.create(tmp <- tempfile()) 48 | write.table(iris, file = file.path(tmp, "iris.ssv")) 49 | zipfile <- tempfile(fileext = ".zip") 50 | zp <- zip_process()$new(zipfile, tmp) 51 | zp$wait() 52 | zp$get_exit_status() 53 | zip_list(zipfile) 54 | } 55 | -------------------------------------------------------------------------------- /src/Makevars: -------------------------------------------------------------------------------- 1 | 2 | PKG_CFLAGS = $(C_VISIBILITY) 3 | 4 | OBJECTS = init.o miniz.o rzip.o zip.o unixutils.o 5 | 6 | .PHONY: all clean 7 | 8 | all: tools/cmdzip tools/cmdunzip $(SHLIB) 9 | 10 | tools/cmdzip: miniz.c zip.c unixutils.c tools/cmdzip.c 11 | $(CC) $(CFLAGS) miniz.c zip.c unixutils.c tools/cmdzip.c -o tools/cmdzip 12 | 13 | tools/cmdunzip: miniz.c zip.c unixutils.c tools/cmdunzip.c 14 | $(CC) $(CFLAGS) miniz.c zip.c unixutils.c tools/cmdunzip.c -o tools/cmdunzip 15 | 16 | clean: 17 | rm -rf $(SHLIB) $(OBJECTS) \ 18 | tools/cmdunzip tools/cmdunzip.exe tools/cmdunzip.dSYM \ 19 | tools/cmdzip tools/cmdzip.exe tools/cmdzip.dSYM 20 | -------------------------------------------------------------------------------- /src/Makevars.win: -------------------------------------------------------------------------------- 1 | 2 | OBJECTS = init.o miniz.o rzip.o zip.o winutils.o 3 | 4 | .PHONY: all clean 5 | 6 | all: tools/cmdzip.exe tools/cmdunzip.exe tools/zip.exe $(SHLIB) 7 | 8 | tools/cmdzip.exe: miniz.c zip.c winutils.c tools/cmdzip.c 9 | $(CC) $(CFLAGS) -DCMDZIP miniz.c zip.c winutils.c tools/cmdzip.c \ 10 | -municode -o tools/cmdzip.exe 11 | 12 | tools/cmdunzip.exe: miniz.c zip.c winutils.c tools/cmdunzip.c 13 | $(CC) $(CFLAGS) -DCMDZIP miniz.c zip.c winutils.c tools/cmdunzip.c \ 14 | -municode -o tools/cmdunzip.exe 15 | 16 | tools/zip.exe: 17 | "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/getzipexe.R" 18 | 19 | clean: 20 | rm -rf $(SHLIB) $(OBJECTS) \ 21 | tools/cmdunzip tools/cmdunzip.exe tools/cmdunzip.dSYM \ 22 | tools/cmdzip tools/cmdzip.exe tools/cmdzip.dSYM \ 23 | tools/zip.exe 24 | -------------------------------------------------------------------------------- /src/init.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include // for NULL 5 | #include 6 | #include 7 | #define r_export attribute_visible extern 8 | 9 | /* .Call calls */ 10 | extern SEXP R_zip_list(SEXP); 11 | extern SEXP R_zip_zip(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); 12 | extern SEXP R_zip_unzip(SEXP, SEXP, SEXP, SEXP, SEXP); 13 | extern SEXP R_make_big_file(SEXP, SEXP); 14 | extern SEXP R_inflate(SEXP, SEXP, SEXP); 15 | extern SEXP R_deflate(SEXP, SEXP, SEXP, SEXP); 16 | 17 | static const R_CallMethodDef CallEntries[] = { 18 | { "R_zip_list", (DL_FUNC) &R_zip_list, 1 }, 19 | { "R_zip_zip", (DL_FUNC) &R_zip_zip, 7 }, 20 | { "R_zip_unzip", (DL_FUNC) &R_zip_unzip, 5 }, 21 | { "R_make_big_file", (DL_FUNC) &R_make_big_file, 2 }, 22 | { "R_inflate", (DL_FUNC) &R_inflate, 3 }, 23 | { "R_deflate", (DL_FUNC) &R_deflate, 4 }, 24 | { NULL, NULL, 0 } 25 | }; 26 | 27 | r_export void R_init_zip(DllInfo *dll) { 28 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 29 | R_useDynamicSymbols(dll, FALSE); 30 | R_forceSymbols(dll, TRUE); 31 | } 32 | -------------------------------------------------------------------------------- /src/install.libs.R: -------------------------------------------------------------------------------- 1 | progs <- if (WINDOWS) { 2 | file.path("tools", c("cmdzip.exe", "cmdunzip.exe", "zip.exe")) 3 | } else { 4 | file.path("tools", c("cmdzip", "cmdunzip")) 5 | } 6 | 7 | dest <- file.path(R_PACKAGE_DIR, paste0("bin", R_ARCH)) 8 | dir.create(dest, recursive = TRUE, showWarnings = FALSE) 9 | file.copy(progs, dest, overwrite = TRUE) 10 | 11 | files <- Sys.glob(paste0("*", SHLIB_EXT)) 12 | dest <- file.path(R_PACKAGE_DIR, paste0('libs', R_ARCH)) 13 | dir.create(dest, recursive = TRUE, showWarnings = FALSE) 14 | file.copy(files, dest, overwrite = TRUE) 15 | if (file.exists("symbols.rds")) { 16 | file.copy("symbols.rds", dest, overwrite = TRUE) 17 | } 18 | -------------------------------------------------------------------------------- /src/rzip.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include /* _mkdir */ 10 | #include 11 | #endif 12 | 13 | #include 14 | 15 | #include "miniz.h" 16 | #include "zip.h" 17 | 18 | #ifndef S_IFLINK 19 | #define S_IFLNK 0120000 /* [XSI] symbolic link */ 20 | #endif 21 | #ifndef S_IFSOCK 22 | #define S_IFSOCK 0140000 /* [XSI] socket */ 23 | #endif 24 | 25 | #ifndef S_ISLNK 26 | #define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */ 27 | #endif 28 | #ifndef S_ISSOCK 29 | #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */ 30 | #endif 31 | 32 | SEXP R_zip_list(SEXP zipfile) { 33 | const char *czipfile = CHAR(STRING_ELT(zipfile, 0)); 34 | size_t num_files; 35 | unsigned int i; 36 | SEXP result = R_NilValue; 37 | mz_bool status; 38 | mz_zip_archive zip_archive; 39 | 40 | FILE *fh; 41 | wchar_t *uzipfile = NULL; 42 | 43 | #ifdef _WIN32 44 | #define R_ZIP_FSEEK64 _fseeki64 45 | #define R_ZIP_FTELL64 _ftelli64 46 | size_t uzipfile_len = 0; 47 | if (zip__utf8_to_utf16(czipfile, &uzipfile, &uzipfile_len)) { 48 | if (uzipfile) free(uzipfile); 49 | error("Cannot convert zip file name to unicode"); 50 | } 51 | fh = zip_long_wfopen(uzipfile, L"rb"); 52 | #else 53 | #define R_ZIP_FSEEK64 fseek 54 | #define R_ZIP_FTELL64 ftell 55 | fh = fopen(czipfile, "rb"); 56 | #endif 57 | 58 | if (fh == NULL) { 59 | if (uzipfile) free(uzipfile); 60 | error("Cannot open zip file `%s`", czipfile); 61 | } 62 | 63 | R_ZIP_FSEEK64(fh, 0, SEEK_END); 64 | mz_uint64 file_size = R_ZIP_FTELL64(fh); 65 | R_ZIP_FSEEK64(fh, 0, SEEK_SET); 66 | 67 | memset(&zip_archive, 0, sizeof(zip_archive)); 68 | status = mz_zip_reader_init_cfile(&zip_archive, fh, file_size, 0); 69 | if (!status) { 70 | fclose(fh); 71 | free(uzipfile); 72 | error("Cannot open zip file `%s`", czipfile); 73 | } 74 | 75 | num_files = mz_zip_reader_get_num_files(&zip_archive); 76 | result = PROTECT(allocVector(VECSXP, 8)); 77 | SET_VECTOR_ELT(result, 0, allocVector(STRSXP, num_files)); 78 | SET_VECTOR_ELT(result, 1, allocVector(REALSXP, num_files)); 79 | SET_VECTOR_ELT(result, 2, allocVector(REALSXP, num_files)); 80 | SET_VECTOR_ELT(result, 3, allocVector(INTSXP, num_files)); 81 | SET_VECTOR_ELT(result, 4, allocVector(INTSXP, num_files)); 82 | SET_VECTOR_ELT(result, 5, allocVector(INTSXP, num_files)); 83 | SET_VECTOR_ELT(result, 6, allocVector(REALSXP, num_files)); 84 | SET_VECTOR_ELT(result, 7, allocVector(INTSXP, num_files)); 85 | 86 | for (i = 0; i < num_files; i++) { 87 | mz_zip_archive_file_stat file_stat; 88 | mode_t mode; 89 | status = mz_zip_reader_file_stat (&zip_archive, i, &file_stat); 90 | if (!status) goto cleanup; 91 | 92 | SET_STRING_ELT(VECTOR_ELT(result, 0), i, mkChar(file_stat.m_filename)); 93 | REAL(VECTOR_ELT(result, 1))[i] = file_stat.m_comp_size; 94 | REAL(VECTOR_ELT(result, 2))[i] = file_stat.m_uncomp_size; 95 | INTEGER(VECTOR_ELT(result, 3))[i] = (int) file_stat.m_time; 96 | zip_get_permissions(&file_stat, &mode); 97 | INTEGER(VECTOR_ELT(result, 4))[i] = (int) mode; 98 | INTEGER(VECTOR_ELT(result, 5))[i] = (int) file_stat.m_crc32; 99 | REAL(VECTOR_ELT(result, 6))[i] = (double) file_stat.m_local_header_ofs; 100 | INTEGER(VECTOR_ELT(result, 7))[i] = 0; 101 | mz_uint32 attr = file_stat.m_external_attr >> 16; 102 | if (S_ISBLK(attr)) { 103 | INTEGER(VECTOR_ELT(result, 7))[i] = 1; 104 | } else if (S_ISCHR(attr)) { 105 | INTEGER(VECTOR_ELT(result, 7))[i] = 2; 106 | } else if (S_ISDIR(attr)) { 107 | INTEGER(VECTOR_ELT(result, 7))[i] = 3; 108 | } else if (S_ISFIFO(attr)) { 109 | INTEGER(VECTOR_ELT(result, 7))[i] = 4; 110 | } else if (S_ISREG(attr)) { 111 | INTEGER(VECTOR_ELT(result, 7))[i] = 0; 112 | } else if (S_ISLNK(attr)) { 113 | INTEGER(VECTOR_ELT(result, 7))[i] = 5; 114 | } else if (S_ISSOCK(attr)) { 115 | INTEGER(VECTOR_ELT(result, 7))[i] = 6; 116 | } 117 | } 118 | 119 | fclose(fh); 120 | free(uzipfile); 121 | mz_zip_reader_end(&zip_archive); 122 | UNPROTECT(1); 123 | return result; 124 | 125 | cleanup: 126 | fclose(fh); 127 | mz_zip_reader_end(&zip_archive); 128 | error("Cannot list zip entries, corrupt zip file?"); 129 | return result; 130 | } 131 | 132 | void R_zip_error_handler(const char *reason, const char *file, 133 | int line, int zip_errno, int eno) { 134 | error("zip error: %s in file %s:%i", reason, file, line); 135 | } 136 | 137 | SEXP R_zip_zip(SEXP zipfile, SEXP keys, SEXP files, SEXP dirs, SEXP mtime, 138 | SEXP compression_level, SEXP append) { 139 | 140 | const char *czipfile = CHAR(STRING_ELT(zipfile, 0)); 141 | const char **ckeys = 0, **cfiles = 0; 142 | int *cdirs = INTEGER(dirs); 143 | double *cmtimes = REAL(mtime); 144 | int ccompression_level = INTEGER(compression_level)[0]; 145 | int cappend = LOGICAL(append)[0]; 146 | int i, n = LENGTH(keys); 147 | 148 | /* The reason we allocate n+1 here is that otherwise R_alloc will 149 | return a NULL pointer for n == 0, and zip_unzip interprets that 150 | as extracting the whole archive. */ 151 | 152 | ckeys = (const char **) R_alloc(n + 1, sizeof(char*)); 153 | cfiles = (const char **) R_alloc(n + 1, sizeof(char*)); 154 | for (i = 0; i < n; i++) { 155 | ckeys [i] = CHAR(STRING_ELT(keys, i)); 156 | cfiles[i] = CHAR(STRING_ELT(files, i)); 157 | } 158 | 159 | zip_set_error_handler(R_zip_error_handler); 160 | 161 | zip_zip(czipfile, n, ckeys, cfiles, cdirs, cmtimes, ccompression_level, 162 | cappend); 163 | 164 | return R_NilValue; 165 | } 166 | 167 | SEXP R_zip_unzip(SEXP zipfile, SEXP files, SEXP overwrite, SEXP junkpaths, 168 | SEXP exdir) { 169 | 170 | const char *czipfile = CHAR(STRING_ELT(zipfile, 0)); 171 | int coverwrite = LOGICAL(overwrite)[0]; 172 | int cjunkpaths = LOGICAL(junkpaths)[0]; 173 | const char *cexdir = CHAR(STRING_ELT(exdir, 0)); 174 | int allfiles = isNull(files); 175 | int i, n = allfiles ? 0 : LENGTH(files); 176 | const char **cfiles = 0; 177 | 178 | if (!isNull(files)) { 179 | /* The reason we allocate n+1 here is that otherwise R_alloc will 180 | return a NULL pointer for n == 0, and zip_unzip interprets that 181 | as extracting the whole archive. */ 182 | cfiles = (const char**) R_alloc(n + 1, sizeof(char*)); 183 | for (i = 0; i < n; i++) cfiles[i] = CHAR(STRING_ELT(files, i)); 184 | } 185 | 186 | zip_set_error_handler(R_zip_error_handler); 187 | zip_unzip(czipfile, cfiles, n, coverwrite, cjunkpaths, cexdir); 188 | 189 | return R_NilValue; 190 | } 191 | 192 | 193 | #ifdef __APPLE__ 194 | #include 195 | #include 196 | #endif 197 | 198 | 199 | #ifdef _WIN32 200 | 201 | int zip__utf8_to_utf16(const char* s, wchar_t** buffer, 202 | size_t *buffer_size); 203 | 204 | #endif 205 | 206 | SEXP R_make_big_file(SEXP filename, SEXP mb) { 207 | 208 | #ifdef _WIN32 209 | 210 | const char *cfilename = CHAR(STRING_ELT(filename, 0)); 211 | LARGE_INTEGER li; 212 | 213 | wchar_t *wfilename = NULL; 214 | size_t wfilename_size = 0; 215 | 216 | if (zip__utf8_to_utf16(cfilename, &wfilename, &wfilename_size)) { 217 | error("utf8 -> utf16 conversion"); 218 | } 219 | 220 | HANDLE h = CreateFileW( 221 | wfilename, 222 | GENERIC_WRITE, 223 | FILE_SHARE_DELETE, 224 | NULL, 225 | CREATE_NEW, 226 | FILE_ATTRIBUTE_NORMAL, 227 | NULL); 228 | if (h == INVALID_HANDLE_VALUE) { 229 | if (wfilename) free(wfilename); 230 | error("Cannot create big file"); 231 | } 232 | 233 | li.QuadPart = INTEGER(mb)[0] * 1024.0 * 1024.0; 234 | li.LowPart = SetFilePointer(h, li.LowPart, &li.HighPart, FILE_BEGIN); 235 | 236 | if (0xffffffff == li.LowPart && GetLastError() != NO_ERROR) { 237 | CloseHandle(h); 238 | if (wfilename) free(wfilename); 239 | error("Cannot create big file"); 240 | } 241 | 242 | if (!SetEndOfFile(h)) { 243 | CloseHandle(h); 244 | if (wfilename) free(wfilename); 245 | error("Cannot create big file"); 246 | } 247 | 248 | if (wfilename) free(wfilename); 249 | CloseHandle(h); 250 | 251 | #endif 252 | 253 | #ifdef __APPLE__ 254 | 255 | const char *cfilename = CHAR(STRING_ELT(filename, 0)); 256 | int fd = open(cfilename, O_WRONLY | O_CREAT); 257 | double sz = INTEGER(mb)[0] * 1024.0 * 1024.0; 258 | fstore_t store = { F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, (off_t) sz }; 259 | // Try to get a continous chunk of disk space 260 | int ret = fcntl(fd, F_PREALLOCATE, &store); 261 | if (-1 == ret) { 262 | // OK, perhaps we are too fragmented, allocate non-continuous 263 | store.fst_flags = F_ALLOCATEALL; 264 | ret = fcntl(fd, F_PREALLOCATE, &store); 265 | if (-1 == ret) error("Cannot create big file"); 266 | } 267 | 268 | if (ftruncate(fd, (off_t) sz)) { 269 | close(fd); 270 | error("Cannot create big file"); 271 | } 272 | 273 | close(fd); 274 | 275 | #endif 276 | 277 | #ifndef _WIN32 278 | #ifndef __APPLE__ 279 | error("cannot create big file (only implemented for windows and macos"); 280 | #endif 281 | #endif 282 | 283 | return R_NilValue; 284 | } 285 | 286 | SEXP R_inflate(SEXP buffer, SEXP pos, SEXP size) { 287 | int status; 288 | mz_stream stream; 289 | size_t cpos = INTEGER(pos)[0] - 1; 290 | size_t csize; 291 | const char *nms[] = { "output", "bytes_read", "bytes_written", "" }; 292 | SEXP result = PROTECT(Rf_mkNamed(VECSXP, nms)); 293 | if (isNull(size)) { 294 | csize = (LENGTH(buffer) - cpos) * 2; 295 | } else { 296 | csize = INTEGER(size)[0]; 297 | } 298 | if (csize < 10) csize = 10; 299 | SEXP output = PROTECT(allocVector(RAWSXP, csize)); 300 | 301 | memset(&stream, 0, sizeof(stream)); 302 | stream.next_in = RAW(buffer) + cpos; 303 | stream.avail_in = LENGTH(buffer) - cpos; 304 | stream.next_out = RAW(output); 305 | stream.avail_out = csize; 306 | 307 | status = mz_inflateInit2(&stream, MZ_DEFAULT_WINDOW_BITS); 308 | 309 | if (status != 0) { 310 | error("Failed to initiaalize decompressor"); 311 | } 312 | 313 | for (;;) { 314 | status = mz_inflate(&stream, MZ_SYNC_FLUSH); 315 | 316 | if (status == MZ_STREAM_END) { 317 | mz_inflateEnd(&stream); 318 | break; 319 | } else if (status == MZ_STREAM_ERROR) { 320 | mz_inflateEnd(&stream); 321 | error("Input stream is bogus"); 322 | } else if (status == MZ_DATA_ERROR) { 323 | mz_deflateEnd(&stream); 324 | error("Input data is invalid"); 325 | } 326 | 327 | if ((status == MZ_OK || status == MZ_BUF_ERROR) && 328 | stream.avail_out == 0) { 329 | int newsize = csize * 1.5; 330 | output = Rf_lengthgets(output, newsize); 331 | UNPROTECT(1); 332 | PROTECT(output); 333 | stream.next_out = RAW(output) + csize; 334 | stream.avail_out = newsize - csize; 335 | csize = newsize; 336 | continue; 337 | } 338 | 339 | if (status == MZ_OK) { 340 | mz_inflateEnd(&stream); 341 | break; 342 | } 343 | 344 | if (status != MZ_OK) { 345 | mz_inflateEnd(&stream); 346 | error("Failed to inflate data"); 347 | } 348 | } 349 | 350 | output = PROTECT(Rf_lengthgets(output, stream.total_out)); 351 | 352 | SET_VECTOR_ELT(result, 0, output); 353 | SET_VECTOR_ELT(result, 1, Rf_ScalarInteger(stream.total_in)); 354 | SET_VECTOR_ELT(result, 2, Rf_ScalarInteger(stream.total_out)); 355 | UNPROTECT(3); 356 | return result; 357 | } 358 | 359 | SEXP R_deflate(SEXP buffer, SEXP level, SEXP pos, SEXP size) { 360 | int clevel = INTEGER(level)[0]; 361 | int status; 362 | mz_stream stream; 363 | size_t cpos = INTEGER(pos)[0] - 1; 364 | size_t csize; 365 | const char *nms[] = { "output", "bytes_read", "bytes_written", "" }; 366 | SEXP result = PROTECT(Rf_mkNamed(VECSXP, nms)); 367 | 368 | if (isNull(size)) { 369 | csize = (LENGTH(buffer) - cpos); 370 | } else { 371 | csize = INTEGER(size)[0]; 372 | } 373 | if (csize < 10) csize = 10; 374 | SEXP output = PROTECT(allocVector(RAWSXP, csize)); 375 | 376 | memset(&stream, 0, sizeof(stream)); 377 | stream.next_in = RAW(buffer) + cpos; 378 | stream.avail_in = LENGTH(buffer) - cpos; 379 | stream.next_out = RAW(output); 380 | stream.avail_out = csize; 381 | 382 | status = mz_deflateInit2( 383 | &stream, 384 | clevel, 385 | MZ_DEFLATED, 386 | MZ_DEFAULT_WINDOW_BITS, 387 | /* mem_level= */ 9, 388 | MZ_DEFAULT_STRATEGY 389 | ); 390 | 391 | if (status != 0) { 392 | error("Failed to initiaalize compressor"); 393 | } 394 | 395 | for (;;) { 396 | status = mz_deflate(&stream, MZ_SYNC_FLUSH); 397 | 398 | if (status == MZ_STREAM_END) { 399 | mz_deflateEnd(&stream); 400 | break; 401 | } else if (status == MZ_STREAM_ERROR) { 402 | mz_deflateEnd(&stream); 403 | error("Input stream is bogus"); 404 | } else if (status == MZ_DATA_ERROR) { 405 | mz_deflateEnd(&stream); 406 | error("Input data is invalid"); 407 | } 408 | 409 | if ((status == MZ_OK || status == MZ_BUF_ERROR) && 410 | stream.avail_out == 0) { 411 | int newsize = csize * 1.5; 412 | output = Rf_lengthgets(output, newsize); 413 | UNPROTECT(1); 414 | PROTECT(output); 415 | stream.next_out = RAW(output) + csize; 416 | stream.avail_out = newsize - csize; 417 | csize = newsize; 418 | continue; 419 | } 420 | 421 | if (status == MZ_OK) { 422 | mz_deflateEnd(&stream); 423 | break; 424 | } 425 | 426 | if (status != MZ_OK) { 427 | mz_deflateEnd(&stream); 428 | error("Failed to deflate data"); 429 | } 430 | } 431 | 432 | output = PROTECT(Rf_lengthgets(output, stream.total_out)); 433 | 434 | SET_VECTOR_ELT(result, 0, output); 435 | SET_VECTOR_ELT(result, 1, Rf_ScalarInteger(stream.total_in)); 436 | SET_VECTOR_ELT(result, 2, Rf_ScalarInteger(stream.total_out)); 437 | UNPROTECT(3); 438 | return result; 439 | } 440 | -------------------------------------------------------------------------------- /src/tools/cmdunzip.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "../zip.h" 5 | 6 | #ifdef _WIN32 7 | #define CHAR wchar_t 8 | #define MAIN wmain 9 | #define CFMT "%ls" 10 | #else 11 | #define CHAR char 12 | #define MAIN main 13 | #define CFMT "%s" 14 | #endif 15 | 16 | #define ZERROR(x) while (1) { retval = (x); goto cleanup; } 17 | 18 | static void cmd_zip_error_handler(const char *reason, const char *file, 19 | int line, int zip_errno, int eno) { 20 | fprintf(stderr, "zip error: `%s` in file `%s:%i`\n", reason, file, line); 21 | if (eno < 0) { 22 | eno = - eno; 23 | } else if (eno == 0) { 24 | eno = 1; 25 | } 26 | exit(eno); 27 | } 28 | 29 | int MAIN(int argc, CHAR* argv[]) { 30 | int retval = 0; 31 | if (argc != 3) { 32 | fprintf(stderr, "Usage: " CFMT " zip-file target-dir\n", argv[0]); 33 | return 1; 34 | } 35 | 36 | zip_set_error_handler(cmd_zip_error_handler); 37 | 38 | #ifdef _WIN32 39 | char *zipfile = 0; 40 | size_t zipfile_len = 0; 41 | char *exdir = 0; 42 | size_t exdir_len = 0; 43 | if (zip__utf16_to_utf8(argv[1], &zipfile, &zipfile_len)) ZERROR(2); 44 | if (zip__utf16_to_utf8(argv[2], &exdir, &exdir_len)) ZERROR(3); 45 | 46 | #else 47 | char *zipfile = argv[1]; 48 | char *exdir = argv[2]; 49 | #endif 50 | 51 | zip_unzip(zipfile, /* cfiles= */ 0, /* num_files= */ 0, 52 | /* coverwrite= */ 1, /* cjunkpaths= */ 0, /* exdir= */ exdir); 53 | 54 | #ifdef _WIN32 55 | cleanup: 56 | #endif 57 | 58 | if (retval != 0) { 59 | fprintf(stderr, "Failed to create zip archive " CFMT, argv[1]); 60 | } 61 | 62 | return retval; 63 | } 64 | -------------------------------------------------------------------------------- /src/tools/cmdzip.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "../zip.h" 6 | 7 | #ifdef _WIN32 8 | #define CHAR wchar_t 9 | #define MAIN wmain 10 | #define CFMT "%ls" 11 | #else 12 | #define CHAR char 13 | #define MAIN main 14 | #define CFMT "%s" 15 | #endif 16 | 17 | #define ZERROR(x) while (1) { retval = (x); goto cleanup; } 18 | 19 | void cmd_zip_error_handler(const char *reason, const char *file, 20 | int line, int zip_errno, int eno) { 21 | fprintf(stderr, "zip error: `%s` in file `%s:%i`\n", reason, file, line); 22 | if (eno < 0) { 23 | eno = - eno; 24 | } else if (eno == 0) { 25 | eno = 1; 26 | } 27 | exit(eno); 28 | } 29 | 30 | int MAIN(int argc, CHAR* argv[]) { 31 | int i, num_files = 0; 32 | int ckeysbytes, cfilesbytes; 33 | char *keysbuffer = 0, *filesbuffer = 0; 34 | const char **ckeys = 0, **cfiles = 0; 35 | int *cdirs = 0; 36 | double *ctimes = 0; 37 | int fd = -1; 38 | int retval = 0; 39 | char *ptr; 40 | 41 | if (argc != 3) { 42 | fprintf(stderr, "Usage: " CFMT " zip-file input-file\n", argv[0]); 43 | return 1; 44 | } 45 | 46 | #ifdef _WIN32 47 | fd = _wopen(argv[2], O_RDONLY | O_BINARY); 48 | #else 49 | fd = open(argv[2], O_RDONLY); 50 | #endif 51 | if (fd == -1) ZERROR(1); 52 | 53 | /* Number of keys */ 54 | if (read(fd, &num_files, sizeof(num_files)) != sizeof(num_files)) { 55 | ZERROR(2); 56 | } 57 | 58 | ckeys = calloc(num_files, sizeof(const char*)); 59 | cfiles = calloc(num_files, sizeof(const char*)); 60 | cdirs = calloc(num_files, sizeof(int)); 61 | ctimes = calloc(num_files, sizeof(double)); 62 | 63 | if (!ckeys || !cfiles || !cdirs || !ctimes) ZERROR(3); 64 | 65 | /* keys first the total size of the buffer in bytes */ 66 | if (read(fd, &ckeysbytes, sizeof(ckeysbytes)) != sizeof(ckeysbytes)) { 67 | ZERROR(4); 68 | } 69 | keysbuffer = malloc(ckeysbytes); 70 | if (!keysbuffer) ZERROR(5); 71 | if (read(fd, keysbuffer, ckeysbytes) != ckeysbytes) ZERROR(6); 72 | for (i = 0, ptr = keysbuffer; i < num_files; ptr++, i++) { 73 | ckeys[i] = ptr; 74 | while (*ptr != '\0') ++ptr; 75 | } 76 | 77 | /* file names next */ 78 | if (read(fd, &cfilesbytes, sizeof(cfilesbytes)) != sizeof(cfilesbytes)) { 79 | ZERROR(4); 80 | } 81 | filesbuffer = malloc(cfilesbytes); 82 | if (!filesbuffer) ZERROR(7); 83 | if (read(fd, filesbuffer, cfilesbytes) != cfilesbytes) ZERROR(8); 84 | for (i = 0, ptr = filesbuffer; i < num_files; ptr++, i++) { 85 | cfiles[i] = ptr; 86 | while (*ptr != '\0') ++ptr; 87 | } 88 | 89 | /* dirs */ 90 | if (read(fd, cdirs, num_files * sizeof(int)) != num_files * sizeof(int)) { 91 | ZERROR(9); 92 | } 93 | 94 | /* mtimes */ 95 | if (read(fd, ctimes, num_files * sizeof(double)) != 96 | num_files * sizeof(double)) { 97 | ZERROR(10); 98 | } 99 | 100 | zip_set_error_handler(cmd_zip_error_handler); 101 | 102 | #ifdef _WIN32 103 | char *fn = 0; 104 | size_t fnlen = 0; 105 | if (zip__utf16_to_utf8(argv[1], &fn, &fnlen)) ZERROR(11); 106 | #else 107 | char *fn = argv[1]; 108 | #endif 109 | 110 | if (zip_zip(fn, num_files, ckeys, cfiles, cdirs, ctimes, 111 | /* compression_level= */ 9, /* cappend= */ 0)) { 112 | ZERROR(11); 113 | } 114 | 115 | cleanup: 116 | /* No need to clean up, we are exiting */ 117 | 118 | if (retval != 0) { 119 | fprintf(stderr, "Failed to create zip archive " CFMT, argv[1]); 120 | } 121 | 122 | return retval; 123 | } 124 | -------------------------------------------------------------------------------- /src/unixutils.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "zip.h" 9 | 10 | /* -------------------------------------------------------------- */ 11 | 12 | FILE *zip_open_utf8(const char *filename, const char *mode, 13 | char **buffer, size_t *buffer_size) { 14 | FILE *fh = fopen(filename, mode); 15 | return fh; 16 | } 17 | 18 | 19 | int zip_str_file_path(const char *cexdir, const char *key, 20 | char **buffer, size_t *buffer_size, 21 | int cjunkpaths) { 22 | 23 | size_t len1 = strlen(cexdir); 24 | size_t need_size, len2; 25 | char *newbuffer; 26 | 27 | if (cjunkpaths) { 28 | char *base = strrchr(key, '/'); 29 | if (base) key = base; 30 | } 31 | 32 | len2 = strlen(key); 33 | need_size = len1 + len2 + 2; 34 | 35 | if (*buffer_size < need_size) { 36 | newbuffer = realloc((void*) *buffer, need_size); 37 | if (!newbuffer) return 1; 38 | 39 | *buffer = newbuffer; 40 | *buffer_size = need_size; 41 | } 42 | 43 | strcpy(*buffer, cexdir); 44 | (*buffer)[len1] = '/'; 45 | strcpy(*buffer + len1 + 1, key); 46 | 47 | return 0; 48 | } 49 | 50 | int zip_mkdirp(char *path, int complete) { 51 | char *p; 52 | int status; 53 | 54 | errno = 0; 55 | 56 | /* Iterate the string */ 57 | for (p = path + 1; *p; p++) { 58 | if (*p == '/') { 59 | *p = '\0'; 60 | status = mkdir(path, S_IRWXU); 61 | *p = '/'; 62 | if (status && errno != EEXIST) { 63 | return 1; 64 | } 65 | } 66 | } 67 | 68 | if (complete) { 69 | status = mkdir(path, S_IRWXU); 70 | if ((status && errno != EEXIST)) return 1; 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | int zip_file_exists(char *filename) { 77 | struct stat st; 78 | return ! stat(filename, &st); 79 | } 80 | 81 | int zip_set_mtime(const char *filename, time_t mtime) { 82 | struct timeval times[2]; 83 | times[0].tv_sec = times[1].tv_sec = mtime; 84 | times[0].tv_usec = times[1].tv_usec = 0; 85 | return utimes(filename, times); 86 | } 87 | 88 | int zip_file_size(FILE *fh, mz_uint64 *size) { 89 | if (fseek(fh, 0, SEEK_END)) return 1; 90 | *size = ftello(fh); 91 | if (*size == -1) return 2; 92 | if (fseek(fh, 0, SEEK_SET)) return 3; 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /src/winutils.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "zip.h" 7 | 8 | /* -------------------------------------------------------------- */ 9 | 10 | int zip__utf8_to_utf16(const char* s, wchar_t** buffer, 11 | size_t *buffer_size) { 12 | int ws_len, r; 13 | 14 | ws_len = MultiByteToWideChar( 15 | /* CodePage = */ CP_UTF8, 16 | /* dwFlags = */ 0, 17 | /* lpMultiByteStr = */ s, 18 | /* cbMultiByte = */ -1, 19 | /* lpWideCharStr = */ NULL, 20 | /* cchWideChar = */ 0); 21 | 22 | if (ws_len <= 0) { return GetLastError(); } 23 | 24 | if (*buffer == NULL) { 25 | /* Let's allocated something bigger, so no need to grow much */ 26 | *buffer_size = ws_len > 255 ? ws_len : 255; 27 | *buffer = (wchar_t*) calloc(*buffer_size, sizeof(wchar_t)); 28 | } else if (ws_len > *buffer_size) { 29 | *buffer_size = ws_len; 30 | *buffer = (wchar_t*) realloc(*buffer, ws_len * sizeof(wchar_t)); 31 | } 32 | if (*buffer == NULL) { return ERROR_OUTOFMEMORY; } 33 | 34 | r = MultiByteToWideChar( 35 | /* CodePage = */ CP_UTF8, 36 | /* dwFlags = */ 0, 37 | /* lpMultiByteStr = */ s, 38 | /* cbMultiBytes = */ -1, 39 | /* lpWideCharStr = */ *buffer, 40 | /* cchWideChar = */ ws_len); 41 | 42 | if (r != ws_len) { return GetLastError(); } 43 | 44 | return 0; 45 | } 46 | 47 | int zip__utf16_to_utf8(const wchar_t *ws, char** buffer, size_t *buffer_size) { 48 | 49 | int slen, r; 50 | 51 | slen = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); 52 | 53 | if (slen <= 0) { return GetLastError(); } 54 | 55 | if (*buffer == NULL) { 56 | /* Let's allocated something bigger, so no need to grow much */ 57 | *buffer_size = slen > 255 ? slen : 255; 58 | *buffer = (char*) calloc(*buffer_size, sizeof(char)); 59 | } else if (slen > *buffer_size) { 60 | *buffer_size = slen; 61 | *buffer = (char*) realloc(*buffer, slen * sizeof(char)); 62 | } 63 | if (*buffer == NULL) { return ERROR_OUTOFMEMORY; } 64 | 65 | r = WideCharToMultiByte(CP_UTF8, 0, ws, -1, *buffer, slen, NULL, NULL); 66 | 67 | if (r != slen) { return GetLastError(); } 68 | 69 | return 0; 70 | } 71 | 72 | FILE *zip_open_utf8(const char *filename, const wchar_t *mode, 73 | wchar_t **buffer, size_t *buffer_size) { 74 | int ret = zip__utf8_to_utf16(filename, buffer, buffer_size); 75 | if (ret) return NULL; 76 | 77 | FILE *fh = _wfopen(*buffer, mode); 78 | return fh; 79 | } 80 | 81 | 82 | int zip_str_file_path(const char *cexdir, const char *key, 83 | zip_char_t **buffer, size_t *buffer_size, 84 | int cjunkpaths) { 85 | 86 | int need_size, len1, len2, i; 87 | wchar_t *newbuffer; 88 | 89 | if (cjunkpaths) { 90 | char *base = strrchr(key, '/'); 91 | if (base) key = base; 92 | } else if (key[0] == '/') { 93 | key = key + 1; 94 | } 95 | 96 | len1 = MultiByteToWideChar( 97 | /* CodePage = */ CP_UTF8, 98 | /* dwFlags = */ 0, 99 | /* lpMultiByteStr = */ cexdir, 100 | /* cbMultiByte = */ -1, 101 | /* lpWideCharStr = */ NULL, 102 | /* cchWideChar = */ 0 103 | ); 104 | len2 = MultiByteToWideChar( 105 | /* CodePage = */ CP_UTF8, 106 | /* dwFlags = */ 0, 107 | /* lpMultiByteStr = */ key, 108 | /* cbMultiByte = */ -1, 109 | /* lpWideCharStr = */ NULL, 110 | /* cchWideChar = */ 0 111 | ); 112 | /* No need to +1 for '\\' because both have a terminating zero. */ 113 | int offset = cjunkpaths ? 0 : 4; 114 | need_size = len1 + len2 + offset; 115 | 116 | if (*buffer == NULL) { 117 | *buffer_size = need_size > 256 ? need_size : 256; 118 | *buffer = calloc(*buffer_size, sizeof(zip_char_t)); 119 | if (!*buffer) return 1; 120 | 121 | } else if (*buffer_size < need_size) { 122 | newbuffer = realloc((void*) *buffer, need_size * sizeof(zip_char_t)); 123 | if (!newbuffer) return 1; 124 | 125 | *buffer = newbuffer; 126 | *buffer_size = need_size; 127 | } 128 | 129 | /* work around path length limitations */ 130 | if (!cjunkpaths) { 131 | (*buffer)[0] = L'\\'; 132 | (*buffer)[1] = L'\\'; 133 | (*buffer)[2] = L'?'; 134 | (*buffer)[3] = L'\\'; 135 | } 136 | 137 | len1 = MultiByteToWideChar( 138 | /* CodePage = */ CP_UTF8, 139 | /* dwFlags = */ 0, 140 | /* lpMultiByteStr = */ cexdir, 141 | /* cbMultiByte = */ -1, 142 | /* lpWideCharStr = */ (*buffer) + offset, 143 | /* cchWideChar = */ len1 144 | ); 145 | len2 = MultiByteToWideChar( 146 | /* CodePage = */ CP_UTF8, 147 | /* dwFlags = */ 0, 148 | /* lpMultiByteStr = */ key, 149 | /* cbMultiByte = */ -1, 150 | /* lpWideCharStr = */ (*buffer) + len1 + offset, 151 | /* cchWideChar = */ len2 152 | ); 153 | 154 | (*buffer)[offset + len1 - 1] = L'\\'; 155 | 156 | for (i = offset; i < need_size; i++) { 157 | if ((*buffer)[i] == L'/') (*buffer)[i] = L'\\'; 158 | } 159 | 160 | return 0; 161 | } 162 | 163 | int zip_mkdirp(zip_char_t *path, int complete) { 164 | wchar_t *p = path; 165 | BOOL status; 166 | DWORD err = 0; 167 | 168 | /* Skip \\?\ if any */ 169 | if (*p == L'\\' && *(p+1) == L'\\' && *(p+2) == L'?' && 170 | *(p+3) == L'\\') { 171 | p += 4; 172 | } 173 | 174 | /* Skip host part of an UNC path */ 175 | if ((*p == L'\\' && *(p+1) == L'\\') || 176 | (*p == L'/' && *(p+1) == L'/' )) { 177 | p += 2; 178 | while (*p && *p != L'/' && *p != L'\\') p++; 179 | } 180 | 181 | /* Skip drive letter if any. */ 182 | if (*(p+1) == L':' && (*(p+2) == L'/' || (*(p+2)) == L'\\')) { 183 | p += 3; 184 | } 185 | 186 | /* Iterate the string */ 187 | for (; *p; p++) { 188 | if (*p == L'/' || *p == L'\\') { 189 | *p = L'\0'; 190 | status = CreateDirectoryW(path, NULL); 191 | *p = L'\\'; 192 | if (!status) { 193 | err = GetLastError(); 194 | if (err != ERROR_ALREADY_EXISTS) return 1; 195 | } 196 | } 197 | } 198 | 199 | if (complete) { 200 | p--; 201 | if (*p == L'/' || *p == L'\\') *p = L'\0'; 202 | status = CreateDirectoryW(path, NULL); 203 | if (*p == L'\0') *p = L'\\'; 204 | if (!status) { 205 | err = GetLastError(); 206 | if (err != ERROR_ALREADY_EXISTS) return 1; 207 | } 208 | } 209 | 210 | return 0; 211 | } 212 | 213 | int zip_file_exists(zip_char_t *filename) { 214 | DWORD attrib = GetFileAttributesW(filename); 215 | return attrib != INVALID_FILE_ATTRIBUTES; 216 | } 217 | 218 | int zip_set_mtime(const zip_char_t *filename, time_t mtime) { 219 | SYSTEMTIME st; 220 | FILETIME modft; 221 | struct tm *utctm; 222 | HANDLE hFile; 223 | time_t ftimei = (time_t) mtime; 224 | 225 | utctm = gmtime(&ftimei); 226 | if (!utctm) return 1; 227 | 228 | st.wYear = (WORD) utctm->tm_year + 1900; 229 | st.wMonth = (WORD) utctm->tm_mon + 1; 230 | st.wDayOfWeek = (WORD) utctm->tm_wday; 231 | st.wDay = (WORD) utctm->tm_mday; 232 | st.wHour = (WORD) utctm->tm_hour; 233 | st.wMinute = (WORD) utctm->tm_min; 234 | st.wSecond = (WORD) utctm->tm_sec; 235 | st.wMilliseconds = (WORD) 1000*(mtime - ftimei); 236 | if (!SystemTimeToFileTime(&st, &modft)) return 1; 237 | 238 | hFile = CreateFileW(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 239 | FILE_FLAG_BACKUP_SEMANTICS, NULL); 240 | 241 | if (hFile == INVALID_HANDLE_VALUE) return 1; 242 | int res = SetFileTime(hFile, NULL, NULL, &modft); 243 | CloseHandle(hFile); 244 | return res == 0; /* success is non-zero */ 245 | } 246 | 247 | int zip_file_size(FILE *fh, mz_uint64 *size) { 248 | if (_fseeki64(fh, 0, SEEK_END)) return 1; 249 | *size = _ftelli64(fh); 250 | if (*size == -1) return 2; 251 | if (_fseeki64(fh, 0, SEEK_SET)) return 3; 252 | return 0; 253 | } 254 | 255 | FILE* zip_long_wfopen(const wchar_t *filename, const wchar_t *mode) { 256 | FILE* res = _wfopen(filename, mode); 257 | return res; 258 | } 259 | -------------------------------------------------------------------------------- /src/zip.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef _WIN32 11 | #include /* _mkdir */ 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | #include "miniz.h" 18 | #include "zip.h" 19 | 20 | #define ZIP_ERROR_BUFFER_SIZE 1000 21 | static char zip_error_buffer[ZIP_ERROR_BUFFER_SIZE]; 22 | 23 | static const char *zip_error_strings[] = { 24 | /* 0 R_ZIP_ESUCCESS */ "Success", 25 | /* 1 R_ZIP_EOPEN */ "Cannot open zip file `%s` for reading", 26 | /* 2 R_ZIP_ENOMEM */ "Cannot extract zip file `%s`, out of memory", 27 | /* 3 R_ZIP_ENOENTRY */ "Cannot find file `%s` in zip archive `%s`", 28 | /* 4 R_ZIP_EBROKEN */ "Cannot extract zip archive `%s`", 29 | /* 5 R_ZIP_EBROKENENTRY */ "Cannot extract entry `%s` from archive `%s`", 30 | /* 6 R_ZIP_EOVERWRITE */ "Not overwriting `%s` when extracting `%s`", 31 | /* 7 R_ZIP_ECREATEDIR */ 32 | "Cannot create directory `%s` to extract `%s` from arghive `%s`", 33 | /* 8 R_ZIP_ESETPERM */ 34 | "Cannot set permissions for `%s` from archive `%s`", 35 | /* 9 R_ZIP_ESETMTIME */ 36 | "Failed to set mtime on `%s` while extracting `%s`", 37 | /*10 R_ZIP_EOPENWRITE */ "Cannot open zip file `%s` for writing", 38 | /*11 R_ZIP_EOPENWRITE */ "Cannot open zip file `%s` for appending", 39 | /*12 R_ZIP_EADDDIR */ "Cannot add directory `%s` to archive `%s`", 40 | /*13 R_ZIP_EADDFILE */ "Cannot add file `%s` to archive `%s`", 41 | /*14 R_ZIP_ESETZIPPERM */ 42 | "Cannot set permission on file `%s` in archive `%s`", 43 | /*15 R_ZIP_ECREATE */ "Could not create zip archive `%s`", 44 | /*16 R_ZIP_EOPENX */ "Cannot extract file `%s`", 45 | /*17 R_ZIP_FILESIZE */ "Cannot determine size of `%s`", 46 | /*18 R_ZIP_ECREATELINK */ "Cannot create symlink `%s` in archive `%s`" 47 | }; 48 | 49 | static zip_error_handler_t *zip_error_handler = 0; 50 | 51 | void zip_set_error_handler(zip_error_handler_t *handler) { 52 | zip_error_handler = handler; 53 | } 54 | 55 | void zip_error(int errorcode, const char *file, int line, ...) { 56 | va_list va; 57 | int err = errno; 58 | va_start(va, line); 59 | vsnprintf(zip_error_buffer, ZIP_ERROR_BUFFER_SIZE - 1, 60 | zip_error_strings[errorcode], va); 61 | zip_error_handler(zip_error_buffer, file, line, errorcode, err); 62 | } 63 | 64 | #define ZIP_ERROR(c, ...) do { \ 65 | zip_error((c), __FILE__, __LINE__, __VA_ARGS__); \ 66 | return 1; } while(0) 67 | 68 | int zip_set_permissions(mz_zip_archive *zip_archive, mz_uint file_index, 69 | const char *filename) { 70 | 71 | /* We only do this on Unix currently*/ 72 | #ifdef _WIN32 73 | return 0; 74 | #else 75 | mz_uint16 version_by; 76 | mz_uint32 external_attr; 77 | struct stat st; 78 | 79 | if (! mz_zip_get_version_made_by(zip_archive, file_index, &version_by) || 80 | ! mz_zip_get_external_attr(zip_archive, file_index, &external_attr)) { 81 | return 1; 82 | } 83 | 84 | if (stat(filename, &st)) return 1; 85 | 86 | version_by &= 0x00FF; 87 | version_by |= 3 << 8; 88 | 89 | /* We need to set the created-by version here, apparently, otherwise 90 | miniz will not set it properly.... */ 91 | version_by |= 23; 92 | 93 | external_attr &= 0x0000FFFF; 94 | external_attr |= (st.st_mode & 0777) << 16; 95 | 96 | if (! mz_zip_set_version_made_by(zip_archive, file_index, version_by) || 97 | ! mz_zip_set_external_attr(zip_archive, file_index, external_attr)) { 98 | return 1; 99 | } 100 | 101 | return 0; 102 | #endif 103 | } 104 | 105 | #ifdef _WIN32 106 | int zip_get_permissions(mz_zip_archive_file_stat *stat, mode_t *mode) { 107 | *mode = stat->m_is_directory ? 0700 : 0600; 108 | return 0; 109 | } 110 | #else 111 | int zip_get_permissions(mz_zip_archive_file_stat *stat, mode_t *mode) { 112 | mz_uint16 version_by = (stat->m_version_made_by >> 8) & 0xFF; 113 | mz_uint32 external_attr = (stat->m_external_attr >> 16) & 0xFFFF; 114 | 115 | /* If it is not made by Unix, or the permission field is zero, 116 | we ignore them. */ 117 | if (version_by != 3 || external_attr == 0) { 118 | *mode = stat->m_is_directory ? 0700 : 0600; 119 | return 1; 120 | } else { 121 | *mode = (mode_t) external_attr & 0777; 122 | } 123 | 124 | return 0; 125 | } 126 | #endif 127 | 128 | int zip_unzip(const char *czipfile, const char **cfiles, int num_files, 129 | int coverwrite, int cjunkpaths, const char *cexdir) { 130 | 131 | int allfiles = cfiles == NULL; 132 | int i, n; 133 | mz_zip_archive zip_archive; 134 | memset(&zip_archive, 0, sizeof(zip_archive)); 135 | 136 | zip_char_t *buffer = NULL; 137 | size_t buffer_size = 0; 138 | 139 | FILE *zfh = zip_open_utf8(czipfile, ZIP__READ, &buffer, &buffer_size); 140 | if (zfh == NULL) ZIP_ERROR(R_ZIP_EOPEN, czipfile); 141 | if (!mz_zip_reader_init_cfile(&zip_archive, zfh, 0, 0)) { 142 | if (buffer) free(buffer); 143 | fclose(zfh); 144 | ZIP_ERROR(R_ZIP_EOPEN, czipfile); 145 | } 146 | 147 | n = allfiles ? mz_zip_reader_get_num_files(&zip_archive) : num_files; 148 | 149 | for (i = 0; i < n; i++) { 150 | mz_uint32 idx = -1; 151 | const char *key = 0; 152 | mz_zip_archive_file_stat file_stat; 153 | 154 | if (allfiles) { 155 | idx = (mz_uint32) i; 156 | } else { 157 | key = cfiles[i]; 158 | if (!mz_zip_reader_locate_file_v2(&zip_archive, key, /* pComment= */ 0, 159 | /* flags= */ 0, &idx)) { 160 | mz_zip_reader_end(&zip_archive); 161 | if (buffer) free(buffer); 162 | fclose(zfh); 163 | ZIP_ERROR(R_ZIP_ENOENTRY, key, czipfile); 164 | } 165 | } 166 | 167 | if (! mz_zip_reader_file_stat(&zip_archive, idx, &file_stat)) { 168 | mz_zip_reader_end(&zip_archive); 169 | if (buffer) free(buffer); 170 | fclose(zfh); 171 | ZIP_ERROR(R_ZIP_EBROKEN, czipfile); 172 | } 173 | key = file_stat.m_filename; 174 | 175 | if (zip_str_file_path(cexdir, key, &buffer, &buffer_size, cjunkpaths)) { 176 | mz_zip_reader_end(&zip_archive); 177 | if (buffer) free(buffer); 178 | fclose(zfh); 179 | ZIP_ERROR(R_ZIP_ENOMEM, czipfile); 180 | } 181 | #ifndef WIN32 182 | mz_uint32 attr = file_stat.m_external_attr >> 16; 183 | #endif 184 | 185 | if (file_stat.m_is_directory) { 186 | if (! cjunkpaths && zip_mkdirp(buffer, 1)) { 187 | mz_zip_reader_end(&zip_archive); 188 | if (buffer) free(buffer); 189 | fclose(zfh); 190 | ZIP_ERROR(R_ZIP_EBROKENENTRY, key, czipfile); 191 | } 192 | 193 | #ifndef WIN32 194 | } else if (S_ISLNK(attr)) { 195 | char *tmpbuf = malloc(file_stat.m_uncomp_size + 1); // trailing 0 196 | if (!tmpbuf) { 197 | mz_zip_reader_end(&zip_archive); 198 | if (buffer) free(buffer); 199 | fclose(zfh); 200 | ZIP_ERROR(R_ZIP_ENOMEM, key, czipfile); 201 | } 202 | 203 | if (!mz_zip_reader_extract_to_mem( 204 | &zip_archive, 205 | idx, 206 | tmpbuf, 207 | file_stat.m_uncomp_size, 208 | 0 209 | )) { 210 | free(tmpbuf); 211 | mz_zip_reader_end(&zip_archive); 212 | if (buffer) free(buffer); 213 | fclose(zfh); 214 | ZIP_ERROR(R_ZIP_EBROKENENTRY, key, czipfile); 215 | } 216 | tmpbuf[file_stat.m_uncomp_size] = '\0'; 217 | if (symlink(tmpbuf, buffer)) { 218 | free(tmpbuf); 219 | mz_zip_reader_end(&zip_archive); 220 | if (buffer) free(buffer); 221 | fclose(zfh); 222 | ZIP_ERROR(R_ZIP_ECREATELINK, key, czipfile); 223 | } 224 | free(tmpbuf); 225 | 226 | #endif 227 | 228 | } else { 229 | if (!coverwrite && zip_file_exists(buffer)) { 230 | mz_zip_reader_end(&zip_archive); 231 | if (buffer) free(buffer); 232 | fclose(zfh); 233 | ZIP_ERROR(R_ZIP_EOVERWRITE, key, czipfile); 234 | } 235 | 236 | if (! cjunkpaths && zip_mkdirp(buffer, 0)) { 237 | mz_zip_reader_end(&zip_archive); 238 | if (buffer) free(buffer); 239 | fclose(zfh); 240 | ZIP_ERROR(R_ZIP_ECREATEDIR, key, czipfile); 241 | } 242 | 243 | FILE *fh = NULL; 244 | #ifdef _WIN32 245 | fh = _wfopen(buffer, L"wb"); 246 | #else 247 | fh = fopen(buffer, "wb"); 248 | #endif 249 | if (fh == NULL) { 250 | mz_zip_reader_end(&zip_archive); 251 | if (buffer) free(buffer); 252 | fclose(zfh); 253 | ZIP_ERROR(R_ZIP_EOPENX, key); 254 | } 255 | 256 | if (!mz_zip_reader_extract_to_cfile(&zip_archive, idx, fh, 0)) { 257 | mz_zip_reader_end(&zip_archive); 258 | if (buffer) free(buffer); 259 | fclose(fh); 260 | fclose(zfh); 261 | ZIP_ERROR(R_ZIP_EBROKENENTRY, key, czipfile); 262 | } 263 | fclose(fh); 264 | } 265 | #ifndef _WIN32 266 | mode_t mode; 267 | /* returns 1 if there are no permissions. In that case we don't call 268 | call chmod() and leave the permissions as they are, the file was 269 | created with the default umask. */ 270 | int ret = zip_get_permissions(&file_stat, &mode); 271 | if (!ret) { 272 | if (chmod(buffer, mode)) { 273 | mz_zip_reader_end(&zip_archive); 274 | if (buffer) free(buffer); 275 | fclose(zfh); 276 | ZIP_ERROR(R_ZIP_ESETPERM, key, czipfile); 277 | } 278 | } 279 | #endif 280 | } 281 | 282 | /* Round two, to set the mtime on directories. We skip handling most 283 | of the errors here, because the central directory is unchanged, and 284 | if we got here, then it must be still good. */ 285 | 286 | for (i = 0; ! cjunkpaths && i < n; i++) { 287 | mz_uint32 idx = -1; 288 | const char *key = 0; 289 | mz_zip_archive_file_stat file_stat; 290 | 291 | if (allfiles) { 292 | idx = (mz_uint32) i; 293 | } else { 294 | key = cfiles[i]; 295 | mz_zip_reader_locate_file_v2(&zip_archive, key, /* pComment= */ 0, 296 | /* flags= */ 0, &idx); 297 | } 298 | 299 | mz_zip_reader_file_stat(&zip_archive, idx, &file_stat); 300 | key = file_stat.m_filename; 301 | 302 | zip_str_file_path(cexdir, key, &buffer, &buffer_size, cjunkpaths); 303 | if (zip_set_mtime(buffer, file_stat.m_time)) { 304 | if (buffer) free(buffer); 305 | mz_zip_reader_end(&zip_archive); 306 | fclose(zfh); 307 | ZIP_ERROR(R_ZIP_ESETMTIME, key, czipfile); 308 | } 309 | } 310 | 311 | if (buffer) free(buffer); 312 | mz_zip_reader_end(&zip_archive); 313 | fclose(zfh); 314 | 315 | /* TODO: return info */ 316 | return 0; 317 | } 318 | 319 | int zip_zip(const char *czipfile, int num_files, const char **ckeys, 320 | const char **cfiles, int *cdirs, double *cmtimes, 321 | int compression_level, int cappend) { 322 | 323 | mz_uint ccompression_level = (mz_uint) compression_level; 324 | int i, n = num_files; 325 | mz_zip_archive zip_archive; 326 | memset(&zip_archive, 0, sizeof(zip_archive)); 327 | zip_char_t *filenameu16 = NULL; 328 | size_t filenameu16_len = 0; 329 | 330 | FILE *zfh = NULL; 331 | 332 | if (cappend) { 333 | zfh = zip_open_utf8(czipfile, ZIP__APPEND, &filenameu16, 334 | &filenameu16_len); 335 | if (zfh == NULL) { 336 | if (filenameu16) free(filenameu16); 337 | ZIP_ERROR(R_ZIP_EOPENAPPEND, czipfile); 338 | } 339 | if (!mz_zip_reader_init_cfile(&zip_archive, zfh, 0, 0) || 340 | !mz_zip_writer_init_from_reader(&zip_archive, NULL)) { 341 | if (filenameu16) free(filenameu16); 342 | fclose(zfh); 343 | ZIP_ERROR(R_ZIP_EOPENAPPEND, czipfile); 344 | } 345 | } else { 346 | zfh = zip_open_utf8(czipfile, ZIP__WRITE, &filenameu16, 347 | &filenameu16_len); 348 | if (zfh == NULL) { 349 | if (filenameu16) free(filenameu16); 350 | ZIP_ERROR(R_ZIP_EOPENWRITE, czipfile); 351 | } 352 | if (!mz_zip_writer_init_cfile(&zip_archive, zfh, 0)) { 353 | if (filenameu16) free(filenameu16); 354 | fclose(zfh); 355 | ZIP_ERROR(R_ZIP_EOPENWRITE, czipfile); 356 | } 357 | } 358 | 359 | for (i = 0; i < n; i++) { 360 | const char *key = ckeys[i]; 361 | const char *filename = cfiles[i]; 362 | int directory = cdirs[i]; 363 | MZ_TIME_T cmtime = (MZ_TIME_T) cmtimes[i]; 364 | if (directory) { 365 | if (!mz_zip_writer_add_mem_ex_v2(&zip_archive, key, 0, 0, 0, 0, 366 | ccompression_level, 0, 0, &cmtime, 0, 0, 367 | 0, 0)) { 368 | mz_zip_writer_end(&zip_archive); 369 | if (filenameu16) free(filenameu16); 370 | fclose(zfh); 371 | ZIP_ERROR(R_ZIP_EADDDIR, key, czipfile); 372 | } 373 | 374 | } else { 375 | FILE* fh = zip_open_utf8(filename, ZIP__READ, &filenameu16, 376 | &filenameu16_len); 377 | if (fh == NULL) { 378 | if (filenameu16) free(filenameu16); 379 | fclose(zfh); 380 | ZIP_ERROR(R_ZIP_EADDFILE, key, czipfile); 381 | } 382 | mz_uint64 uncomp_size = 0; 383 | if (zip_file_size(fh, &uncomp_size)) { 384 | if (filenameu16) free(filenameu16); 385 | fclose(zfh); 386 | ZIP_ERROR(R_ZIP_FILESIZE, filename); 387 | } 388 | 389 | int ret = mz_zip_writer_add_cfile(&zip_archive, key, fh, 390 | /* size_to_all= */ uncomp_size, /* pFile_time = */ &cmtime, 391 | /* pComment= */ NULL, /* comment_size= */ 0, 392 | /* level_and_flags = */ ccompression_level, 393 | /* user_extra_data_local = */ NULL, 394 | /* user_extra_data_local_len = */ 0, 395 | /* user_extra_data_central = */ NULL, 396 | /* user_extra_data_central_len = */ 0); 397 | fclose(fh); 398 | if (!ret) { 399 | if (filenameu16) free(filenameu16); 400 | mz_zip_writer_end(&zip_archive); 401 | ZIP_ERROR(R_ZIP_EADDFILE, key, czipfile); 402 | } 403 | } 404 | 405 | if (zip_set_permissions(&zip_archive, i, filename)) { 406 | if (filenameu16) free(filenameu16); 407 | mz_zip_writer_end(&zip_archive); 408 | fclose(zfh); 409 | ZIP_ERROR(R_ZIP_ESETZIPPERM, key, czipfile); 410 | } 411 | } 412 | 413 | if (!mz_zip_writer_finalize_archive(&zip_archive)) { 414 | if (filenameu16) free(filenameu16); 415 | mz_zip_writer_end(&zip_archive); 416 | fclose(zfh); 417 | ZIP_ERROR(R_ZIP_ECREATE, czipfile); 418 | } 419 | 420 | if (!mz_zip_writer_end(&zip_archive)) { 421 | if (filenameu16) free(filenameu16); 422 | fclose(zfh); 423 | ZIP_ERROR(R_ZIP_ECREATE, czipfile); 424 | } 425 | 426 | if (filenameu16) free(filenameu16); 427 | fclose(zfh); 428 | 429 | /* TODO: return info */ 430 | return 0; 431 | } 432 | -------------------------------------------------------------------------------- /src/zip.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef R_ZIP_H 3 | #define R_ZIP_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "miniz.h" 10 | 11 | typedef enum zip_error_codes { 12 | R_ZIP_ESUCCESS = 0, 13 | R_ZIP_EOPEN = 1, 14 | R_ZIP_ENOMEM = 2, 15 | R_ZIP_ENOENTRY = 3, 16 | R_ZIP_EBROKEN = 4, 17 | R_ZIP_EBROKENENTRY = 5, 18 | R_ZIP_EOVERWRITE = 6, 19 | R_ZIP_ECREATEDIR = 7, 20 | R_ZIP_ESETPERM = 8, 21 | R_ZIP_ESETMTIME = 9, 22 | R_ZIP_EOPENWRITE = 10, 23 | R_ZIP_EOPENAPPEND = 11, 24 | R_ZIP_EADDDIR = 12, 25 | R_ZIP_EADDFILE = 13, 26 | R_ZIP_ESETZIPPERM = 14, 27 | R_ZIP_ECREATE = 15, 28 | R_ZIP_EOPENX = 16, 29 | R_ZIP_FILESIZE = 17, 30 | R_ZIP_ECREATELINK = 18 31 | } zip_error_codes_t; 32 | 33 | typedef void zip_error_handler_t(const char *reason, const char *file, 34 | int line, int zip_errno, int eno); 35 | 36 | void zip_set_error_handler(zip_error_handler_t *handler); 37 | 38 | int zip_get_permissions(mz_zip_archive_file_stat *stat, mode_t *mode); 39 | 40 | int zip_set_permissions(mz_zip_archive *zip_archive, mz_uint file_index, 41 | const char *filename); 42 | 43 | int zip_zip(const char *czipfile, int num_files, const char **ckeys, 44 | const char **cfiles, int *cdirs, double *cmtimes, 45 | int compression_level, int cappend); 46 | 47 | int zip_unzip(const char *czipfile, const char **cfiles, int num_files, 48 | int coverwrite, int cjunkpaths, const char *exdir); 49 | 50 | #ifdef _WIN32 51 | 52 | #include 53 | 54 | #define zip_char_t wchar_t 55 | 56 | int zip__utf8_to_utf16(const char* s, wchar_t** buffer, 57 | size_t *buffer_size); 58 | int zip__utf16_to_utf8(const wchar_t *ws, char** buffer, 59 | size_t *buffer_size); 60 | 61 | FILE* zip_long_wfopen(const wchar_t *filename, 62 | const wchar_t *mode); 63 | 64 | #define ZIP__READ L"rb" 65 | #define ZIP__WRITE L"wb" 66 | #define ZIP__APPEND L"r+b" 67 | 68 | #else 69 | 70 | #define zip_char_t char 71 | 72 | #define ZIP__READ "rb" 73 | #define ZIP__WRITE "wb" 74 | #define ZIP__APPEND "r+b" 75 | 76 | #endif 77 | 78 | FILE *zip_open_utf8(const char *filename, const zip_char_t *mode, 79 | zip_char_t **buffer, size_t *buffer_size); 80 | int zip_str_file_path(const char *cexdir, const char *key, 81 | zip_char_t **buffer, size_t *buffer_size, 82 | int cjunkpaths); 83 | int zip_mkdirp(zip_char_t *path, int complete); 84 | int zip_set_mtime(const zip_char_t *filename, time_t mtime); 85 | int zip_file_exists(zip_char_t *filename); 86 | int zip_file_size(FILE *fh, mz_uint64 *size); 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(zip) 3 | 4 | test_check("zip") 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/ascii/inflate.md: -------------------------------------------------------------------------------- 1 | # inflate 2 | 3 | Code 4 | cat(out) 5 | Output 6 | tree 8e72c4ca68c095053b2c2dbe08f729a330af1fea 7 | parent df4a286449005dc0ef54ee6ae80adf5f14d0ecd3 8 | author Gbor Csrdi 1671272030 +0100 9 | committer Gbor Csrdi 1671272030 +0100 10 | 11 | Bump to RC version 12 | 13 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/errors.md: -------------------------------------------------------------------------------- 1 | # non-existant file 2 | 3 | Code 4 | withr::with_dir(dirname(tmp), zipr(zipfile, basename(tmp))) 5 | Condition 6 | Error in `zip_internal()`: 7 | ! Some files do not exist 8 | 9 | # non readable file 10 | 11 | Code 12 | withr::with_dir(dirname(tmp), zipr(zipfile, basename(tmp))) 13 | Condition 14 | Error in `zip_internal()`: 15 | ! zip error: Cannot add file `` to archive `/.zip` in file zip.c: 16 | 17 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/inflate.md: -------------------------------------------------------------------------------- 1 | # inflate 2 | 3 | Code 4 | inflate(data_gz, 10L, 300L) 5 | Condition 6 | Error in `inflate()`: 7 | ! Input data is invalid 8 | 9 | # deflate 10 | 11 | Code 12 | rawToChar(data$output) 13 | Output 14 | [1] "Hello world!" 15 | 16 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/unzip.md: -------------------------------------------------------------------------------- 1 | # overwrite is FALSE 2 | 3 | Code 4 | zip::unzip(z$zip, overwrite = FALSE, exdir = tmp) 5 | Condition 6 | Error in `zip::unzip()`: 7 | ! zip error: Not overwriting `test-dir-/dir/` when extracting `/test-file-.zip` in file zip.c:233 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/utf8/inflate.md: -------------------------------------------------------------------------------- 1 | # inflate 2 | 3 | Code 4 | cat(out) 5 | Output 6 | tree 8e72c4ca68c095053b2c2dbe08f729a330af1fea 7 | parent df4a286449005dc0ef54ee6ae80adf5f14d0ecd3 8 | author Gábor Csárdi 1671272030 +0100 9 | committer Gábor Csárdi 1671272030 +0100 10 | 11 | Bump to RC version 12 | 13 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/weird-paths.md: -------------------------------------------------------------------------------- 1 | # backslash is an error 2 | 3 | Code 4 | zip(tmpzip, tmp, mode = "cherry-pick") 5 | Condition 6 | Error in `zip_internal()`: 7 | ! zip error: Cannot add file `zip-test-bs-/real\bad` to archive `/zip-test-bs-.zip` in file zip.c: 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/zip-list.md: -------------------------------------------------------------------------------- 1 | # symlinks 2 | 3 | Code 4 | zip_list(zf)$type 5 | Output 6 | [1] "directory" "file" "symlink" 7 | 8 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/abs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/zip/14d8c7eb7a5abe4c60dbc718a2507b184115d8a4/tests/testthat/fixtures/abs.zip -------------------------------------------------------------------------------- /tests/testthat/fixtures/msdos.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/zip/14d8c7eb7a5abe4c60dbc718a2507b184115d8a4/tests/testthat/fixtures/msdos.zip -------------------------------------------------------------------------------- /tests/testthat/fixtures/symlink.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/zip/14d8c7eb7a5abe4c60dbc718a2507b184115d8a4/tests/testthat/fixtures/symlink.zip -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | df <- function(key, file, dir = FALSE) { 2 | data_frame( 3 | key = key, 4 | file = file, 5 | dir = dir 6 | ) 7 | } 8 | 9 | make_big_file <- function(file, mb) { 10 | tryCatch( 11 | make_big_file1(file, mb), 12 | error = function(e) { 13 | try(unlink(file, recursive = TRUE), silent = TRUE) 14 | skip("cannot create big files") 15 | } 16 | ) 17 | } 18 | 19 | make_big_file1 <- function(file, mb) { 20 | if (.Platform$OS.type == "windows") { 21 | .Call(c_R_make_big_file, file, as.integer(mb)) 22 | } else if (Sys.info()["sysname"] == "Darwin") { 23 | .Call(c_R_make_big_file, file, as.integer(mb)) 24 | } else if (nzchar(Sys.which("fallocate"))) { 25 | status <- system2("fallocate", c("-l", paste0(mb, "m"), shQuote(file))) 26 | if (status != 0) stop("Cannot create big files") 27 | } else if (nzchar(Sys.which("mkfile"))) { 28 | status <- system2("mkfile", c(paste0(mb, "m"), shQuote(file))) 29 | if (status != 0) stop("Cannot create big files") 30 | } else { 31 | stop("Cannot create big files") 32 | } 33 | 34 | Sys.chmod(file, "0644") 35 | } 36 | 37 | bns <- function(x) { 38 | paste0(basename(x), "/") 39 | } 40 | 41 | test_temp_file <- function( 42 | fileext = "", 43 | pattern = "test-file-", 44 | envir = parent.frame(), 45 | create = TRUE 46 | ) { 47 | tmp <- tempfile(pattern = pattern, fileext = fileext) 48 | if (identical(envir, .GlobalEnv)) { 49 | message("Temporary files will _not_ be cleaned up") 50 | } else { 51 | withr::defer( 52 | try(unlink(tmp, recursive = TRUE, force = TRUE), silent = TRUE), 53 | envir = envir 54 | ) 55 | } 56 | if (create) { 57 | cat("", file = tmp) 58 | normalizePath(tmp) 59 | } else { 60 | tmp 61 | } 62 | } 63 | 64 | test_temp_dir <- function( 65 | pattern = "test-dir-", 66 | envir = parent.frame(), 67 | create = TRUE 68 | ) { 69 | tmp <- test_temp_file(pattern = pattern, envir = envir, create = FALSE) 70 | if (create) { 71 | dir.create(tmp, recursive = TRUE, showWarnings = FALSE) 72 | normalizePath(tmp) 73 | } else { 74 | tmp 75 | } 76 | } 77 | 78 | make_a_zip <- function( 79 | mtime = Sys.time(), 80 | envir = parent.frame(), 81 | include_directories = TRUE 82 | ) { 83 | tmp <- test_temp_dir(envir = envir) 84 | cat("file1\n", file = file.path(tmp, "file1")) 85 | cat("file11\n", file = file.path(tmp, "file11")) 86 | dir.create(file.path(tmp, "dir")) 87 | cat("file2\n", file = file.path(tmp, "dir", "file2")) 88 | cat("file3\n", file = file.path(tmp, "dir", "file3")) 89 | 90 | Sys.setFileTime(file.path(tmp, "file1"), mtime) 91 | Sys.setFileTime(file.path(tmp, "file11"), mtime) 92 | Sys.setFileTime(file.path(tmp, "dir", "file2"), mtime) 93 | Sys.setFileTime(file.path(tmp, "dir", "file3"), mtime) 94 | Sys.setFileTime(file.path(tmp, "dir"), mtime) 95 | Sys.setFileTime(tmp, mtime) 96 | 97 | zip <- test_temp_file(".zip", envir = envir) 98 | zipr(zip, tmp, include_directories = include_directories) 99 | list(zip = zip, ex = tmp) 100 | } 101 | 102 | local_temp_dir <- function( 103 | pattern = "file", 104 | tmpdir = tempdir(), 105 | fileext = "", 106 | envir = parent.frame() 107 | ) { 108 | path <- tempfile(pattern = pattern, tmpdir = tmpdir, fileext = fileext) 109 | dir.create(path) 110 | setwd(path) 111 | do.call( 112 | withr::defer, 113 | list( 114 | bquote(unlink(.(path), recursive = TRUE)), 115 | envir = envir 116 | ) 117 | ) 118 | invisible(path) 119 | } 120 | 121 | transform_tempdir <- function(x) { 122 | x <- sub(tempdir(), "", x, fixed = TRUE) 123 | x <- sub(normalizePath(tempdir()), "", x, fixed = TRUE) 124 | x <- sub( 125 | normalizePath(tempdir(), winslash = "/"), 126 | "", 127 | x, 128 | fixed = TRUE 129 | ) 130 | x <- sub("\\R\\", "/R/", x, fixed = TRUE) 131 | x <- sub("[\\\\/]file[a-zA-Z0-9]+", "/", x) 132 | x <- sub("[A-Z]:.*Rtmp[a-zA-Z0-9]+[\\\\/]", "/", x) 133 | x 134 | } 135 | 136 | -------------------------------------------------------------------------------- /tests/testthat/test-errors.R: -------------------------------------------------------------------------------- 1 | test_that("non-existant file", { 2 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 3 | tmp <- tempfile() 4 | 5 | zipfile <- tempfile(fileext = ".zip") 6 | 7 | expect_snapshot( 8 | error = TRUE, 9 | withr::with_dir( 10 | dirname(tmp), 11 | zipr(zipfile, basename(tmp)) 12 | ) 13 | ) 14 | }) 15 | 16 | test_that("appending non-existant file", { 17 | on.exit(try(unlink(c(zipfile, tmp, tmp2), recursive = TRUE))) 18 | cat("compress this if you can!", file = tmp <- tempfile()) 19 | 20 | zipfile <- tempfile(fileext = ".zip") 21 | 22 | expect_silent( 23 | withr::with_dir( 24 | dirname(tmp), 25 | zipr(zipfile, basename(tmp)) 26 | ) 27 | ) 28 | 29 | cat("compress this as well, if you can!", file = tmp2 <- tempfile()) 30 | 31 | expect_silent( 32 | withr::with_dir( 33 | dirname(tmp2), 34 | zipr_append(zipfile, basename(tmp2)) 35 | ) 36 | ) 37 | 38 | expect_true(file.exists(zipfile)) 39 | 40 | list <- zip_list(zipfile) 41 | expect_equal(basename(list$filename), basename(c(tmp, tmp2))) 42 | }) 43 | 44 | test_that("non readable file", { 45 | skip_on_os("windows") 46 | skip_on_os("linux") 47 | 48 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 49 | cat("compress this if you can!", file = tmp <- tempfile()) 50 | Sys.chmod(tmp, "0000") 51 | 52 | zipfile <- tempfile(fileext = ".zip") 53 | 54 | expect_snapshot( 55 | error = TRUE, 56 | withr::with_dir( 57 | dirname(tmp), 58 | zipr(zipfile, basename(tmp)) 59 | ), 60 | transform = function(x) { 61 | x <- transform_tempdir(x) 62 | x <- sub("`file[^`]+`", "``", x) 63 | x <- sub("file zip.c:[0-9]+", "file zip.c:", x) 64 | x 65 | } 66 | ) 67 | }) 68 | 69 | test_that("empty archive, no files", { 70 | on.exit(try(unlink(zipfile))) 71 | zipfile <- tempfile(fileext = ".zip") 72 | 73 | expect_silent(zipr(zipfile, character())) 74 | 75 | expect_true(file.exists(zipfile)) 76 | 77 | list <- zip_list(zipfile) 78 | expect_equal(nrow(list), 0) 79 | expect_equal(list$filename, character()) 80 | }) 81 | 82 | test_that("single empty directory", { 83 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 84 | dir.create(tmp <- tempfile()) 85 | 86 | zipfile <- tempfile(fileext = ".zip") 87 | 88 | expect_silent( 89 | withr::with_dir( 90 | dirname(tmp), 91 | zipr(zipfile, basename(tmp)) 92 | ) 93 | ) 94 | 95 | expect_true(file.exists(zipfile)) 96 | 97 | list <- zip_list(zipfile) 98 | expect_equal(nrow(list), 1) 99 | expect_equal(list$filename, bns(tmp)) 100 | 101 | dir.create(tmp2 <- tempfile()) 102 | on.exit(try(unlink(tmp2, recursive = TRUE))) 103 | utils::unzip(zipfile, exdir = tmp2) 104 | expect_equal(dir(tmp2), basename(tmp)) 105 | expect_true(file.info(file.path(tmp2, dir(tmp2)))$isdir) 106 | }) 107 | 108 | test_that("single empty directory, non-recursive", { 109 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 110 | dir.create(tmp <- tempfile()) 111 | 112 | zipfile <- tempfile(fileext = ".zip") 113 | 114 | expect_warning( 115 | withr::with_dir( 116 | dirname(tmp), 117 | zipr(zipfile, basename(tmp), recurse = FALSE) 118 | ), 119 | "directories ignored" 120 | ) 121 | 122 | expect_true(file.exists(zipfile)) 123 | 124 | list <- zip_list(zipfile) 125 | expect_equal(nrow(list), 0) 126 | expect_equal(list$filename, character()) 127 | }) 128 | 129 | test_that("appending single empty directory", { 130 | on.exit(try(unlink(c(zipfile, tmp, tmp2), recursive = TRUE))) 131 | 132 | dir.create(tmp <- tempfile()) 133 | cat("first file", file = file.path(tmp, "file1")) 134 | cat("second file", file = file.path(tmp, "file2")) 135 | 136 | zipfile <- tempfile(fileext = ".zip") 137 | 138 | expect_silent( 139 | withr::with_dir( 140 | dirname(tmp), 141 | zipr(zipfile, basename(tmp)) 142 | ) 143 | ) 144 | 145 | expect_true(file.exists(zipfile)) 146 | 147 | list <- zip_list(zipfile) 148 | expect_equal( 149 | basename(list$filename), 150 | c(basename(tmp), "file1", "file2") 151 | ) 152 | 153 | dir.create(tmp2 <- tempfile()) 154 | 155 | expect_silent( 156 | withr::with_dir( 157 | dirname(tmp), 158 | zipr_append(zipfile, basename(tmp2)) 159 | ) 160 | ) 161 | 162 | expect_true(file.exists(zipfile)) 163 | 164 | list <- zip_list(zipfile) 165 | expect_equal( 166 | basename(list$filename), 167 | c(basename(tmp), "file1", "file2", basename(tmp2)) 168 | ) 169 | }) 170 | 171 | test_that("appending single empty directory, non-recursive", { 172 | on.exit(try(unlink(c(zipfile, tmp, tmp2), recursive = TRUE))) 173 | 174 | dir.create(tmp <- tempfile()) 175 | cat("first file", file = file.path(tmp, "file1")) 176 | cat("second file", file = file.path(tmp, "file2")) 177 | 178 | zipfile <- tempfile(fileext = ".zip") 179 | 180 | expect_silent( 181 | withr::with_dir( 182 | dirname(tmp), 183 | zipr(zipfile, basename(tmp)) 184 | ) 185 | ) 186 | 187 | expect_true(file.exists(zipfile)) 188 | 189 | list <- zip_list(zipfile) 190 | expect_equal( 191 | basename(list$filename), 192 | c(basename(tmp), "file1", "file2") 193 | ) 194 | 195 | dir.create(tmp2 <- tempfile()) 196 | 197 | expect_warning( 198 | withr::with_dir( 199 | dirname(tmp), 200 | zipr_append(zipfile, basename(tmp2), recurse = FALSE) 201 | ), 202 | "directories ignored" 203 | ) 204 | 205 | expect_true(file.exists(zipfile)) 206 | 207 | list <- zip_list(zipfile) 208 | expect_equal( 209 | basename(list$filename), 210 | c(basename(tmp), "file1", "file2") 211 | ) 212 | }) 213 | -------------------------------------------------------------------------------- /tests/testthat/test-get-zip-data-path.R: -------------------------------------------------------------------------------- 1 | test_that("get_zip_data", { 2 | on.exit(try(unlink(tmp, recursive = TRUE)), add = TRUE) 3 | dir.create(tmp <- tempfile()) 4 | 5 | expect_equal( 6 | get_zip_data_path_recursive(tmp), 7 | df(paste0(tmp, "/"), normalizePath(tmp), TRUE) 8 | ) 9 | expect_equal(get_zip_data_path_recursive(tmp), get_zip_data_path(tmp, TRUE)) 10 | 11 | foobar <- file.path(tmp, "foobar") 12 | cat("foobar", file = foobar) 13 | 14 | expect_equal( 15 | get_zip_data_path_recursive(foobar), 16 | df(foobar, normalizePath(foobar), FALSE) 17 | ) 18 | expect_equal( 19 | get_zip_data_path_recursive(foobar), 20 | get_zip_data_path(foobar, TRUE) 21 | ) 22 | 23 | expect_equal( 24 | get_zip_data_path_recursive(tmp), 25 | df( 26 | c(paste0(tmp, "/"), file.path(tmp, "foobar")), 27 | normalizePath(c(tmp, foobar)), 28 | c(TRUE, FALSE) 29 | ) 30 | ) 31 | expect_equal(get_zip_data_path_recursive(tmp), get_zip_data_path(tmp, TRUE)) 32 | 33 | expect_equal( 34 | withr::with_dir(tmp, get_zip_data_path_recursive(".")), 35 | df(c("./", "./foobar"), normalizePath(c(tmp, foobar)), c(TRUE, FALSE)) 36 | ) 37 | withr::with_dir( 38 | tmp, 39 | expect_equal(get_zip_data_path_recursive("."), get_zip_data_path(".", TRUE)) 40 | ) 41 | 42 | dir.create(file.path(tmp, "empty")) 43 | dir.create(file.path(tmp, "foo")) 44 | bar <- file.path(tmp, "foo", "bar") 45 | cat("bar\n", file = bar) 46 | 47 | data <- df( 48 | c( 49 | paste0(tmp, "/"), 50 | paste0(file.path(tmp, "empty"), "/"), 51 | paste0(file.path(tmp, "foo"), "/"), 52 | file.path(tmp, "foo", "bar"), 53 | file.path(tmp, "foobar") 54 | ), 55 | normalizePath(c( 56 | tmp, 57 | file.path(tmp, "empty"), 58 | file.path(tmp, "foo"), 59 | bar, 60 | file.path(tmp, "foobar") 61 | )), 62 | c(TRUE, TRUE, TRUE, FALSE, FALSE) 63 | ) 64 | data <- data[order(data$file), ] 65 | rownames(data) <- NULL 66 | 67 | data2 <- get_zip_data_path_recursive(tmp) 68 | data2 <- data2[order(data2$file), ] 69 | rownames(data2) <- NULL 70 | 71 | expect_equal(data2, data) 72 | expect_equal(get_zip_data_path(tmp, TRUE), data) 73 | 74 | expect_equal( 75 | get_zip_data_path(c(foobar, bar), TRUE), 76 | df(c(foobar, bar), normalizePath(c(foobar, bar)), c(FALSE, FALSE)) 77 | ) 78 | 79 | expect_equal( 80 | get_zip_data_path(file.path(tmp, "foo"), TRUE), 81 | df( 82 | c(paste0(file.path(tmp, "foo"), "/"), file.path(tmp, "foo", "bar")), 83 | normalizePath(c(file.path(tmp, "foo"), file.path(tmp, "foo", "bar"))), 84 | c(TRUE, FALSE) 85 | ) 86 | ) 87 | }) 88 | 89 | test_that("get_zip_data relative paths", { 90 | on.exit(try(unlink(tmp, recursive = TRUE)), add = TRUE) 91 | dir.create(tmp <- tempfile()) 92 | 93 | dir.create(file.path(tmp, "foo")) 94 | dir.create(file.path(tmp, "foo", "bar")) 95 | 96 | withr::with_dir( 97 | file.path(tmp, "foo"), 98 | expect_equal( 99 | get_zip_data_path(file.path("..", "foo"), TRUE), 100 | df( 101 | paste0(c(file.path("..", "foo"), file.path("..", "foo", "bar")), "/"), 102 | normalizePath(c(file.path(tmp, "foo"), file.path(tmp, "foo", "bar"))), 103 | c(TRUE, TRUE) 104 | ) 105 | ) 106 | ) 107 | }) 108 | -------------------------------------------------------------------------------- /tests/testthat/test-get-zip-data.R: -------------------------------------------------------------------------------- 1 | test_that("get_zip_data", { 2 | on.exit(try(unlink(tmp, recursive = TRUE)), add = TRUE) 3 | dir.create(tmp <- tempfile()) 4 | 5 | expect_equal( 6 | get_zip_data_nopath_recursive(tmp), 7 | df(paste0(basename(tmp), "/"), normalizePath(tmp), TRUE) 8 | ) 9 | expect_equal( 10 | get_zip_data_nopath_recursive(tmp), 11 | get_zip_data_nopath(tmp, TRUE) 12 | ) 13 | 14 | foobar <- file.path(tmp, "foobar") 15 | cat("foobar", file = foobar) 16 | 17 | expect_equal( 18 | get_zip_data_nopath_recursive(foobar), 19 | df(basename(foobar), normalizePath(foobar), FALSE) 20 | ) 21 | expect_equal( 22 | get_zip_data_nopath_recursive(foobar), 23 | get_zip_data_nopath(foobar, TRUE) 24 | ) 25 | 26 | expect_equal( 27 | get_zip_data_nopath_recursive(tmp), 28 | df( 29 | c(paste0(basename(tmp), "/"), file.path(basename(tmp), "foobar")), 30 | normalizePath(c(tmp, foobar)), 31 | c(TRUE, FALSE) 32 | ) 33 | ) 34 | expect_equal( 35 | get_zip_data_nopath_recursive(tmp), 36 | get_zip_data_nopath(tmp, TRUE) 37 | ) 38 | 39 | expect_equal( 40 | withr::with_dir(tmp, get_zip_data_nopath_recursive(".")), 41 | df("foobar", normalizePath(foobar), FALSE) 42 | ) 43 | withr::with_dir( 44 | tmp, 45 | expect_equal( 46 | get_zip_data_nopath_recursive("."), 47 | get_zip_data_nopath(".", TRUE) 48 | ) 49 | ) 50 | 51 | dir.create(file.path(tmp, "empty")) 52 | dir.create(file.path(tmp, "foo")) 53 | bar <- file.path(tmp, "foo", "bar") 54 | cat("bar\n", file = bar) 55 | 56 | data <- df( 57 | c( 58 | paste0(basename(tmp), "/"), 59 | paste0(file.path(basename(tmp), "empty"), "/"), 60 | paste0(file.path(basename(tmp), "foo"), "/"), 61 | file.path(basename(tmp), "foo", "bar"), 62 | file.path(basename(tmp), "foobar") 63 | ), 64 | normalizePath(c( 65 | tmp, 66 | file.path(tmp, "empty"), 67 | file.path(tmp, "foo"), 68 | bar, 69 | file.path(tmp, "foobar") 70 | )), 71 | c(TRUE, TRUE, TRUE, FALSE, FALSE) 72 | ) 73 | data <- data[order(data$file), ] 74 | rownames(data) <- NULL 75 | 76 | data2 <- get_zip_data_nopath_recursive(tmp) 77 | data2 <- data2[order(data2$file), ] 78 | rownames(data2) <- NULL 79 | 80 | expect_equal(data2, data) 81 | expect_equal(get_zip_data_nopath(tmp, TRUE), data) 82 | 83 | expect_equal( 84 | get_zip_data_nopath(c(foobar, bar), TRUE), 85 | df(c("foobar", "bar"), normalizePath(c(foobar, bar)), c(FALSE, FALSE)) 86 | ) 87 | 88 | expect_equal( 89 | get_zip_data_nopath(file.path(tmp, "foo"), TRUE), 90 | df( 91 | c("foo/", "foo/bar"), 92 | normalizePath(c(file.path(tmp, "foo"), file.path(tmp, "foo", "bar"))), 93 | c(TRUE, FALSE) 94 | ) 95 | ) 96 | }) 97 | -------------------------------------------------------------------------------- /tests/testthat/test-inflate.R: -------------------------------------------------------------------------------- 1 | test_that("inflate", { 2 | data_gz <- as.raw(c( 3 | 0x78, 4 | 0x9c, 5 | 0xa5, 6 | 0xcc, 7 | 0x4d, 8 | 0x0a, 9 | 0x83, 10 | 0x30, 11 | 0x10, 12 | 0x40, 13 | 0xe1, 14 | 0x7d, 15 | 0x4e, 16 | 0x31, 17 | 0xfb, 18 | 0x82, 19 | 0x4c, 20 | 0xfe, 21 | 0x34, 22 | 0x42, 23 | 0x29, 24 | 0xa5, 25 | 0x2e, 26 | 0xba, 27 | 0xef, 28 | 0x0d, 29 | 0xc6, 30 | 0x64, 31 | 0x62, 32 | 0x85, 33 | 0x46, 34 | 0x25, 35 | 0xc6, 36 | 0xde, 37 | 0xc7, 38 | 0xb3, 39 | 0x78, 40 | 0xb1, 41 | 0x7a, 42 | 0x87, 43 | 0xae, 44 | 0x1e, 45 | 0x7c, 46 | 0x8b, 47 | 0x57, 48 | 0x32, 49 | 0x33, 50 | 0x38, 51 | 0x6e, 52 | 0x94, 53 | 0x37, 54 | 0x9e, 55 | 0x6a, 56 | 0xe7, 57 | 0xb1, 58 | 0xb5, 59 | 0x68, 60 | 0x75, 61 | 0xaf, 62 | 0xbc, 63 | 0x0a, 64 | 0x3d, 65 | 0xa3, 66 | 0x8b, 67 | 0x8d, 68 | 0x6a, 69 | 0x49, 70 | 0x6b, 71 | 0xa4, 72 | 0x28, 73 | 0x23, 74 | 0x93, 75 | 0x58, 76 | 0x28, 77 | 0xf3, 78 | 0x54, 79 | 0x20, 80 | 0x44, 81 | 0x43, 82 | 0xca, 83 | 0xd5, 84 | 0xc6, 85 | 0xb4, 86 | 0x88, 87 | 0x36, 88 | 0x78, 89 | 0xe4, 90 | 0x68, 91 | 0x0d, 92 | 0x73, 93 | 0x4d, 94 | 0xec, 95 | 0x90, 96 | 0x42, 97 | 0xb4, 98 | 0x51, 99 | 0x9a, 100 | 0x80, 101 | 0xec, 102 | 0x83, 103 | 0x16, 104 | 0xb4, 105 | 0x95, 106 | 0xf7, 107 | 0x9c, 108 | 0xe1, 109 | 0x79, 110 | 0xec, 111 | 0xfd, 112 | 0x99, 113 | 0x6e, 114 | 0x3d, 115 | 0xf6, 116 | 0x1c, 117 | 0x46, 118 | 0xb8, 119 | 0xfa, 120 | 0x95, 121 | 0xce, 122 | 0x56, 123 | 0x03, 124 | 0x9d, 125 | 0x7a, 126 | 0x1f, 127 | 0x12, 128 | 0x8d, 129 | 0x9f, 130 | 0xca, 131 | 0xcf, 132 | 0xe9, 133 | 0x06, 134 | 0xb2, 135 | 0x6e, 136 | 0xa4, 137 | 0x6a, 138 | 0x14, 139 | 0x6a, 140 | 0x84, 141 | 0x0b, 142 | 0x4a, 143 | 0x44, 144 | 0x71, 145 | 0x6a, 146 | 0x1a, 147 | 0x4b, 148 | 0xe1, 149 | 0x3f, 150 | 0x16, 151 | 0xe2, 152 | 0xb1, 153 | 0xa5, 154 | 0x05, 155 | 0xca, 156 | 0x0c, 157 | 0xaf, 158 | 0x0e, 159 | 0xbe, 160 | 0x9c, 161 | 0xd7, 162 | 0x71, 163 | 0x9e, 164 | 0xc4, 165 | 0x0f, 166 | 0x2b, 167 | 0x30, 168 | 0x4d, 169 | 0xe3, 170 | 0xa0, 171 | 0x31, 172 | 0x78, 173 | 0x9c, 174 | 0x33, 175 | 0x34, 176 | 0x30, 177 | 0x30, 178 | 0x33, 179 | 0x31, 180 | 0x51, 181 | 0xd0, 182 | 0x0b, 183 | 0x4a, 184 | 0x2a, 185 | 0xcd, 186 | 0xcc, 187 | 0x49, 188 | 0xc9, 189 | 0x4c, 190 | 0xcf, 191 | 0xcb, 192 | 0x2f, 193 | 0x4a, 194 | 0x65, 195 | 0xa8, 196 | 0x48, 197 | 0xae, 198 | 0x29, 199 | 0x77, 200 | 0xfc, 201 | 0x78, 202 | 0x21 203 | )) 204 | 205 | data <- inflate(data_gz, 1L, 245L) 206 | out <- rawToChar(data$output) 207 | Encoding(out) <- "UTF-8" 208 | if (l10n_info()[["UTF-8"]]) { 209 | variant <- "utf8" 210 | } else { 211 | out <- iconv(out, "UTF-8", "ASCII", sub = "byte") 212 | variant <- "ascii" 213 | } 214 | expect_snapshot( 215 | cat(out), 216 | variant = variant 217 | ) 218 | 219 | # buffer is resized 220 | expect_silent(inflate(data_gz, 1L, 200L)) 221 | 222 | # bad format 223 | expect_snapshot(error = TRUE, inflate(data_gz, 10L, 300L)) 224 | }) 225 | 226 | test_that("deflate", { 227 | data_gz <- deflate(charToRaw("Hello world!")) 228 | data <- inflate(data_gz$output) 229 | expect_equal(data_gz$bytes_written, data$bytes_read) 230 | expect_equal(data_gz$bytes_read, data$bytes_written) 231 | expect_snapshot(rawToChar(data$output)) 232 | 233 | # output is resized 234 | data_gz_2 <- deflate(charToRaw("Hello world!"), size = 5) 235 | expect_equal(data_gz, data_gz_2) 236 | data_gz_3 <- deflate(charToRaw("Hello world!"), size = 500) 237 | expect_equal(data_gz, data_gz_3) 238 | }) 239 | -------------------------------------------------------------------------------- /tests/testthat/test-large-files.R: -------------------------------------------------------------------------------- 1 | test_that("large zip files", { 2 | skip_on_cran() 3 | 4 | dir.create(tmp <- tempfile("zip-test-large-")) 5 | tmpzip <- tempfile("zip-test-large-", fileext = ".zip") 6 | on.exit(unlink(c(tmp, tmpzip), recursive = TRUE), add = TRUE) 7 | 8 | oc <- file(file.path(tmp, "file1"), open = "wb") 9 | for (i in 1:6) { 10 | data <- runif(1e7) 11 | writeBin(data, oc) 12 | } 13 | close(oc) 14 | writeLines("hi there", file.path(tmp, "file2")) 15 | 16 | zip::zip(tmpzip, tmp, compression_level = 0, mode = "cherry-pick") 17 | zip::zip_list(tmpzip) 18 | 19 | unlink(tmp, recursive = TRUE) 20 | zip::unzip(tmpzip, exdir = dirname(tmp)) 21 | 22 | expect_true(file.exists(tmp)) 23 | expect_true(file.exists(file.path(tmp, "file1"))) 24 | expect_true(file.exists(file.path(tmp, "file2"))) 25 | 26 | expect_true(file.size(file.path(tmp, "file1")) > 450000000) 27 | expect_true(file.size(tmpzip) > 450000000) 28 | }) 29 | 30 | test_that("can compress / uncompress large files", { 31 | skip_on_cran() 32 | if ( 33 | !nzchar(Sys.getenv("ZIP_LONG_TESTS")) && 34 | !nzchar(Sys.getenv("CI")) 35 | ) { 36 | skip("takes long") 37 | } 38 | 39 | ## Note: it will be also skipped if we cannot find a reasonable quick 40 | ## way to create a 5GB file. 41 | 42 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 43 | dir.create(tmp <- tempfile()) 44 | file1 <- file.path(tmp, "file1") 45 | make_big_file(file1, 5000) 46 | size <- file.info(file1)$size 47 | 48 | zipfile <- tempfile(fileext = ".zip") 49 | on.exit(unlink(zipfile), add = TRUE) 50 | 51 | zipr(zipfile, file1, compression_level = 1) 52 | expect_true(file.exists(zipfile)) 53 | list <- zip_list(zipfile) 54 | expect_equal(list$filename, "file1") 55 | expect_equal(list$uncompressed_size, size) 56 | 57 | on.exit(unlink(tmp2, recursive = TRUE), add = TRUE) 58 | dir.create(tmp2 <- tempfile()) 59 | 60 | unlink(file1) 61 | zip::unzip(zipfile, exdir = tmp2) 62 | expect_equal(file.info(file.path(tmp2, "file1"))$size, size) 63 | }) 64 | 65 | test_that("can compress / uncompress many files", { 66 | skip_on_cran() 67 | if ( 68 | !nzchar(Sys.getenv("ZIP_LONG_TESTS")) && 69 | !nzchar(Sys.getenv("CI")) 70 | ) { 71 | skip("takes long") 72 | } 73 | 74 | tmp <- test_temp_dir() 75 | for (i in 1:70000) cat("file", i, file = file.path(tmp, i)) 76 | 77 | zip <- test_temp_file(".zip") 78 | zipr(zip, tmp) 79 | 80 | l <- zip_list(zip) 81 | expect_equal(nrow(l), 70001) 82 | 83 | tmp2 <- test_temp_dir() 84 | zip::unzip(zip, exdir = tmp2) 85 | expect_equal( 86 | length(dir(file.path(tmp2, basename(tmp)))), 87 | 70000 88 | ) 89 | }) 90 | -------------------------------------------------------------------------------- /tests/testthat/test-paths.R: -------------------------------------------------------------------------------- 1 | test_that("base path with spaces", { 2 | local_temp_dir() 3 | dir.create("space 1 2") 4 | setwd("space 1 2") 5 | dir.create("dir1") 6 | dir.create("dir2") 7 | cat("file1", file = "dir1/file1") 8 | cat("file2", file = "dir2/file2") 9 | 10 | zip::zip("zip1.zip", c("dir1", "dir2"), mode = "mirror") 11 | l <- zip_list("zip1.zip") 12 | expect_equal(l$filename, c("dir1/", "dir1/file1", "dir2/", "dir2/file2")) 13 | 14 | dir.create("ex1") 15 | zip::unzip("zip1.zip", exdir = "ex1") 16 | expect_equal( 17 | sort(dir("ex1", recursive = TRUE)), 18 | c("dir1/file1", "dir2/file2") 19 | ) 20 | 21 | zip::zip("zip2.zip", c("dir1", "dir2/file2"), mode = "cherry-pick") 22 | l2 <- zip_list("zip2.zip") 23 | expect_equal(l2$filename, c("dir1/", "dir1/file1", "file2")) 24 | 25 | dir.create("ex2") 26 | zip::unzip("zip2.zip", exdir = "ex2") 27 | expect_equal( 28 | sort(dir("ex2", recursive = TRUE)), 29 | c("dir1/file1", "file2") 30 | ) 31 | }) 32 | 33 | test_that("uncompressed path with spaces", { 34 | local_temp_dir() 35 | root <- "root 1 2" 36 | dir.create(root) 37 | cat("contents\n", file = file.path(root, "file 3 4")) 38 | zip("zip1.zip", root, mode = "mirror") 39 | l <- zip_list("zip1.zip") 40 | expect_equal( 41 | l$filename, 42 | c(paste0(root, "/"), file.path(root, "file 3 4", fsep = "/")) 43 | ) 44 | 45 | dir.create("ex1") 46 | zip::unzip("zip1.zip", exdir = "ex1") 47 | expect_equal( 48 | sort(dir("ex1", recursive = TRUE)), 49 | file.path(root, "file 3 4", fsep = "/") 50 | ) 51 | 52 | zip("zip2.zip", root, mode = "cherry-pick") 53 | l2 <- zip_list("zip2.zip") 54 | 55 | dir.create("ex2") 56 | zip::unzip("zip2.zip", exdir = "ex2") 57 | expect_equal( 58 | l2$filename, 59 | c(paste0(root, "/"), file.path(root, "file 3 4", fsep = "/")) 60 | ) 61 | }) 62 | 63 | test_that("base path with non-ASCII characters", { 64 | if (tolower(Sys.info()[["sysname"]]) != "windows") { 65 | skip("Only on Windows") 66 | } 67 | local_temp_dir() 68 | root <- enc2native("\u00fa\u00e1\u00f6\u0151\u00e9") 69 | dir.create(root) 70 | setwd(root) 71 | dir.create("dir1") 72 | dir.create("dir2") 73 | cat("file1", file = "dir1/file1") 74 | cat("file2", file = "dir2/file2") 75 | 76 | zip::zip("zip1.zip", c("dir1", "dir2"), mode = "mirror") 77 | l <- zip_list("zip1.zip") 78 | expect_equal(l$filename, c("dir1/", "dir1/file1", "dir2/", "dir2/file2")) 79 | 80 | dir.create("ex1") 81 | zip::unzip("zip1.zip", exdir = "ex1") 82 | expect_equal( 83 | sort(dir("ex1", recursive = TRUE)), 84 | c("dir1/file1", "dir2/file2") 85 | ) 86 | 87 | zip::zip("zip2.zip", c("dir1", "dir2/file2"), mode = "cherry-pick") 88 | l2 <- zip_list("zip2.zip") 89 | expect_equal(l2$filename, c("dir1/", "dir1/file1", "file2")) 90 | 91 | dir.create("ex2") 92 | zip::unzip("zip2.zip", exdir = "ex2") 93 | expect_equal( 94 | sort(dir("ex2", recursive = TRUE)), 95 | c("dir1/file1", "file2") 96 | ) 97 | }) 98 | 99 | test_that("uncompressed path with non-ASCII characters", { 100 | if (tolower(Sys.info()[["sysname"]]) != "windows") { 101 | skip("Only on Windows") 102 | } 103 | local_temp_dir() 104 | root <- enc2native("\u00fa\u00e1\u00f6\u0151\u00e9") 105 | ufile <- enc2native("ufile\u00fa\u00e1") 106 | dir.create(root) 107 | cat("contents\n", file = file.path(root, ufile)) 108 | zip("zip1.zip", root, mode = "mirror") 109 | l <- zip_list("zip1.zip") 110 | expect_equal( 111 | l$filename, 112 | c(paste0(root, "/"), file.path(root, ufile, fsep = "/")) 113 | ) 114 | expect_equal(Encoding(l$filename), rep("UTF-8", 2)) 115 | 116 | dir.create("ex1") 117 | zip::unzip("zip1.zip", exdir = "ex1") 118 | expect_equal( 119 | sort(dir("ex1", recursive = TRUE)), 120 | file.path(root, ufile, fsep = "/") 121 | ) 122 | 123 | zip("zip2.zip", root, mode = "cherry-pick") 124 | l2 <- zip_list("zip2.zip") 125 | 126 | dir.create("ex2") 127 | zip::unzip("zip2.zip", exdir = "ex2") 128 | expect_equal( 129 | l2$filename, 130 | c(paste0(root, "/"), file.path(root, ufile, fsep = "/")) 131 | ) 132 | }) 133 | 134 | test_that("zip file with spaces", { 135 | local_temp_dir() 136 | dir.create("dir1") 137 | dir.create("dir 2") 138 | cat("file1", file = "dir1/file 1") 139 | cat("file2", file = "dir 2/file2") 140 | 141 | zip::zip("zip1.zip", c("dir1", "dir 2"), mode = "mirror") 142 | l <- zip_list("zip1.zip") 143 | expect_equal(l$filename, c("dir1/", "dir1/file 1", "dir 2/", "dir 2/file2")) 144 | 145 | dir.create("ex1") 146 | zip::unzip("zip1.zip", exdir = "ex1") 147 | expect_equal( 148 | sort(dir("ex1", recursive = TRUE)), 149 | sort(c("dir1/file 1", "dir 2/file2")) 150 | ) 151 | 152 | zip::zip("zip2.zip", c("dir1", "dir 2/file2"), mode = "cherry-pick") 153 | l2 <- zip_list("zip2.zip") 154 | expect_equal(l2$filename, c("dir1/", "dir1/file 1", "file2")) 155 | 156 | dir.create("ex2") 157 | zip::unzip("zip2.zip", exdir = "ex2") 158 | expect_equal( 159 | sort(dir("ex2", recursive = TRUE)), 160 | c("dir1/file 1", "file2") 161 | ) 162 | }) 163 | 164 | test_that("zip file with non-ASCII characters", { 165 | skip_on_cran() 166 | local_temp_dir() 167 | zipfile <- enc2native("x-\u00fa\u00e1\u00f6\u0151\u00e9.zip") 168 | dir.create("dir1") 169 | dir.create("dir2") 170 | cat("file1", file = "dir1/file1") 171 | cat("file2", file = "dir2/file2") 172 | 173 | zip::zip(zipfile, c("dir1", "dir2"), mode = "mirror") 174 | l <- zip_list(zipfile) 175 | expect_equal(l$filename, c("dir1/", "dir1/file1", "dir2/", "dir2/file2")) 176 | 177 | dir.create("ex1") 178 | zip::unzip(zipfile, exdir = "ex1") 179 | expect_equal( 180 | sort(dir("ex1", recursive = TRUE)), 181 | c("dir1/file1", "dir2/file2") 182 | ) 183 | 184 | unlink(zipfile) 185 | 186 | # ---------------------------------------------------------------- 187 | 188 | zip::zip(zipfile, c("dir1", "dir2/file2"), mode = "cherry-pick") 189 | l2 <- zip_list(zipfile) 190 | expect_equal(l2$filename, c("dir1/", "dir1/file1", "file2")) 191 | 192 | dir.create("ex2") 193 | zip::unzip(zipfile, exdir = "ex2") 194 | expect_equal( 195 | sort(dir("ex2", recursive = TRUE)), 196 | c("dir1/file1", "file2") 197 | ) 198 | 199 | unlink(zipfile) 200 | 201 | # ---------------------------------------------------------------- 202 | 203 | p <- zip::zip_process()$new(zipfile, c("dir1", "dir2")) 204 | p$wait(5000) 205 | p$kill() 206 | expect_equal(p$get_exit_status(), 0L) 207 | 208 | l <- zip_list(zipfile) 209 | expect_equal(l$filename, c("dir1/", "dir1/file1", "dir2/", "dir2/file2")) 210 | 211 | p2 <- zip::unzip_process()$new(zipfile, "ex3") 212 | p2$wait(5000) 213 | p2$kill() 214 | expect_equal(p2$get_exit_status(), 0L) 215 | expect_equal( 216 | sort(dir("ex3", recursive = TRUE)), 217 | c("dir1/file1", "dir2/file2") 218 | ) 219 | }) 220 | -------------------------------------------------------------------------------- /tests/testthat/test-special-dot.R: -------------------------------------------------------------------------------- 1 | test_that("`.` is special in cherry picking mode", { 2 | dir.create(tmp <- tempfile("zip-test-dot-")) 3 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 4 | 5 | old <- getwd() 6 | on.exit(setwd(old), add = TRUE) 7 | setwd(tmp) 8 | 9 | dir.create("xxx") 10 | writeLines("bar", file.path("xxx", "bar")) 11 | writeLines("foo", file.path("xxx", "foo")) 12 | setwd("xxx") 13 | 14 | zip::zip("../out.zip", ".", mode = "cherry-pick", include_directories = FALSE) 15 | 16 | expect_equal(sort(zip_list("../out.zip")$file), sort(c("bar", "foo"))) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test-unzip-process.R: -------------------------------------------------------------------------------- 1 | test_that("unzip_process", { 2 | z <- make_a_zip() 3 | tmp2 <- test_temp_dir() 4 | p1 <- unzip_process()$new(z$zip, tmp2) 5 | p1$wait(2000) 6 | p1$kill() 7 | 8 | expect_equal(p1$get_exit_status(), 0) 9 | 10 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "file1"))) 11 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "dir"))) 12 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "dir", "file2"))) 13 | 14 | expect_equal(readLines(file.path(tmp2, basename(z$ex), "file1")), "file1") 15 | expect_equal( 16 | readLines(file.path(tmp2, basename(z$ex), "dir", "file2")), 17 | "file2" 18 | ) 19 | }) 20 | -------------------------------------------------------------------------------- /tests/testthat/test-unzip.R: -------------------------------------------------------------------------------- 1 | test_that("can unzip all", { 2 | z <- make_a_zip() 3 | 4 | tmp2 <- test_temp_dir() 5 | zip::unzip(z$zip, exdir = tmp2) 6 | 7 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "file1"))) 8 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "dir"))) 9 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "dir", "file2"))) 10 | 11 | expect_equal(readLines(file.path(tmp2, basename(z$ex), "file1")), "file1") 12 | expect_equal( 13 | readLines(file.path(tmp2, basename(z$ex), "dir", "file2")), 14 | "file2" 15 | ) 16 | }) 17 | 18 | test_that("unzip creates exdir if needed", { 19 | z <- make_a_zip() 20 | 21 | tmp2 <- test_temp_dir(create = FALSE) 22 | expect_false(file.exists(tmp2)) 23 | zip::unzip(z$zip, exdir = tmp2) 24 | 25 | expect_true(file.exists(tmp2)) 26 | 27 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "file1"))) 28 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "dir"))) 29 | expect_true(file.exists(file.path(tmp2, basename(z$ex), "dir", "file2"))) 30 | 31 | expect_equal(readLines(file.path(tmp2, basename(z$ex), "file1")), "file1") 32 | expect_equal( 33 | readLines(file.path(tmp2, basename(z$ex), "dir", "file2")), 34 | "file2" 35 | ) 36 | }) 37 | 38 | test_that("unzip certain files only", { 39 | z <- make_a_zip() 40 | 41 | ## No files 42 | tmp2 <- test_temp_dir() 43 | zip::unzip(z$zip, character(), exdir = tmp2) 44 | expect_true(file.exists(tmp2)) 45 | expect_equal(dir(tmp2), character()) 46 | 47 | ## File in directory 48 | tmp3 <- test_temp_dir() 49 | zip::unzip(z$zip, paste0(basename(z$ex), "/", "file1"), exdir = tmp3) 50 | expect_true(file.exists(tmp3)) 51 | expect_true(file.exists(file.path(tmp3, basename(z$ex), "file1"))) 52 | expect_false(file.exists(file.path(tmp3, basename(z$ex), "dir"))) 53 | expect_equal(readLines(file.path(tmp3, basename(z$ex), "file1")), "file1") 54 | 55 | ## Only file(s) in root 56 | f <- test_temp_file() 57 | cat("foobar\n", file = f) 58 | zip <- test_temp_file(".zip") 59 | zipr(zip, f) 60 | 61 | tmp4 <- test_temp_dir() 62 | zip::unzip(zip, paste0(basename(f)), exdir = tmp4) 63 | expect_true(file.exists(tmp4)) 64 | expect_equal(dir(tmp4), basename(f)) 65 | expect_equal(readLines(file.path(tmp4, basename(f))), "foobar") 66 | 67 | ## Directory only 68 | tmp5 <- test_temp_dir() 69 | zip::unzip(z$zip, paste0(basename(z$ex), "/dir/"), exdir = tmp5) 70 | expect_true(file.exists(tmp5)) 71 | expect_true(file.exists(file.path(tmp5, basename(z$ex), "dir"))) 72 | 73 | ## Files and dirs 74 | tmp6 <- test_temp_dir() 75 | zip::unzip( 76 | z$zip, 77 | paste0(basename(z$ex), c("/dir/file2", "/file1")), 78 | exdir = tmp6 79 | ) 80 | 81 | expect_true(file.exists(file.path(tmp6, basename(z$ex), "file1"))) 82 | expect_true(file.exists(file.path(tmp6, basename(z$ex), "dir"))) 83 | expect_true(file.exists(file.path(tmp6, basename(z$ex), "dir", "file2"))) 84 | 85 | expect_equal(readLines(file.path(tmp6, basename(z$ex), "file1")), "file1") 86 | expect_equal( 87 | readLines(file.path(tmp6, basename(z$ex), "dir", "file2")), 88 | "file2" 89 | ) 90 | }) 91 | 92 | test_that("unzip sets mtime correctly", { 93 | ## ten minutes earlier 94 | mtime <- Sys.time() - 60 * 10 95 | z <- make_a_zip(mtime = mtime) 96 | 97 | ## Some Windows file systems have a 2-second precision 98 | three <- as.difftime(3, units = "secs") 99 | expect_true(all(abs(zip_list(z$zip)$timestamp - mtime) < 3)) 100 | 101 | tmp2 <- test_temp_dir() 102 | zip::unzip(z$zip, exdir = tmp2) 103 | 104 | ok <- function(...) { 105 | t <- file.info(file.path(tmp2, basename(z$ex), ...))$mtime 106 | expect_true(abs(t - mtime) < 3) 107 | } 108 | 109 | ok("file1") 110 | ok("file11") 111 | ok("dir") 112 | ok("dir", "file2") 113 | ok("dir", "file3") 114 | }) 115 | 116 | test_that("overwrite is FALSE", { 117 | z <- make_a_zip() 118 | tmp <- test_temp_dir() 119 | zip::unzip(z$zip, exdir = tmp) 120 | zip::unzip(z$zip, exdir = tmp) 121 | expect_snapshot( 122 | error = TRUE, 123 | zip::unzip(z$zip, overwrite = FALSE, exdir = tmp), 124 | transform = function(x) { 125 | x <- transform_tempdir(x) 126 | x <- sub("test-dir-[^/]+/", "test-dir-/", x) 127 | x <- sub("test-file-[^.]+[.]", "test-file-.", x) 128 | x <- sub("\\", "/", x, fixed = TRUE) 129 | x 130 | } 131 | ) 132 | }) 133 | 134 | test_that("junkpaths is TRUE", { 135 | z <- make_a_zip() 136 | tmp <- test_temp_dir() 137 | zip::unzip(z$zip, exdir = tmp, junkpaths = TRUE) 138 | 139 | expect_true(file.exists(file.path(tmp, "file1"))) 140 | expect_true(file.exists(file.path(tmp, "file2"))) 141 | expect_true(file.exists(file.path(tmp, "file11"))) 142 | expect_true(file.exists(file.path(tmp, "file3"))) 143 | 144 | expect_false(file.exists(file.path(tmp, "dir"))) 145 | 146 | expect_equal(readLines(file.path(tmp, "file1")), "file1") 147 | expect_equal(readLines(file.path(tmp, "file2")), "file2") 148 | }) 149 | 150 | test_that("permissions as kept on Unix", { 151 | skip_on_os("windows") 152 | 153 | tmp <- test_temp_dir() 154 | Sys.chmod(tmp, "0777", FALSE) 155 | 156 | cat("foobar\n", file = f <- file.path(tmp, "file1")) 157 | Sys.chmod(f, "0400", FALSE) 158 | 159 | dir.create(f <- file.path(tmp, "dir")) 160 | Sys.chmod(f, "0700", FALSE) 161 | 162 | cat("foobar2\n", file = f <- file.path(tmp, "dir", "file2")) 163 | Sys.chmod(f, "0755", FALSE) 164 | 165 | cat("foobar3\n", file = f <- file.path(tmp, "dir", "file3")) 166 | Sys.chmod(f, "0777", FALSE) 167 | 168 | zip <- test_temp_file(".zip", create = FALSE) 169 | zipr(zip, tmp) 170 | 171 | tmp2 <- test_temp_dir() 172 | zip::unzip(zip, exdir = tmp2) 173 | 174 | check_perm <- function(mode, ...) { 175 | f <- file.path(tmp2, ...) 176 | expect_equal(file.info(f)$mode, as.octmode(mode)) 177 | } 178 | 179 | check_perm("0777", basename(tmp)) 180 | check_perm("0400", basename(tmp), "file1") 181 | check_perm("0700", basename(tmp), "dir") 182 | check_perm("0755", basename(tmp), "dir", "file2") 183 | check_perm("0777", basename(tmp), "dir", "file3") 184 | }) 185 | 186 | test_that("umask if no permissions", { 187 | msdos <- test_path("fixtures/msdos.zip") 188 | dir.create(tmp <- tempfile("zip-test-noperm")) 189 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 190 | 191 | zip::unzip(msdos, exdir = tmp) 192 | dsc <- file.path(tmp, "DESCRIPT") 193 | expect_true(file.exists(dsc)) 194 | 195 | if (.Platform$OS.type == "unix") { 196 | mode <- file.mode(dsc) 197 | umask <- system("umask", intern = TRUE) 198 | expect_equal(as.integer(format(mode)) + as.integer(umask), 666) 199 | } else { 200 | expect_true(TRUE) 201 | } 202 | }) 203 | 204 | test_that("symlinks on Unix", { 205 | skip_on_os("windows") 206 | symlink <- test_path("fixtures/symlink.zip") 207 | dir.create(tmp <- tempfile("zip-test-symlink")) 208 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 209 | 210 | zip::unzip(symlink, exdir = tmp) 211 | expect_true(file.exists(file.path(tmp, "a"))) 212 | expect_true(file.exists(file.path(tmp, "a", "foo"))) 213 | expect_true(file.exists(file.path(tmp, "a", "bar"))) 214 | expect_equal( 215 | Sys.readlink(file.path(tmp, "a", "bar")), 216 | "foo" 217 | ) 218 | }) 219 | -------------------------------------------------------------------------------- /tests/testthat/test-weird-paths.R: -------------------------------------------------------------------------------- 1 | test_that("warning for colon", { 2 | skip_on_os("windows") 3 | 4 | tmpzip <- tempfile("zip-test-colon-", fileext = ".zip") 5 | dir.create(tmp <- tempfile("zip-test-colon-")) 6 | on.exit(unlink(c(tmp, tmpzip), recursive = TRUE), add = TRUE) 7 | 8 | writeLines("boo", file.path(tmp, "bad:boy")) 9 | expect_warning( 10 | zip(tmpzip, tmp, mode = "cherry-pick"), 11 | "Some paths include a `:` character" 12 | ) 13 | 14 | expect_true(file.exists(tmpzip)) 15 | expect_equal( 16 | basename(zip_list(tmpzip)$filename[2]), 17 | "bad:boy" 18 | ) 19 | }) 20 | 21 | test_that("absolute paths lose leading /", { 22 | skip_on_os("windows") 23 | 24 | tmpzip <- tempfile("zip-test-sbs-", fileext = ".zip") 25 | dir.create(tmp <- tempfile("zip-test-abs-")) 26 | on.exit(unlink(c(tmp, tmpzip), recursive = TRUE), add = TRUE) 27 | 28 | writeLines("boo", file.path(tmp, "bad")) 29 | expect_warning( 30 | zip(tmpzip, tmp, mode = "mirror"), 31 | "Dropping leading `/` from paths" 32 | ) 33 | 34 | expect_true(file.exists(tmpzip)) 35 | expect_equal( 36 | paste0("/", zip_list(tmpzip)$filename[1]), 37 | paste0(tmp, "/") 38 | ) 39 | }) 40 | 41 | test_that("backslash is an error", { 42 | skip_on_os("windows") 43 | 44 | tmpzip <- tempfile("zip-test-bs-", fileext = ".zip") 45 | dir.create(tmp <- tempfile("zip-test-bs-")) 46 | on.exit(unlink(c(tmp, tmpzip), recursive = TRUE), add = TRUE) 47 | 48 | writeLines("boo", file.path(tmp, "real\\bad")) 49 | expect_snapshot( 50 | error = TRUE, 51 | zip(tmpzip, tmp, mode = "cherry-pick"), 52 | transform = function(x) { 53 | x <- transform_tempdir(x) 54 | x <- gsub("zip-test-bs-[^./]+\\b", "zip-test-bs-", x) 55 | x <- sub("file zip.c:[0-9]+", "file zip.c:", x) 56 | x 57 | } 58 | ) 59 | }) 60 | 61 | test_that("extracting absolute path", { 62 | abs <- test_path("fixtures", "abs.zip") 63 | dir.create(tmp <- tempfile("zip-test-xabs-")) 64 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 65 | 66 | unzip(abs, exdir = tmp) 67 | expect_true(file.exists(file.path(tmp, "tmp"))) 68 | expect_true(file.exists(file.path(tmp, "tmp", "boo"))) 69 | expect_equal( 70 | readLines(file.path(tmp, "tmp", "boo")), 71 | "boo" 72 | ) 73 | }) 74 | -------------------------------------------------------------------------------- /tests/testthat/test-zip-list.R: -------------------------------------------------------------------------------- 1 | test_that("can list a zip file", { 2 | dir.create(tmp <- tempfile()) 3 | cat("first file", file = file.path(tmp, "file1")) 4 | cat("second file", file = file.path(tmp, "file2")) 5 | 6 | zipfile <- tempfile(fileext = ".zip") 7 | 8 | expect_silent( 9 | withr::with_dir( 10 | dirname(tmp), 11 | zipr(zipfile, basename(tmp)) 12 | ) 13 | ) 14 | 15 | expect_true(file.exists(zipfile)) 16 | 17 | list <- zip_list(zipfile) 18 | expect_equal( 19 | basename(list$filename), 20 | c(basename(tmp), "file1", "file2") 21 | ) 22 | 23 | expect_equal( 24 | colnames(list), 25 | c( 26 | "filename", 27 | "compressed_size", 28 | "uncompressed_size", 29 | "timestamp", 30 | "permissions", 31 | "crc32", 32 | "offset", 33 | "type" 34 | ) 35 | ) 36 | expect_true(is.numeric(list$offset)) 37 | expect_true(inherits(list$crc32, 'hexmode')) 38 | }) 39 | 40 | test_that("symlinks", { 41 | options(width = 200) 42 | zf <- test_path("fixtures/symlink.zip") 43 | expect_snapshot(zip_list(zf)$type) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/testthat/test-zip-process.R: -------------------------------------------------------------------------------- 1 | test_that("zip_process", { 2 | z <- make_a_zip() 3 | 4 | zip2 <- test_temp_file(".zip") 5 | p <- zip_process()$new(zip2, z$ex) 6 | p$wait(2000) 7 | p$kill() 8 | 9 | expect_equal(p$get_exit_status(), 0L) 10 | expect_equal(zip_list(z$zip), zip_list(zip2)) 11 | }) 12 | 13 | test_that("can omit directories", { 14 | z <- make_a_zip(include_directories = FALSE) 15 | 16 | zip2 <- test_temp_file(".zip") 17 | p <- zip_process()$new(zip2, z$ex, include_directories = FALSE) 18 | p$wait(2000) 19 | p$kill() 20 | 21 | expect_equal(p$get_exit_status(), 0L) 22 | expect_equal(zip_list(z$zip), zip_list(zip2)) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/testthat/test-zip.R: -------------------------------------------------------------------------------- 1 | test_that("can compress single directory", { 2 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 3 | 4 | dir.create(tmp <- tempfile()) 5 | cat("first file", file = file.path(tmp, "file1")) 6 | cat("second file", file = file.path(tmp, "file2")) 7 | 8 | zipfile <- tempfile(fileext = ".zip") 9 | 10 | withr::with_dir( 11 | dirname(tmp), 12 | zip(zipfile, basename(tmp)) 13 | ) 14 | 15 | expect_true(file.exists(zipfile)) 16 | 17 | list <- zip_list(zipfile) 18 | expect_equal( 19 | list$filename, 20 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 21 | ) 22 | }) 23 | 24 | test_that("can compress single file", { 25 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 26 | 27 | tmp <- tempfile() 28 | cat("compress this if you can!", file = tmp) 29 | 30 | zipfile <- tempfile(fileext = ".zip") 31 | 32 | withr::with_dir( 33 | dirname(tmp), 34 | zip(zipfile, basename(tmp)) 35 | ) 36 | 37 | expect_true(file.exists(zipfile)) 38 | 39 | list <- zip_list(zipfile) 40 | expect_equal(list$filename, basename(tmp)) 41 | }) 42 | 43 | test_that("can compress multiple files", { 44 | on.exit(try(unlink(c(zipfile, tmp1, tmp2), recursive = TRUE))) 45 | 46 | cat("compress this if you can!", file = tmp1 <- tempfile()) 47 | cat("or even this one", file = tmp2 <- tempfile()) 48 | 49 | zipfile <- tempfile(fileext = ".zip") 50 | 51 | withr::with_dir( 52 | dirname(tmp1), 53 | zip(zipfile, basename(c(tmp1, tmp2))) 54 | ) 55 | 56 | expect_true(file.exists(zipfile)) 57 | 58 | list <- zip_list(zipfile) 59 | expect_equal(list$filename, basename(c(tmp1, tmp2))) 60 | }) 61 | 62 | test_that("can compress multiple directories", { 63 | on.exit(try(unlink(c(zipfile, tmp1, tmp2), recursive = TRUE)), add = TRUE) 64 | 65 | dir.create(tmp1 <- tempfile()) 66 | dir.create(tmp2 <- tempfile()) 67 | cat("first file\n", file = file.path(tmp1, "file1")) 68 | cat("second file\n", file = file.path(tmp1, "file2")) 69 | cat("third file\n", file = file.path(tmp2, "file3")) 70 | cat("fourth file\n", file = file.path(tmp2, "file4")) 71 | 72 | zipfile <- tempfile(fileext = ".zip") 73 | 74 | withr::with_dir( 75 | dirname(tmp1), 76 | zip(zipfile, basename(c(tmp1, tmp2))) 77 | ) 78 | 79 | expect_true(file.exists(zipfile)) 80 | 81 | list <- zip_list(zipfile) 82 | expect_equal( 83 | list$filename, 84 | c( 85 | bns(tmp1), 86 | file.path(basename(tmp1), c("file1", "file2")), 87 | bns(tmp2), 88 | file.path(basename(tmp2), c("file3", "file4")) 89 | ) 90 | ) 91 | 92 | on.exit(try(unlink(c(tmp3), recursive = TRUE)), add = TRUE) 93 | dir.create(tmp3 <- tempfile()) 94 | utils::unzip(zipfile, exdir = tmp3) 95 | expect_true(file.info(file.path(tmp3, basename(tmp1)))$isdir) 96 | expect_true(file.info(file.path(tmp3, basename(tmp2)))$isdir) 97 | expect_equal( 98 | readLines(file.path(tmp1, "file1")), 99 | readLines(file.path(tmp3, basename(tmp1), "file1")) 100 | ) 101 | expect_equal( 102 | readLines(file.path(tmp1, "file2")), 103 | readLines(file.path(tmp3, basename(tmp1), "file2")) 104 | ) 105 | expect_equal( 106 | readLines(file.path(tmp2, "file3")), 107 | readLines(file.path(tmp3, basename(tmp2), "file3")) 108 | ) 109 | expect_equal( 110 | readLines(file.path(tmp2, "file4")), 111 | readLines(file.path(tmp3, basename(tmp2), "file4")) 112 | ) 113 | }) 114 | 115 | test_that("can compress files and directories", { 116 | on.exit(try(unlink(c(zipfile, tmp, file1, file2), recursive = TRUE))) 117 | 118 | dir.create(tmp <- tempfile()) 119 | cat("first file", file = file.path(tmp, "file1")) 120 | cat("second file", file = file.path(tmp, "file2")) 121 | cat("third file", file = file1 <- tempfile()) 122 | cat("fourth file", file = file2 <- tempfile()) 123 | 124 | zipfile <- tempfile(fileext = ".zip") 125 | 126 | withr::with_dir( 127 | dirname(tmp), 128 | zip(zipfile, basename(c(file1, tmp, file2))) 129 | ) 130 | 131 | expect_true(file.exists(zipfile)) 132 | 133 | list <- zip_list(zipfile) 134 | expect_equal( 135 | list$filename, 136 | c( 137 | basename(file1), 138 | bns(tmp), 139 | file.path(basename(tmp), c("file1", "file2")), 140 | basename(file2) 141 | ) 142 | ) 143 | }) 144 | 145 | test_that("warning for directories in non-recursive mode", { 146 | on.exit(try(unlink(c(zipfile, tmp, file1, file2), recursive = TRUE))) 147 | 148 | dir.create(tmp <- tempfile()) 149 | cat("first file", file = file.path(tmp, "file1")) 150 | cat("second file", file = file.path(tmp, "file2")) 151 | cat("third file", file = file1 <- tempfile()) 152 | cat("fourth file", file = file2 <- tempfile()) 153 | 154 | zipfile <- tempfile(fileext = ".zip") 155 | 156 | expect_warning( 157 | withr::with_dir( 158 | dirname(tmp), 159 | zip(zipfile, basename(c(file1, tmp, file2)), recurse = FALSE) 160 | ), 161 | "directories ignored" 162 | ) 163 | 164 | expect_true(file.exists(zipfile)) 165 | 166 | list <- zip_list(zipfile) 167 | expect_equal( 168 | list$filename, 169 | c(basename(file1), basename(file2)) 170 | ) 171 | }) 172 | 173 | test_that("compression level is used", { 174 | on.exit(try(unlink(c(zipfile1, zipfile2, file), recursive = TRUE))) 175 | 176 | tmp <- tempfile() 177 | write(1:10000, file = file <- tempfile()) 178 | 179 | zipfile1 <- tempfile(fileext = ".zip") 180 | zipfile2 <- tempfile(fileext = ".zip") 181 | 182 | withr::with_dir( 183 | dirname(file), 184 | zip(zipfile1, basename(file), compression_level = 1) 185 | ) 186 | 187 | withr::with_dir( 188 | dirname(file), 189 | zip(zipfile2, basename(file), compression_level = 9) 190 | ) 191 | 192 | expect_true(file.exists(zipfile1)) 193 | expect_true(file.exists(zipfile2)) 194 | 195 | list <- zip_list(zipfile1) 196 | expect_equal(list$filename, basename(file)) 197 | 198 | list <- zip_list(zipfile2) 199 | expect_equal(list$filename, basename(file)) 200 | 201 | expect_true(file.info(zipfile1)$size <= file.info(zipfile2)$size) 202 | }) 203 | 204 | test_that("can append a directory to an archive", { 205 | on.exit(try(unlink(c(zipfile, tmp, tmp2), recursive = TRUE))) 206 | 207 | dir.create(tmp <- tempfile()) 208 | cat("first file", file = file.path(tmp, "file1")) 209 | cat("second file", file = file.path(tmp, "file2")) 210 | 211 | zipfile <- tempfile(fileext = ".zip") 212 | 213 | withr::with_dir( 214 | dirname(tmp), 215 | zip(zipfile, basename(tmp)) 216 | ) 217 | 218 | expect_true(file.exists(zipfile)) 219 | 220 | list <- zip_list(zipfile) 221 | expect_equal( 222 | list$filename, 223 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 224 | ) 225 | 226 | dir.create(tmp2 <- tempfile()) 227 | cat("first file2", file = file.path(tmp2, "file3")) 228 | cat("second file2", file = file.path(tmp2, "file4")) 229 | 230 | withr::with_dir( 231 | dirname(tmp), 232 | zip_append(zipfile, basename(tmp2)) 233 | ) 234 | 235 | list <- zip_list(zipfile) 236 | expect_equal( 237 | list$filename, 238 | c( 239 | bns(tmp), 240 | file.path(basename(tmp), c("file1", "file2")), 241 | bns(tmp2), 242 | file.path(basename(tmp2), c("file3", "file4")) 243 | ) 244 | ) 245 | }) 246 | 247 | test_that("can append a file to an archive", { 248 | on.exit(try(unlink(c(zipfile, tmp, file1), recursive = TRUE))) 249 | 250 | dir.create(tmp <- tempfile()) 251 | cat("first file", file = file.path(tmp, "file1")) 252 | cat("second file", file = file.path(tmp, "file2")) 253 | 254 | zipfile <- tempfile(fileext = ".zip") 255 | 256 | withr::with_dir( 257 | dirname(tmp), 258 | zip(zipfile, basename(tmp)) 259 | ) 260 | 261 | expect_true(file.exists(zipfile)) 262 | 263 | list <- zip_list(zipfile) 264 | expect_equal( 265 | list$filename, 266 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 267 | ) 268 | 269 | cat("first file2", file = file1 <- tempfile()) 270 | 271 | withr::with_dir( 272 | dirname(tmp), 273 | zip_append(zipfile, basename(file1)) 274 | ) 275 | 276 | list <- zip_list(zipfile) 277 | expect_equal( 278 | list$filename, 279 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2")), basename(file1)) 280 | ) 281 | }) 282 | 283 | test_that("can append files and directories to an archive", { 284 | on.exit(try(unlink(c(zipfile, tmp, tmp2, file1), recursive = TRUE))) 285 | 286 | dir.create(tmp <- tempfile()) 287 | cat("first file", file = file.path(tmp, "file1")) 288 | cat("second file", file = file.path(tmp, "file2")) 289 | 290 | zipfile <- tempfile(fileext = ".zip") 291 | 292 | withr::with_dir( 293 | dirname(tmp), 294 | zip(zipfile, basename(tmp)) 295 | ) 296 | 297 | expect_true(file.exists(zipfile)) 298 | 299 | list <- zip_list(zipfile) 300 | expect_equal( 301 | list$filename, 302 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 303 | ) 304 | 305 | cat("first file2", file = file1 <- tempfile()) 306 | dir.create(tmp2 <- tempfile()) 307 | cat("another", file = file.path(tmp2, "file3")) 308 | cat("and another", file = file.path(tmp2, "file4")) 309 | 310 | withr::with_dir( 311 | dirname(tmp), 312 | zip_append(zipfile, basename(c(file1, tmp2))) 313 | ) 314 | 315 | list <- zip_list(zipfile) 316 | expect_equal( 317 | list$filename, 318 | c( 319 | bns(tmp), 320 | file.path(basename(tmp), c("file1", "file2")), 321 | basename(file1), 322 | bns(tmp2), 323 | file.path(basename(tmp2), c("file3", "file4")) 324 | ) 325 | ) 326 | }) 327 | 328 | test_that("empty directories are archived as directories", { 329 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE)), add = TRUE) 330 | dir.create(tmp <- tempfile()) 331 | zipfile <- tempfile(fileext = ".zip") 332 | 333 | dir.create(file.path(tmp, "foo", "bar"), recursive = TRUE) 334 | dir.create(file.path(tmp, "foo", "bar2")) 335 | cat("contents\n", file = file.path(tmp, "foo", "file1")) 336 | 337 | withr::with_dir( 338 | dirname(tmp), 339 | zip(zipfile, basename(tmp)) 340 | ) 341 | 342 | bt <- basename(tmp) 343 | list <- zip_list(zipfile) 344 | expect_equal( 345 | list$filename, 346 | c( 347 | paste0(bt, "/"), 348 | paste0(bt, "/foo/"), 349 | paste0(bt, "/foo/bar/"), 350 | paste0(bt, "/foo/bar2/"), 351 | paste0(bt, "/foo/file1") 352 | ) 353 | ) 354 | 355 | on.exit(unlink(tmp2, recursive = TRUE), add = TRUE) 356 | dir.create(tmp2 <- tempfile()) 357 | utils::unzip(zipfile, exdir = tmp2) 358 | files <- sort(dir(tmp2, recursive = TRUE, include.dirs = TRUE)) 359 | expect_equal( 360 | files, 361 | c( 362 | bt, 363 | file.path(bt, "foo"), 364 | file.path(bt, "foo", "bar"), 365 | file.path(bt, "foo", "bar2"), 366 | file.path(bt, "foo", "file1") 367 | ) 368 | ) 369 | 370 | expect_equal( 371 | file.info(file.path(tmp2, files))$isdir, 372 | c(TRUE, TRUE, TRUE, TRUE, FALSE) 373 | ) 374 | 375 | expect_equal(readLines(file.path(tmp2, bt, "foo", "file1")), "contents") 376 | }) 377 | 378 | test_that("warn for relative paths", { 379 | on.exit(try(unlink(tmp, recursive = TRUE)), add = TRUE) 380 | dir.create(tmp <- tempfile()) 381 | 382 | dir.create(file.path(tmp, "foo")) 383 | dir.create(file.path(tmp, "foo", "bar")) 384 | 385 | on.exit(unlink(zipfile), add = TRUE) 386 | zipfile <- tempfile(fileext = ".zip") 387 | 388 | withr::with_dir( 389 | file.path(tmp, "foo"), 390 | expect_warning(zip(zipfile, file.path("..", "foo"))) 391 | ) 392 | 393 | withr::with_dir( 394 | file.path(tmp, "foo"), 395 | expect_warning(zip(zipfile, file.path("..", "foo", "bar"))) 396 | ) 397 | 398 | withr::with_dir( 399 | file.path(tmp, "foo"), 400 | expect_warning(zip(zipfile, ".")) 401 | ) 402 | }) 403 | 404 | test_that("example", { 405 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 406 | dir.create(tmp <- tempfile()) 407 | 408 | withr::with_dir( 409 | tmp, 410 | { 411 | dir.create("foo") 412 | dir.create(file.path("foo", "bar")) 413 | dir.create(file.path("foo", "bar2")) 414 | dir.create("foo2") 415 | 416 | cat("contents\n", file = file.path("foo", "bar", "file1")) 417 | cat("contents\n", file = file.path("foo", "bar2", "file2")) 418 | cat("contents\n", file = file.path("foo2", "file3")) 419 | 420 | setwd("foo") 421 | tz <- c(file.path("bar", "file1"), "bar2", file.path("..", "foo2")) 422 | expect_warning(zip("x.zip", tz)) 423 | expect_equal( 424 | zip_list("x.zip")$filename, 425 | c( 426 | file.path("bar", "file1"), 427 | "bar2/", 428 | file.path("bar2", "file2"), 429 | paste0("../foo2/"), 430 | file.path("..", "foo2", "file3") 431 | ) 432 | ) 433 | 434 | zipr("xr.zip", tz) 435 | expect_equal( 436 | zip_list("xr.zip")$filename, 437 | c( 438 | "file1", 439 | "bar2/", 440 | file.path("bar2", "file2"), 441 | "foo2/", 442 | file.path("foo2", "file3") 443 | ) 444 | ) 445 | } 446 | ) 447 | }) 448 | 449 | test_that("can omit directories", { 450 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 451 | 452 | dir.create(tmp <- tempfile()) 453 | cat("first file", file = file.path(tmp, "file1")) 454 | cat("second file", file = file.path(tmp, "file2")) 455 | 456 | zipfile <- tempfile(fileext = ".zip") 457 | 458 | withr::with_dir( 459 | dirname(tmp), 460 | zip(zipfile, basename(tmp), include_directories = FALSE) 461 | ) 462 | 463 | expect_true(file.exists(zipfile)) 464 | 465 | list <- zip_list(zipfile) 466 | expect_equal( 467 | list$filename, 468 | file.path(basename(tmp), c("file1", "file2")) 469 | ) 470 | }) 471 | -------------------------------------------------------------------------------- /tests/testthat/test-zipr.R: -------------------------------------------------------------------------------- 1 | test_that("can compress single directory", { 2 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 3 | 4 | dir.create(tmp <- tempfile()) 5 | cat("first file", file = file.path(tmp, "file1")) 6 | cat("second file", file = file.path(tmp, "file2")) 7 | 8 | zipfile <- tempfile(fileext = ".zip") 9 | 10 | expect_silent( 11 | withr::with_dir( 12 | dirname(tmp), 13 | zipr(zipfile, basename(tmp)) 14 | ) 15 | ) 16 | 17 | expect_true(file.exists(zipfile)) 18 | 19 | list <- zip_list(zipfile) 20 | expect_equal( 21 | list$filename, 22 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 23 | ) 24 | }) 25 | 26 | test_that("can compress single file", { 27 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 28 | 29 | tmp <- tempfile() 30 | cat("compress this if you can!", file = tmp) 31 | 32 | zipfile <- tempfile(fileext = ".zip") 33 | 34 | expect_silent( 35 | withr::with_dir( 36 | dirname(tmp), 37 | zipr(zipfile, basename(tmp)) 38 | ) 39 | ) 40 | 41 | expect_true(file.exists(zipfile)) 42 | 43 | list <- zip_list(zipfile) 44 | expect_equal(list$filename, basename(tmp)) 45 | }) 46 | 47 | test_that("can compress multiple files", { 48 | on.exit(try(unlink(c(zipfile, tmp1, tmp2), recursive = TRUE))) 49 | 50 | cat("compress this if you can!", file = tmp1 <- tempfile()) 51 | cat("or even this one", file = tmp2 <- tempfile()) 52 | 53 | zipfile <- tempfile(fileext = ".zip") 54 | 55 | expect_silent( 56 | withr::with_dir( 57 | dirname(tmp1), 58 | zipr(zipfile, basename(c(tmp1, tmp2))) 59 | ) 60 | ) 61 | 62 | expect_true(file.exists(zipfile)) 63 | 64 | list <- zip_list(zipfile) 65 | expect_equal(list$filename, basename(c(tmp1, tmp2))) 66 | }) 67 | 68 | test_that("can compress multiple directories", { 69 | on.exit(try(unlink(c(zipfile, tmp1, tmp2), recursive = TRUE))) 70 | 71 | dir.create(tmp1 <- tempfile()) 72 | dir.create(tmp2 <- tempfile()) 73 | cat("first file", file = file.path(tmp1, "file1")) 74 | cat("second file", file = file.path(tmp1, "file2")) 75 | cat("third file", file = file.path(tmp2, "file3")) 76 | cat("fourth file", file = file.path(tmp2, "file4")) 77 | 78 | zipfile <- tempfile(fileext = ".zip") 79 | 80 | expect_silent( 81 | withr::with_dir( 82 | dirname(tmp1), 83 | zipr(zipfile, basename(c(tmp1, tmp2))) 84 | ) 85 | ) 86 | 87 | expect_true(file.exists(zipfile)) 88 | 89 | list <- zip_list(zipfile) 90 | expect_equal( 91 | list$filename, 92 | c( 93 | bns(tmp1), 94 | file.path(basename(tmp1), c("file1", "file2")), 95 | bns(tmp2), 96 | file.path(basename(tmp2), c("file3", "file4")) 97 | ) 98 | ) 99 | }) 100 | 101 | test_that("can compress files and directories", { 102 | on.exit(try(unlink(c(zipfile, tmp, file1, file2), recursive = TRUE))) 103 | 104 | dir.create(tmp <- tempfile()) 105 | cat("first file", file = file.path(tmp, "file1")) 106 | cat("second file", file = file.path(tmp, "file2")) 107 | cat("third file", file = file1 <- tempfile()) 108 | cat("fourth file", file = file2 <- tempfile()) 109 | 110 | zipfile <- tempfile(fileext = ".zip") 111 | 112 | expect_silent( 113 | withr::with_dir( 114 | dirname(tmp), 115 | zipr(zipfile, basename(c(file1, tmp, file2))) 116 | ) 117 | ) 118 | 119 | expect_true(file.exists(zipfile)) 120 | 121 | list <- zip_list(zipfile) 122 | expect_equal( 123 | list$filename, 124 | c( 125 | basename(file1), 126 | bns(tmp), 127 | file.path(basename(tmp), c("file1", "file2")), 128 | basename(file2) 129 | ) 130 | ) 131 | }) 132 | 133 | test_that("warning for directories in non-recursive mode", { 134 | on.exit(try(unlink(c(zipfile, tmp, file1, file2), recursive = TRUE))) 135 | 136 | dir.create(tmp <- tempfile()) 137 | cat("first file", file = file.path(tmp, "file1")) 138 | cat("second file", file = file.path(tmp, "file2")) 139 | cat("third file", file = file1 <- tempfile()) 140 | cat("fourth file", file = file2 <- tempfile()) 141 | 142 | zipfile <- tempfile(fileext = ".zip") 143 | 144 | expect_warning( 145 | withr::with_dir( 146 | dirname(tmp), 147 | zipr(zipfile, basename(c(file1, tmp, file2)), recurse = FALSE) 148 | ), 149 | "directories ignored" 150 | ) 151 | 152 | expect_true(file.exists(zipfile)) 153 | 154 | list <- zip_list(zipfile) 155 | expect_equal( 156 | list$filename, 157 | c(basename(file1), basename(file2)) 158 | ) 159 | }) 160 | 161 | test_that("compression level is used", { 162 | on.exit(try(unlink(c(zipfile1, zipfile2, file), recursive = TRUE))) 163 | 164 | tmp <- tempfile() 165 | write(1:10000, file = file <- tempfile()) 166 | 167 | zipfile1 <- tempfile(fileext = ".zip") 168 | zipfile2 <- tempfile(fileext = ".zip") 169 | 170 | expect_silent( 171 | withr::with_dir( 172 | dirname(file), 173 | zipr(zipfile1, basename(file), compression_level = 1) 174 | ) 175 | ) 176 | 177 | expect_silent( 178 | withr::with_dir( 179 | dirname(file), 180 | zipr(zipfile2, basename(file), compression_level = 9) 181 | ) 182 | ) 183 | 184 | expect_true(file.exists(zipfile1)) 185 | expect_true(file.exists(zipfile2)) 186 | 187 | list <- zip_list(zipfile1) 188 | expect_equal(list$filename, basename(file)) 189 | 190 | list <- zip_list(zipfile2) 191 | expect_equal(list$filename, basename(file)) 192 | 193 | expect_true(file.info(zipfile1)$size <= file.info(zipfile2)$size) 194 | }) 195 | 196 | test_that("can append a directory to an archive", { 197 | on.exit(try(unlink(c(zipfile, tmp, tmp2), recursive = TRUE))) 198 | 199 | dir.create(tmp <- tempfile()) 200 | cat("first file", file = file.path(tmp, "file1")) 201 | cat("second file", file = file.path(tmp, "file2")) 202 | 203 | zipfile <- tempfile(fileext = ".zip") 204 | 205 | expect_silent( 206 | withr::with_dir( 207 | dirname(tmp), 208 | zipr(zipfile, basename(tmp)) 209 | ) 210 | ) 211 | 212 | expect_true(file.exists(zipfile)) 213 | 214 | list <- zip_list(zipfile) 215 | expect_equal( 216 | list$filename, 217 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 218 | ) 219 | 220 | dir.create(tmp2 <- tempfile()) 221 | cat("first file2", file = file.path(tmp2, "file3")) 222 | cat("second file2", file = file.path(tmp2, "file4")) 223 | 224 | expect_silent( 225 | withr::with_dir( 226 | dirname(tmp), 227 | zipr_append(zipfile, basename(tmp2)) 228 | ) 229 | ) 230 | 231 | list <- zip_list(zipfile) 232 | expect_equal( 233 | list$filename, 234 | c( 235 | bns(tmp), 236 | file.path(basename(tmp), c("file1", "file2")), 237 | bns(tmp2), 238 | file.path(basename(tmp2), c("file3", "file4")) 239 | ) 240 | ) 241 | }) 242 | 243 | test_that("can append a file to an archive", { 244 | on.exit(try(unlink(c(zipfile, tmp, file1), recursive = TRUE))) 245 | 246 | dir.create(tmp <- tempfile()) 247 | cat("first file", file = file.path(tmp, "file1")) 248 | cat("second file", file = file.path(tmp, "file2")) 249 | 250 | zipfile <- tempfile(fileext = ".zip") 251 | 252 | expect_silent( 253 | withr::with_dir( 254 | dirname(tmp), 255 | zipr(zipfile, basename(tmp)) 256 | ) 257 | ) 258 | 259 | expect_true(file.exists(zipfile)) 260 | 261 | list <- zip_list(zipfile) 262 | expect_equal( 263 | list$filename, 264 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 265 | ) 266 | 267 | cat("first file2", file = file1 <- tempfile()) 268 | 269 | expect_silent( 270 | withr::with_dir( 271 | dirname(tmp), 272 | zipr_append(zipfile, basename(file1)) 273 | ) 274 | ) 275 | 276 | list <- zip_list(zipfile) 277 | expect_equal( 278 | list$filename, 279 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2")), basename(file1)) 280 | ) 281 | }) 282 | 283 | test_that("can append files and directories to an archive", { 284 | on.exit(try(unlink(c(zipfile, tmp, tmp2, file1), recursive = TRUE))) 285 | 286 | dir.create(tmp <- tempfile()) 287 | cat("first file", file = file.path(tmp, "file1")) 288 | cat("second file", file = file.path(tmp, "file2")) 289 | 290 | zipfile <- tempfile(fileext = ".zip") 291 | 292 | expect_silent( 293 | withr::with_dir( 294 | dirname(tmp), 295 | zipr(zipfile, basename(tmp)) 296 | ) 297 | ) 298 | 299 | expect_true(file.exists(zipfile)) 300 | 301 | list <- zip_list(zipfile) 302 | expect_equal( 303 | list$filename, 304 | c(bns(tmp), file.path(basename(tmp), c("file1", "file2"))) 305 | ) 306 | 307 | cat("first file2", file = file1 <- tempfile()) 308 | dir.create(tmp2 <- tempfile()) 309 | cat("another", file = file.path(tmp2, "file3")) 310 | cat("and another", file = file.path(tmp2, "file4")) 311 | 312 | expect_silent( 313 | withr::with_dir( 314 | dirname(tmp), 315 | zipr_append(zipfile, basename(c(file1, tmp2))) 316 | ) 317 | ) 318 | 319 | list <- zip_list(zipfile) 320 | expect_equal( 321 | list$filename, 322 | c( 323 | bns(tmp), 324 | file.path(basename(tmp), c("file1", "file2")), 325 | basename(file1), 326 | bns(tmp2), 327 | file.path(basename(tmp2), c("file3", "file4")) 328 | ) 329 | ) 330 | }) 331 | 332 | test_that("empty directories are archived as directories", { 333 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE)), add = TRUE) 334 | dir.create(tmp <- tempfile()) 335 | zipfile <- tempfile(fileext = ".zip") 336 | 337 | dir.create(file.path(tmp, "foo", "bar"), recursive = TRUE) 338 | dir.create(file.path(tmp, "foo", "bar2")) 339 | cat("contents\n", file = file.path(tmp, "foo", "file1")) 340 | 341 | expect_silent( 342 | withr::with_dir( 343 | dirname(tmp), 344 | zipr(zipfile, basename(tmp)) 345 | ) 346 | ) 347 | 348 | bt <- basename(tmp) 349 | list <- zip_list(zipfile) 350 | expect_equal( 351 | list$filename, 352 | c( 353 | paste0(bt, "/"), 354 | paste0(bt, "/foo/"), 355 | paste0(bt, "/foo/bar/"), 356 | paste0(bt, "/foo/bar2/"), 357 | paste0(bt, "/foo/file1") 358 | ) 359 | ) 360 | 361 | on.exit(unlink(tmp2, recursive = TRUE), add = TRUE) 362 | dir.create(tmp2 <- tempfile()) 363 | utils::unzip(zipfile, exdir = tmp2) 364 | files <- sort(dir(tmp2, recursive = TRUE, include.dirs = TRUE)) 365 | expect_equal( 366 | files, 367 | c( 368 | bt, 369 | file.path(bt, "foo"), 370 | file.path(bt, "foo", "bar"), 371 | file.path(bt, "foo", "bar2"), 372 | file.path(bt, "foo", "file1") 373 | ) 374 | ) 375 | 376 | expect_equal( 377 | file.info(file.path(tmp2, files))$isdir, 378 | c(TRUE, TRUE, TRUE, TRUE, FALSE) 379 | ) 380 | 381 | expect_equal(readLines(file.path(tmp2, bt, "foo", "file1")), "contents") 382 | }) 383 | 384 | test_that("Permissions are kept on Unix", { 385 | skip_on_os("windows") 386 | 387 | tmp <- test_temp_dir() 388 | Sys.chmod(tmp, "0777", FALSE) 389 | 390 | cat("foobar\n", file = f <- file.path(tmp, "file1")) 391 | Sys.chmod(f, "0400", FALSE) 392 | 393 | dir.create(f <- file.path(tmp, "dir")) 394 | Sys.chmod(f, "0700", FALSE) 395 | 396 | cat("foobar2\n", file = f <- file.path(tmp, "dir", "file2")) 397 | Sys.chmod(f, "0755", FALSE) 398 | 399 | cat("foobar3\n", file = f <- file.path(tmp, "dir", "file3")) 400 | Sys.chmod(f, "0777", FALSE) 401 | 402 | zip <- test_temp_file(".zip", create = FALSE) 403 | zipr(zip, tmp) 404 | 405 | l <- zip_list(zip) 406 | 407 | check_perm <- function(name, mode) { 408 | w <- match(name, basename(l$filename)) 409 | expect_equal(l$permissions[w], as.octmode(mode)) 410 | } 411 | 412 | check_perm(basename(tmp), "0777") 413 | check_perm("file1", "0400") 414 | check_perm("dir", "0700") 415 | check_perm("file2", "0755") 416 | check_perm("file3", "0777") 417 | }) 418 | 419 | test_that("can omit directories", { 420 | on.exit(try(unlink(c(zipfile, tmp), recursive = TRUE))) 421 | 422 | dir.create(tmp <- tempfile()) 423 | cat("first file", file = file.path(tmp, "file1")) 424 | cat("second file", file = file.path(tmp, "file2")) 425 | 426 | zipfile <- tempfile(fileext = ".zip") 427 | 428 | expect_silent( 429 | withr::with_dir( 430 | dirname(tmp), 431 | zipr(zipfile, basename(tmp), include_directories = FALSE) 432 | ) 433 | ) 434 | 435 | expect_true(file.exists(zipfile)) 436 | 437 | list <- zip_list(zipfile) 438 | expect_equal( 439 | list$filename, 440 | file.path(basename(tmp), c("file1", "file2")) 441 | ) 442 | }) 443 | -------------------------------------------------------------------------------- /tools/getzipexe.R: -------------------------------------------------------------------------------- 1 | if (getRversion() < "3.3.0") setInternet2() 2 | 3 | if (!file.exists("../tools/zip.exe")) { 4 | download.file( 5 | "https://github.com/rwinlib/zip/blob/master/zip.exe?raw=true", 6 | "../tools/zip.exe", 7 | quiet = TRUE, 8 | mode = "wb" 9 | ) 10 | } 11 | 12 | file.copy("../tools/zip.exe", "tools/zip.exe") 13 | -------------------------------------------------------------------------------- /zip.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | --------------------------------------------------------------------------------