├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ ├── custom-issue.md │ └── feature-request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── conventional-commits.yml │ ├── deny.yml │ ├── docs.yml │ ├── lint.yml │ ├── release.yml │ ├── reuse.yml │ └── scorecards.yml ├── .gitignore ├── .reuse └── dep5 ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── LICENSES ├── Apache-2.0.txt ├── CC-BY-4.0.txt ├── LicenseRef-file.txt └── MIT.txt ├── README-crate.md ├── README.md ├── SECURITY.md ├── clippy.toml ├── data └── tests │ ├── db-images-png │ ├── db-images-png-precompiled.mgc │ ├── db-python │ └── rust-logo-128x128-blk.png ├── deny.toml ├── examples └── file-ish.rs ├── release-plz.toml ├── rust-toolchain.toml └── src ├── ffi.rs └── lib.rs /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | - [ ] I have searched the [existing bug report issues](https://github.com/robo9k/rust-magic/labels/bug) similar ones. 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug 🪲 is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Run the Rust code '...' 18 | 2. With the inputs '....' 19 | 3. See error / backtrace 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Environment (please complete the following information):** 28 | - `magic` crate version: [e.g. v0.16.1] 29 | - `libmagic` version [e.g. 5.41] 30 | - Operating System [e.g. Ubuntu 22.04 x64] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Community Support 4 | url: https://github.com/robo9k/rust-magic/discussions 5 | about: Please ask and answer questions here. 6 | - name: Security policy 7 | url: https://github.com/robo9k/rust-magic/security/policy 8 | about: Please view the security policy here. 9 | - name: magic-sys crate 10 | url: https://github.com/robo9k/rust-magic-sys 11 | about: Please find the separate magic-sys crate here. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue 3 | about: Submit an issue that is neither a bug report nor a feature request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | - [ ] I have searched the [existing feature request issues](https://github.com/robo9k/rust-magic/labels/enhancement) for similar ones. 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | day: friday 9 | commit-message: 10 | prefix: chore(deps) 11 | - package-ecosystem: cargo 12 | directory: / 13 | schedule: 14 | interval: weekly 15 | day: friday 16 | commit-message: 17 | prefix: chore(deps) 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **References** 2 | Previous [issues](https://github.com/robo9k/rust-magic/issues) or [discussions](https://github.com/robo9k/rust-magic/discussions) for this change. 3 | 4 | **Description** 5 | A clear and concise description of the proposed changes. 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "build" 2 | 3 | permissions: {} 4 | 5 | on: 6 | # run "test" job on push events as well to get main branch coverage 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | build: 16 | name: "cargo build" 17 | if: github.event_name == 'pull_request' 18 | permissions: 19 | contents: read 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 23 | with: 24 | egress-policy: audit 25 | 26 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 27 | 28 | - id: toolchain 29 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 30 | with: 31 | toolchain: "1.56.0" # hardcoded crate MSRV, see rust-toolchain.toml etc. 32 | # minimal profile includes rustc component which includes cargo and rustdoc 33 | 34 | - uses: rui314/setup-mold@8de9eea54963d01c1a6c200606257d65bd53bea1 # does not have recent tags 35 | 36 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 37 | 38 | - run: cargo +${{ steps.toolchain.outputs.name }} build --all-targets --all-features --verbose 39 | 40 | test: 41 | name: "cargo test (with coverage)" 42 | permissions: 43 | contents: read 44 | runs-on: ubuntu-22.04 45 | steps: 46 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 47 | with: 48 | egress-policy: audit 49 | 50 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 51 | 52 | - id: toolchain 53 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 54 | with: 55 | toolchain: "stable" 56 | # minimal profile includes rustc component which includes cargo and rustdoc 57 | 58 | - uses: rui314/setup-mold@8de9eea54963d01c1a6c200606257d65bd53bea1 # does not have recent tags 59 | 60 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 61 | 62 | - uses: taiki-e/install-action@3eb90b20bc1fe55dfbf30d81d4a7e0ef8dd34caa # v2.36.0 63 | with: 64 | tool: cargo-llvm-cov@0.5.32 65 | 66 | - uses: taiki-e/install-action@3eb90b20bc1fe55dfbf30d81d4a7e0ef8dd34caa # v2.36.0 67 | with: 68 | tool: cargo-careful@0.4.2 69 | 70 | - run: cargo +${{ steps.toolchain.outputs.name }} llvm-cov test --codecov --output-path codecov.json --all-targets --all-features --verbose 71 | 72 | - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 73 | with: 74 | name: codecov.json 75 | path: codecov.json 76 | 77 | # this will likely fail for forks, maybe adapt bencher.dev workaround with separate workflow for uploaded artifact 78 | - uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 79 | with: 80 | files: codecov.json 81 | fail_ci_if_error: true 82 | env: 83 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 84 | 85 | test-careful: 86 | if: github.event_name == 'pull_request' 87 | name: "cargo test (carefully)" 88 | permissions: 89 | contents: read 90 | runs-on: ubuntu-22.04 91 | steps: 92 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 93 | with: 94 | egress-policy: audit 95 | 96 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 97 | 98 | - id: toolchain 99 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 100 | with: 101 | toolchain: "nightly" 102 | # minimal profile includes rustc component which includes cargo and rustdoc 103 | components: rust-src 104 | 105 | - uses: rui314/setup-mold@8de9eea54963d01c1a6c200606257d65bd53bea1 # does not have recent tags 106 | 107 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 108 | 109 | - uses: taiki-e/install-action@3eb90b20bc1fe55dfbf30d81d4a7e0ef8dd34caa # v2.36.0 110 | with: 111 | tool: cargo-careful@0.4.3 112 | 113 | - run: cargo +${{ steps.toolchain.outputs.name }} careful test --all-targets --all-features --verbose 114 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits.yml: -------------------------------------------------------------------------------- 1 | name: "conventional commits" 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | 8 | jobs: 9 | cog-check: 10 | name: "cog check" 11 | permissions: 12 | contents: read 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 16 | with: 17 | egress-policy: audit 18 | 19 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 20 | with: 21 | fetch-depth: 0 22 | # pick the pr HEAD instead of the merge commit 23 | ref: ${{ github.event.pull_request.head.sha }} 24 | 25 | - uses: taiki-e/install-action@3eb90b20bc1fe55dfbf30d81d4a7e0ef8dd34caa # v2.36.0 26 | with: 27 | tool: cocogitto@6.2.0 28 | 29 | - run: cog check ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 30 | -------------------------------------------------------------------------------- /.github/workflows/deny.yml: -------------------------------------------------------------------------------- 1 | name: "deny" 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | cargo-deny: 13 | name: "cargo deny" 14 | permissions: 15 | contents: read 16 | runs-on: ubuntu-22.04 17 | strategy: 18 | matrix: 19 | checks: 20 | - advisories 21 | - bans licenses sources 22 | 23 | # Prevent sudden announcement of a new advisory from failing ci: 24 | continue-on-error: ${{ matrix.checks == 'advisories' }} 25 | 26 | steps: 27 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 28 | with: 29 | egress-policy: audit 30 | 31 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 32 | 33 | - id: toolchain 34 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 35 | with: 36 | toolchain: "stable" 37 | 38 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 39 | 40 | - uses: taiki-e/install-action@3eb90b20bc1fe55dfbf30d81d4a7e0ef8dd34caa # v2.36.0 41 | with: 42 | tool: cargo-deny@0.14.19 43 | 44 | - run: cargo +${{ steps.toolchain.outputs.name }} deny --log-level info --all-features check ${{ matrix.checks }} 45 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: "docs" 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | # run "deploy-pages" job to deploy main branch to GitHub pages 8 | push: 9 | branches: [ "main" ] 10 | 11 | jobs: 12 | build-rustdoc: 13 | name: "cargo doc" 14 | permissions: 15 | contents: read 16 | runs-on: ubuntu-22.04 17 | steps: 18 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 19 | with: 20 | egress-policy: audit 21 | 22 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 23 | 24 | - id: toolchain 25 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 26 | with: 27 | toolchain: nightly # minimal profile includes rustc component which includes cargo and rustdoc 28 | 29 | - run: cargo +${{ steps.toolchain.outputs.name }} doc --verbose --no-deps --all-features 30 | env: 31 | RUSTDOCFLAGS: --crate-version ${{ github.event.pull_request.head.sha || github.sha }} -Z unstable-options --enable-index-page 32 | 33 | - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 34 | 35 | - name: Fix permissions 36 | run: | 37 | chmod -c -R +rX "target/doc/" | while read line; do 38 | echo "::warning title=Invalid file permissions automatically fixed::$line" 39 | done 40 | 41 | - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 42 | with: 43 | path: target/doc/ 44 | 45 | deploy-pages: 46 | name: "deploy GitHub pages" 47 | if: github.event_name == 'push' 48 | permissions: 49 | id-token: write 50 | pages: write 51 | environment: 52 | name: github-pages 53 | url: ${{ steps.deployment.outputs.page_url }} 54 | runs-on: ubuntu-22.04 55 | needs: build-rustdoc 56 | steps: 57 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 58 | with: 59 | egress-policy: audit 60 | - id: deployment 61 | uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 62 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: "lint" 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | 8 | env: 9 | RUSTFLAGS: -Dwarnings 10 | 11 | jobs: 12 | cargo-fmt: 13 | name: "cargo fmt" 14 | permissions: 15 | contents: read 16 | runs-on: ubuntu-22.04 17 | steps: 18 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 19 | with: 20 | egress-policy: audit 21 | 22 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 23 | 24 | - id: toolchain 25 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 26 | with: 27 | toolchain: stable 28 | components: rustfmt # minimal profile does not include it 29 | 30 | - run: cargo +${{ steps.toolchain.outputs.name }} fmt --all --check 31 | 32 | cargo-clippy: 33 | name: "cargo clippy" 34 | permissions: 35 | contents: read 36 | runs-on: ubuntu-22.04 37 | steps: 38 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 39 | with: 40 | egress-policy: audit 41 | 42 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 43 | 44 | - id: toolchain 45 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 46 | with: 47 | toolchain: stable 48 | components: clippy # minimal profile does not include it 49 | 50 | - run: cargo +${{ steps.toolchain.outputs.name }} clippy --all-targets --all-features 51 | 52 | cargo-rustdoc-clippy: 53 | name: "cargo rustdoc-clippy" 54 | permissions: 55 | contents: read 56 | runs-on: ubuntu-22.04 57 | steps: 58 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 59 | with: 60 | egress-policy: audit 61 | 62 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 63 | 64 | - id: toolchain 65 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 66 | with: 67 | toolchain: nightly 68 | components: clippy # minimal profile does not include it 69 | 70 | # dependency of cargo-rustdoc-clippy 71 | - run: | 72 | sudo apt-get update 73 | sudo apt-get install zsh 74 | 75 | # https://github.com/rust-lang/rust/issues/56232#issuecomment-1248359946 76 | - run: | 77 | curl --output ~/.cargo/bin/cargo-rustdoc-clippy https://raw.githubusercontent.com/Nemo157/dotfiles/e6daf083068ff17d14b19dc2569ae62ea86bf23c/bin/cargo-rustdoc-clippy 78 | chmod +x ~/.cargo/bin/cargo-rustdoc-clippy 79 | 80 | - run: cargo +${{ steps.toolchain.outputs.name }} rustdoc-clippy --all-features 81 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "release-plz" 2 | 3 | permissions: {} 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | release-plz: 11 | permissions: 12 | contents: read 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 16 | with: 17 | egress-policy: audit 18 | 19 | - id: toolchain 20 | uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # doesn't have usual versioned releases/tags 21 | with: 22 | toolchain: "stable" 23 | 24 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 25 | 26 | - uses: actions/create-github-app-token@c8f55efbd427e7465d6da1106e7979bc8aaee856 # v1.10.1 27 | id: app-token 28 | with: 29 | app-id: ${{ vars.APP_ID }} 30 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 31 | 32 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 33 | with: 34 | fetch-depth: 0 35 | token: ${{ steps.app-token.outputs.token }} 36 | 37 | - uses: MarcoIeni/release-plz-action@7566221bba53f707d05e4149111ada2c3313ae28 # v0.5.61 38 | env: 39 | GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} 40 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 41 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | name: "REUSE" 2 | 3 | permissions: {} 4 | 5 | on: 6 | # also run on push to main branch for badge 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | lint: 13 | name: "reuse lint" 14 | permissions: 15 | contents: read 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 19 | with: 20 | egress-policy: audit 21 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 22 | - name: REUSE Compliance Check 23 | uses: fsfe/reuse-action@a46482ca367aef4454a87620aa37c2be4b2f8106 # v3.0.0 24 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | name: "scorecards" 2 | 3 | permissions: read-all 4 | 5 | on: 6 | branch_protection_rule: 7 | schedule: 8 | - cron: '38 12 * * 1' 9 | push: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | analysis: 14 | name: "scorecards analysis" 15 | runs-on: ubuntu-latest 16 | permissions: 17 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 18 | id-token: write # for ossf/scorecard-action to access GitHub's OIDC token when publishing 19 | 20 | steps: 21 | - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 22 | with: 23 | egress-policy: audit 24 | - name: "Checkout code" 25 | uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 26 | with: 27 | persist-credentials: false 28 | 29 | - name: "Run analysis" 30 | uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 31 | with: 32 | results_file: results.sarif 33 | results_format: sarif 34 | repo_token: ${{ secrets.SCORECARD_TOKEN }} 35 | 36 | publish_results: true 37 | 38 | - name: "Upload artifact" 39 | uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 40 | with: 41 | name: SARIF file 42 | path: results.sarif 43 | retention-days: 5 44 | 45 | # Upload the results to GitHub's code scanning dashboard. 46 | - name: "Upload to code-scanning" 47 | uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 48 | with: 49 | sarif_file: results.sarif 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | /lcov.info 4 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: The `magic` Rust crate 3 | Upstream-Contact: robot9001 4 | Source: https://github.com/robo9k/rust-magic 5 | 6 | Files: Cargo.toml Cargo.lock README.md README-crate.md LICENSE.md CONTRIBUTING.md CHANGELOG.md SECURITY.md 7 | Copyright: © The `magic` Rust crate authors 8 | License: MIT OR Apache-2.0 9 | 10 | Files: .github/workflows/* .github/ISSUE_TEMPLATE/* .github/pull_request_template.md .github/dependabot.yml clippy.toml deny.toml release-plz.toml rust-toolchain.toml 11 | Copyright: © The `magic` Rust crate authors 12 | License: MIT OR Apache-2.0 13 | 14 | Files: .gitignore 15 | Copyright: © The `magic` Rust crate authors 16 | License: MIT OR Apache-2.0 17 | 18 | Files: data/tests/db-* 19 | Copyright: Copyright (c) Ian F. Darwin 1986, 1987, 1989, 1990, 1991, 1992, 1994, 1995. 20 | License: LicenseRef-file 21 | Comment: https://github.com/robo9k/rust-magic/blob/main/LICENSE.md 22 | 23 | Files: data/tests/rust-logo-128x128-blk.png 24 | Copyright: The Mozilla Foundation 25 | License: CC-BY-4.0 26 | Comment: https://github.com/robo9k/rust-magic/issues/12 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.16.2](https://github.com/robo9k/rust-magic/compare/v0.16.1...v0.16.2) - 2023-10-05 10 | 11 | ### Added 12 | - Implement `TryFrom` for a few more database path types 13 | - Allow recovering cookie from state transition errors 14 | 15 | ## [0.16.1](https://github.com/robo9k/rust-magic/compare/v0.16.0...v0.16.1) - 2023-10-03 16 | 17 | ### Other 18 | - Add a whole lot of rustdoc 19 | 20 | ## [0.16.0](https://github.com/robo9k/rust-magic/compare/v0.15.1...v0.16.0) - 2023-09-30 21 | 22 | ### Changed 23 | - [**breaking**] Move everything `Cookie` into mod `cookie` 24 | - [**breaking**] Use typestate for opened/loaded `Cookie` 25 | - [**breaking**] Introduce `DatabasePaths` and `InvalidDatabasePathError` 26 | - [**breaking**] Introduce `OpenError` for `Cookie::open` 27 | - [**breaking**] Introduce `SetFlagsError` for `Cookie::set_flags` 28 | - [**breaking**] Replace `MagicError` with `cookie::Error` 29 | - [**breaking**] Replace internal `ApiViolation` error for `libmagic` with `panic!` 30 | 31 | ### Other 32 | - Split project/crate README 33 | - *(deps)* Bump thiserror from 1.0.40 to 1.0.49 34 | - *(deps)* Bump libc from 0.2.141 to 0.2.148 35 | 36 | ## [0.15.1](https://github.com/robo9k/rust-magic/compare/v0.15.0...v0.15.1) - 2023-09-28 37 | 38 | ### Other 39 | - Fix clippy findings in rustdoc doctests 40 | - Fix clippy findings in tests 41 | 42 | ## [0.15.0](https://github.com/robo9k/rust-magic/compare/v0.14.0...v0.15.0) - 2023-09-26 43 | 44 | ### Changed 45 | - *(deps)* [**breaking**] Replace `errno` with `std::io::Error` 46 | - *(deps)* [**breaking**] Upgrade to `bitflags` v2 47 | 48 | ## 0.14.0 - 2023-09-16 49 | 50 | TBD 51 | 52 | ## 0.13.0 - 2022-08-18 53 | 54 | TBD 55 | 56 | ## 0.13.0-alpha.3 - 2021-11-07 57 | 58 | TBD 59 | 60 | ## v0.13.0-alpha.2 - 2021-11-06 61 | 62 | TBD 63 | 64 | ## 0.13.0-alpha.1 - 2021-10-26 65 | 66 | TBD 67 | 68 | ## 0.12.2 - 2017-04-12 69 | 70 | TBD 71 | 72 | ## 0.12.1 - 2017-04-11 73 | 74 | TBD 75 | 76 | ## 0.12.0 - 2016-10-18 77 | 78 | TBD 79 | 80 | ## 0.11.0 - 2016-07-01 81 | 82 | TBD 83 | 84 | ## 0.10.0 - 2016-06-11 85 | 86 | TBD 87 | 88 | ## 0.9.0 - 2016-01-30 89 | 90 | TBD 91 | 92 | ## 0.8.0 - 2015-07-11 93 | 94 | TBD 95 | 96 | ## 0.7.0 - 2015-02-03 97 | 98 | TBD 99 | 100 | ## 0.6.4 - 2015-01-24 101 | 102 | TBD 103 | 104 | ## 0.6.3 - 2015-01-19 105 | 106 | TBD 107 | 108 | ## 0.6.2 - 2015-01-09 109 | 110 | TBD 111 | 112 | ## 0.6.1 - 2015-01-05 113 | 114 | TBD 115 | 116 | ## 0.6.0 - 2014-12-22 117 | 118 | TBD 119 | 120 | ## 0.5.2 - 2014-12-21 121 | 122 | TBD 123 | 124 | ## 0.5.1 - 2014-12-21 125 | 126 | TBD 127 | 128 | ## 0.5.0 - 2014-12-21 129 | 130 | TBD 131 | 132 | ## 0.4.0 - 2014-12-21 133 | 134 | TBD 135 | 136 | ## 0.3.0 - 2014-12-19 137 | 138 | TBD 139 | 140 | ## 0.2.0 - 2014-11-20 141 | 142 | TBD 143 | 144 | ## 0.1.0 - 2014-11-20 145 | 146 | TBD 147 | 148 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Thank you for contributing to the `rust-magic` project, which contains the `magic` Rust crate! ♥️ 4 | 5 | ## Maintenance 6 | 7 | Please note that the project is only maintained passively by @robo9k \ 8 | This is sometimes also called ["casual maintenance intended"](https://casuallymaintained.tech/). 9 | 10 | This means that it might take longer for the maintainers to look at your contributions, e.g. 11 | pull requests that require much code review might not be merged 12 | and issues that require much research/work might remain unresolved. 13 | 14 | That being said, the project welcomes your contributions and will try to 15 | prioritize security issues (see [Security](#Security)) 16 | and bug reports. 17 | 18 | ## Discussions 19 | 20 | Please use [discussions](https://github.com/robo9k/rust-magic/discussions) to ask and answer questions. 21 | 22 | The maintainers will try and answer questions, but you can answer other users' questions, too. 23 | 24 | ## Issues 25 | 26 | Please use issues to create bug reports, suggest features or submit a custom issue.\ 27 | The [issue templates](https://github.com/robo9k/rust-magic/issues/new/choose) will guide you towards the expected format. 28 | 29 | Please take a look at [existing issues](https://github.com/robo9k/rust-magic/issues) before 30 | creating new ones. 31 | 32 | ## Pull requests 33 | 34 | Please use [pull requests](https://github.com/robo9k/rust-magic/pulls) to propose concrete changes. Before doing so, 35 | use discussions or issues to align your implementation ideas with the project maintainers. 36 | 37 | ## Code 38 | 39 | The project is pretty standard for Rust and GitHub. 40 | It uses `cargo`, `crates.io`, GitHub Actions and GitHub Pages. 41 | 42 | Check the [crate README](README-crate.md) for 43 | the Minimum Supported Rust Version and 44 | requirements of the `magic-sys` crate / the `libmagic` C library.\ 45 | Note that the `magic-sys` crate is a related but separate sister project, see [robo9k/rust-magic-sys](https://github.com/robo9k/rust-magic-sys). 46 | 47 | When developing code, please use `cargo clippy` and `cargo fmt`. 48 | 49 | New code should also come with new documentation (`cargo doc`, readme) and tests (`cargo test`, GitHub Actions).\ 50 | Changed code should accordingly result in changed documentation and tests.\ 51 | There are no hard rules on good tests, test coverage or what makes good documentation, 52 | but try to adhere to the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/documentation.html). 53 | If you are unsure, just ask. 54 | 55 | ## Commits 56 | 57 | Commit messages should follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and 58 | use the additional types `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`.\ 59 | Ideally use breaking change messages according to [Semantic Versioning](https://semver.org/).\ 60 | You to not have to provide a changelog entry 61 | (we use the [keep a changelog](https://keepachangelog.com/en/1.1.0/) format) 62 | but it helps if you provide a short summary in your pull request. 63 | 64 | Also see [Signoff](#Signoff) as part of the DCO. 65 | 66 | There are other conventions/rules, but the general idea is that continous integration will run for your 67 | pull request and fail if something is not in order (`cargo deny`, linear Git history to name a few).\ 68 | Don't worry trying to get something perfect on the first try - you can always ask the maintainers for 69 | help and force-push fixes to your pull request branch. We're all no wizards 🧙 70 | 71 | ## Donations 72 | 73 | The project does _not_ accept donations, monetary or otherwise. 74 | 75 | ## License 76 | 77 | This project is dual-licensed under the MIT and the Apache-2.0 licenses, see [LICENSE](LICENSE.md). 78 | 79 | Unless you explicitly state otherwise, 80 | any contribution intentionally submitted for inclusion in the work by you, 81 | as defined in the Apache-2.0 license, 82 | shall be dual licensed as above, without any additional terms or conditions. 83 | 84 | Also see the [GitHub Terms of Service](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#6-contributions-under-repository-license). 85 | 86 | ## Developer Certificate of Origin 87 | 88 | As per https://developercertificate.org/ Version 1.1: 89 | 90 | ``` 91 | By making a contribution to this project, I certify that: 92 | 93 | (a) The contribution was created in whole or in part by me and I 94 | have the right to submit it under the open source license 95 | indicated in the file; or 96 | 97 | (b) The contribution is based upon previous work that, to the best 98 | of my knowledge, is covered under an appropriate open source 99 | license and I have the right under that license to submit that 100 | work with modifications, whether created in whole or in part 101 | by me, under the same open source license (unless I am 102 | permitted to submit under a different license), as indicated 103 | in the file; or 104 | 105 | (c) The contribution was provided directly to me by some other 106 | person who certified (a), (b) or (c) and I have not modified 107 | it. 108 | 109 | (d) I understand and agree that this project and the contribution 110 | are public and that a record of the contribution (including all 111 | personal information I submit with it, including my sign-off) is 112 | maintained indefinitely and may be redistributed consistent with 113 | this project or the open source license(s) involved. 114 | ``` 115 | 116 | ### Signoff 117 | 118 | Please use [`git commit --signoff`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff). 119 | 120 | ## Security 121 | 122 | For security related contributions, please follow the policy in [SECURITY](SECURITY.md). 123 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "2.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 10 | 11 | [[package]] 12 | name = "libc" 13 | version = "0.2.155" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 16 | 17 | [[package]] 18 | name = "magic" 19 | version = "0.16.2" 20 | dependencies = [ 21 | "bitflags", 22 | "libc", 23 | "magic-sys", 24 | "static_assertions", 25 | "thiserror", 26 | ] 27 | 28 | [[package]] 29 | name = "magic-sys" 30 | version = "0.3.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "eff86ae08895140d628119d407d568f3b657145ee8c265878064f717534bb3bc" 33 | dependencies = [ 34 | "libc", 35 | "vcpkg", 36 | ] 37 | 38 | [[package]] 39 | name = "proc-macro2" 40 | version = "1.0.75" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" 43 | dependencies = [ 44 | "unicode-ident", 45 | ] 46 | 47 | [[package]] 48 | name = "quote" 49 | version = "1.0.35" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 52 | dependencies = [ 53 | "proc-macro2", 54 | ] 55 | 56 | [[package]] 57 | name = "static_assertions" 58 | version = "1.1.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 61 | 62 | [[package]] 63 | name = "syn" 64 | version = "2.0.48" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 67 | dependencies = [ 68 | "proc-macro2", 69 | "quote", 70 | "unicode-ident", 71 | ] 72 | 73 | [[package]] 74 | name = "thiserror" 75 | version = "1.0.61" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 78 | dependencies = [ 79 | "thiserror-impl", 80 | ] 81 | 82 | [[package]] 83 | name = "thiserror-impl" 84 | version = "1.0.61" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 87 | dependencies = [ 88 | "proc-macro2", 89 | "quote", 90 | "syn", 91 | ] 92 | 93 | [[package]] 94 | name = "unicode-ident" 95 | version = "1.0.8" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 98 | 99 | [[package]] 100 | name = "vcpkg" 101 | version = "0.2.15" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 104 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "magic" 3 | description = "High level bindings for the `libmagic` C library" 4 | readme = "README-crate.md" 5 | license = "MIT OR Apache-2.0" 6 | keywords = [ 7 | "magic", 8 | "file", 9 | "ffi", 10 | "bindings", 11 | ] 12 | categories = [ 13 | "api-bindings", 14 | "filesystem", 15 | "parsing", 16 | ] 17 | repository = "https://github.com/robo9k/rust-magic.git" 18 | authors = [ 19 | "Daniel Micay ", 20 | "Petar Radošević ", 21 | "lilydjwg ", 22 | "Jeff Belgum ", 23 | "Onur Aslan ", 24 | "robo9k ", 25 | ] 26 | version = "0.16.2" 27 | include = [ 28 | "/src/", 29 | ] 30 | edition = "2018" 31 | rust-version = "1.56" 32 | 33 | [package.metadata] 34 | msrv = "1.56.0" 35 | 36 | [package.metadata.docs.rs] 37 | all-features = true 38 | targets = [] 39 | 40 | [badges] 41 | maintenance = { status = "passively-maintained" } 42 | 43 | [dependencies] 44 | bitflags = "2.5.0" 45 | magic-sys = "0.3.0" 46 | thiserror = "1.0.61" 47 | 48 | [dependencies.libc] 49 | version = "0.2.155" 50 | default-features = false 51 | 52 | [dev-dependencies] 53 | static_assertions = "1.1.0" 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | LICENSE 2 | ======= 3 | 4 | 5 | This project is licensed under either of 6 | * Apache License, Version 2.0 7 | ([LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt) or https://opensource.org/licenses/Apache-2.0) 8 | * MIT license 9 | ([LICENSES/MIT.txt](LICENSES/MIT.txt) or https://opensource.org/licenses/MIT) 10 | 11 | at your option. 12 | The machine-readable version of this is `SPDX-License-Identifier: Apache-2.0 OR MIT`. 13 | 14 | 15 | REUSE [![REUSE status](https://api.reuse.software/badge/github.com/robo9k/rust-magic)](https://api.reuse.software/info/github.com/robo9k/rust-magic) 16 | ----- 17 | 18 | This project is compliant with the [REUSE guidelines](https://reuse.software/) for licensing software and other files. 19 | 20 | 21 | Other 22 | ----- 23 | 24 | The following is a human-readable version of parts of [.reuse/dep5](.reuse/dep5). 25 | 26 | The `file` / `libmagic` project is licensed under a modified BSD license (see their [`COPYING`](https://github.com/file/file/blob/master/COPYING) file). 27 | 28 | This project contains partial test-data from the `file` project: 29 | - [`rust-magic/data/tests/db-images-png`](data/tests/db-images-png) is from [`file/magic/Magdir/images`](https://github.com/file/file/blob/master/magic/Magdir/images) 30 | - [`rust-magic/data/tests/db-python`](data/tests/db-python) is from [`file/magic/Magdir/python`](https://github.com/file/file/blob/master/magic/Magdir/python) 31 | 32 | This project contains test-data from the Rust Foundation: 33 | - [rust-magic/data/tests/rust-logo-128x128-blk.png](data/tests/rust-logo-128x128-blk.png) see [#12](https://github.com/robo9k/rust-magic/issues/12) 34 | 35 | If you only use this project in form of the [`magic` crate](https://crates.io/crates/magic) you can ignore those other details since the binary `.crate` distribution does not include the aforementioned test-data files. 36 | 37 | 38 | Contribution 39 | ------------ 40 | 41 | See [CONTRIBUTING.md](CONTRIBUTING.md). 42 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 | 21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 | 23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 | 25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 | 27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 | 29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 32 | 33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 34 | 35 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and 36 | 37 | (b) You must cause any modified files to carry prominent notices stating that You changed the files; and 38 | 39 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | 41 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 42 | 43 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS 56 | 57 | APPENDIX: How to apply the Apache License to your work. 58 | 59 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 60 | 61 | Copyright [yyyy] [name of copyright owner] 62 | 63 | Licensed under the Apache License, Version 2.0 (the "License"); 64 | you may not use this file except in compliance with the License. 65 | You may obtain a copy of the License at 66 | 67 | http://www.apache.org/licenses/LICENSE-2.0 68 | 69 | Unless required by applicable law or agreed to in writing, software 70 | distributed under the License is distributed on an "AS IS" BASIS, 71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 72 | See the License for the specific language governing permissions and 73 | limitations under the License. 74 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International 2 | 3 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. 4 | 5 | Using Creative Commons Public Licenses 6 | 7 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 8 | 9 | Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. 10 | 11 | Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. 12 | 13 | Creative Commons Attribution 4.0 International Public License 14 | 15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 16 | 17 | Section 1 – Definitions. 18 | 19 | a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 20 | 21 | b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 22 | 23 | c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 24 | 25 | d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 26 | 27 | e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 28 | 29 | f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 30 | 31 | g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 32 | 33 | h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. 34 | 35 | i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 36 | 37 | j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 38 | 39 | k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 40 | 41 | Section 2 – Scope. 42 | 43 | a. License grant. 44 | 45 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 46 | 47 | A. reproduce and Share the Licensed Material, in whole or in part; and 48 | 49 | B. produce, reproduce, and Share Adapted Material. 50 | 51 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 52 | 53 | 3. Term. The term of this Public License is specified in Section 6(a). 54 | 55 | 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 56 | 57 | 5. Downstream recipients. 58 | 59 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 60 | 61 | B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 62 | 63 | 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 64 | 65 | b. Other rights. 66 | 67 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 68 | 69 | 2. Patent and trademark rights are not licensed under this Public License. 70 | 71 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 72 | 73 | Section 3 – License Conditions. 74 | 75 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 76 | 77 | a. Attribution. 78 | 79 | 1. If You Share the Licensed Material (including in modified form), You must: 80 | 81 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 82 | 83 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 84 | 85 | ii. a copyright notice; 86 | 87 | iii. a notice that refers to this Public License; 88 | 89 | iv. a notice that refers to the disclaimer of warranties; 90 | 91 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 92 | 93 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 94 | 95 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 96 | 97 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 98 | 99 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 100 | 101 | 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. 102 | 103 | Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 108 | 109 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 110 | 111 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 112 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 113 | 114 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 115 | 116 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 117 | 118 | b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 119 | 120 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 121 | 122 | Section 6 – Term and Termination. 123 | 124 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 125 | 126 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 127 | 128 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 129 | 130 | 2. upon express reinstatement by the Licensor. 131 | 132 | c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 133 | 134 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 135 | 136 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 137 | 138 | Section 7 – Other Terms and Conditions. 139 | 140 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 141 | 142 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 143 | 144 | Section 8 – Interpretation. 145 | 146 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 147 | 148 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 149 | 150 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 151 | 152 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 153 | 154 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 155 | 156 | Creative Commons may be contacted at creativecommons.org. 157 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-file.txt: -------------------------------------------------------------------------------- 1 | $File: COPYING,v 1.2 2018/09/09 20:33:28 christos Exp $ 2 | Copyright (c) Ian F. Darwin 1986, 1987, 1989, 1990, 1991, 1992, 1994, 1995. 3 | Software written by Ian F. Darwin and others; 4 | maintained 1994- Christos Zoulas. 5 | 6 | This software is not subject to any export provision of the United States 7 | Department of Commerce, and may be exported to any country or planet. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 1. Redistributions of source code must retain the above copyright 13 | notice immediately at the beginning of the file, without modification, 14 | this list of conditions, and the following disclaimer. 15 | 2. Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README-crate.md: -------------------------------------------------------------------------------- 1 | 2 | [//]: # (This is the README for the `magic` crate only) 3 | 4 | [//]: # (The whole project has docs in https://github.com/robo9k/rust-magic ) 5 | 6 | High-level bindings for `libmagic` 7 | 8 | # About 9 | 10 | This crate provides bindings for the [`libmagic` C library]((https://www.darwinsys.com/file/)), 11 | which recognizes the type of data contained in a file (or buffer) and can give you 12 | a textual description, a MIME type and the usual file extensions. 13 | 14 | # Usage 15 | 16 | ```rust 17 | // only for Rust Edition 2018, see https://doc.rust-lang.org/edition-guide/rust-2021/prelude.html 18 | use std::convert::TryInto; 19 | 20 | fn file_example() -> Result<(), Box> { 21 | // Open a new configuration with flags 22 | let cookie = magic::Cookie::open(magic::cookie::Flags::ERROR)?; 23 | 24 | // Load a specific database 25 | // (so exact test text assertion below works regardless of the system's default database version) 26 | let database = ["data/tests/db-images-png"].try_into()?; 27 | // You can instead load the default database 28 | //let database = Default::default(); 29 | 30 | let cookie = cookie.load(&database)?; 31 | 32 | let file = "data/tests/rust-logo-128x128-blk.png"; 33 | 34 | // Analyze the file 35 | assert_eq!(cookie.file(file)?, "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced"); 36 | 37 | Ok(()) 38 | } 39 | ``` 40 | 41 | Check the [crate rustdoc](https://docs.rs/magic) for more details. 42 | 43 | # Repository 44 | 45 | The project's repository is [github.com/robo9k/rust-magic](https://github.com/robo9k/rust-magic) 46 | 47 | It contains the latest in-development version of the `magic` crate (might not be published to `crates.io` yet), 48 | more [examples](https://github.com/robo9k/rust-magic/tree/main/examples) how to use the `magic` crate 49 | as well as [issues](https://github.com/robo9k/rust-magic/issues) 50 | and [discussions](https://github.com/robo9k/rust-magic/discussions). 51 | 52 | # MSRV 53 | 54 | The Minimum Supported Rust Version (MSRV) is Rust 1.56 or higher. 55 | 56 | This version might be changed in the future, but it will be done with a crate version bump. 57 | 58 | # Requirements 59 | 60 | By default, compiling the `magic` crate will (via the [`magic-sys` crate](https://crates.io/crates/magic-sys)) 61 | search your system library paths for a shared library version of `libmagic` to link against. 62 | For this to work, you need to install the development version of `libmagic` in a standard location: 63 | ```shell 64 | $ # On Debian based Linux systems: 65 | $ sudo apt-get install libmagic1 libmagic-dev 66 | 67 | $ # On macOS: 68 | $ brew install libmagic 69 | 70 | $ # On Windows: 71 | $ cargo install cargo-vcpkg 72 | $ cargo vcpkg build 73 | ``` 74 | 75 | If you're cross-compiling, or need more control over which library is selected, 76 | see [how to build `magic-sys`](https://crates.io/crates/magic-sys#building). 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [//]: # (This is the README for the whole project / Git repo) 3 | 4 | [//]: # (The crate has a separate README-crate.md ) 5 | 6 | rust-magic 7 | [![maintenance status](https://img.shields.io/maintenance/passively/2023?color=yellow)](https://casuallymaintained.tech/) 8 | [![build status](https://img.shields.io/github/actions/workflow/status/robo9k/rust-magic/build.yml?logo=githubactions)](https://github.com/robo9k/rust-magic/actions/workflows/build.yml) 9 | [![Rust safety dance](https://img.shields.io/badge/unsafe-C%20FFI-red?logo=rust)](https://docs.rs/magic/latest/magic/#safety) 10 | [![crates.io version](https://img.shields.io/crates/v/magic?logo=rust)](https://crates.io/crates/magic) 11 | [![crates.io license](https://img.shields.io/crates/l/magic?logo=opensourceinitiative)](LICENSE.md) 12 | [![docs.rs status](https://img.shields.io/docsrs/magic?logo=docsdotrs&label=docs.rs)](https://docs.rs/magic) 13 | [![REUSE status](https://img.shields.io/reuse/compliance/github.com%2Frobo9k%2Frust-magic?label=REUSE)](https://api.reuse.software/info/github.com/robo9k/rust-magic) 14 | [![OpenSSF best practices](https://img.shields.io/cii/summary/5709?label=OpenSSF%20best%20practices)](https://bestpractices.coreinfrastructure.org/projects/5709) 15 | [![OpenSSF scorecard](https://img.shields.io/ossf-scorecard/github.com/robo9k/rust-magic?label=OpenSSF%20scorecard)](https://securityscorecards.dev/viewer/?uri=github.com/robo9k/rust-magic) 16 | [![Codecov](https://img.shields.io/codecov/c/gh/robo9k/rust-magic?token=YnazJQdLXI&logo=codecov&label=Codecov)](https://codecov.io/gh/robo9k/rust-magic) 17 | ========== 18 | 19 | [`libmagic`](https://www.darwinsys.com/file/) bindings for the [Rust programming language](https://www.rust-lang.org/). 20 | 21 | `libmagic` recognizes the type of data contained in a file (or buffer) and can give you 22 | a textual description, a MIME type and the usual file extensions. 23 | 24 | # Usage 25 | 26 | This project's [crate](https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html) is 27 | published on the [`crates.io` Rust package registry](https://crates.io/): the [`magic` crate](https://crates.io/crates/magic) 28 | 29 | In your Rust project, use [`cargo add`](https://blog.rust-lang.org/2022/06/30/Rust-1.62.0.html#cargo-add) 30 | to [specify dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html): 31 | 32 | ```shell 33 | $ cargo add magic 34 | ``` 35 | 36 | To install the latest in-development version instead: 37 | ```shell 38 | $ cargo add --git https://github.com/robo9k/rust-magic 39 | ``` 40 | 41 | You might be familiar with `libmagic`'s CLI; `file`: 42 | ```shell 43 | $ file data/tests/rust-logo-128x128-blk.png 44 | data/tests/rust-logo-128x128-blk.png: PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced 45 | ``` 46 | 47 | You can implement something similar in Rust with the `magic` crate, see [crate README](README-crate.md): 48 | 49 | ```shell 50 | $ cargo run --example file-ish -- data/tests/rust-logo-128x128-blk.png 51 | PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced 52 | ``` 53 | 54 | For more details, check the `magic` [rustdoc](https://doc.rust-lang.org/rustdoc/index.html): [robo9k.github.io/rust-magic/magic](https://robo9k.github.io/rust-magic/magic/index.html) 55 | 56 | # Requirements 57 | 58 | For the `magic` crate requirements, see [crate README](README-crate.md). 59 | 60 | For developing the `rust-magic` project, see [CONTRIBUTING](CONTRIBUTING.md). 61 | 62 | # License 63 | 64 | This project is licensed under either of 65 | * Apache License, Version 2.0 66 | ([LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt) or https://opensource.org/licenses/Apache-2.0) 67 | * MIT license 68 | ([LICENSES/MIT.txt](LICENSES/MIT.txt) or https://opensource.org/licenses/MIT) 69 | 70 | at your option. 71 | 72 | For further details, see [LICENSE](LICENSE.md). 73 | 74 | # Security 75 | 76 | See [SECURITY](SECURITY.md). 77 | 78 | # Contribution 79 | 80 | See [CONTRIBUTING](CONTRIBUTING.md). 81 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | If you have found a potential security vulnerability in the [`magic` Rust crate](https://crates.io/crates/magic) or elsewhere in this [`robo9k/rust-magic`](https://github.com/robo9k/rust-magic) repository, please follow coordinated disclosure: 4 | - do report it _privately_ on GitHub, i.e. click "Report a vulnerability" on the [repo's "Security" tab](https://github.com/robo9k/rust-magic/security) 5 | - do *NOT* report it _publically_, e.g. do not post it in the public issues or discussions of the project, not on social media nor your own blog just yet 6 | 7 | GitHub has some general info about [privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability), which does apply to this `robo9k/rust-magic` repo. 8 | 9 | Note that the whole project is only passively-maintained, so fast response times can not be guaranteed and instead are made on best-effort. Use your own judgement if you consider the project to be entirely unresponsive. 10 | 11 | If your report is deemed valid (i.e. not "working as intended", a regular bug, or a feature request instead), a public GitHub Security Advisory (GHSA) will be created and you will be credited for the finding. You can also privately collaborate on GitHub for development of a fix. After the project's GHSA publication, feel free to post your own cross-referenced advisory on social media or your own blog. There will be no other embargoed notifications. 12 | 13 | There are no security bug bounties or other forms of rewards other than the attribution of the GitHub advisory. 14 | 15 | No other forms of reporting security issues are provided (e.g. no email contact). 16 | 17 | ## Supported versions 18 | 19 | Only the latest semver compatible version of the `magic` crate is supported. Note that [`cargo` treats `v0.x` development versions differently](https://doc.rust-lang.org/cargo/reference/semver.html#change-categories). 20 | Other versions will not recieve security upates. All affected versions will be yanked. 21 | 22 | ## Reports 23 | 24 | First of all, ensure that the security issue is in fact in this project. Note that the project depends on the [`magic-sys` Rust crate](https://crates.io/crates/magic-sys) (less likely to contain security issues) and the [`libmagic` C library](https://www.darwinsys.com/file/) (more likely to contain security issues). 25 | This project itself might also have non-code security issues, such as in its supply-chain (dependencies, continous integration etc.). 26 | 27 | Like regular bug reports, please include as much information as possible. For example 28 | - which [versions of the `magic` crate](https://crates.io/crates/magic/versions) are affected? 29 | - which revisions of the [`robo9k/rust-magic`](https://github.com/robo9k/rust-magic) repo are affected? 30 | - in which lines of code, configuration or documentation is the vulnerability located? 31 | - how specifically does this vulnerability create a security risk? 32 | - if we leave the code as it is, what could an attacker ultimately do? 33 | - how can we replicate the issue? 34 | do you have a working proof-of-concept that you would be willing to show us? 35 | - what would we have to do to fix the issue? 36 | 37 | Thank you for your contribution. 38 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.56.0" 2 | -------------------------------------------------------------------------------- /data/tests/db-images-png: -------------------------------------------------------------------------------- 1 | 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data 2 | !:mime image/png 3 | !:ext png 4 | >16 belong x \b, %d x 5 | >20 belong x %d, 6 | >24 byte x %d-bit 7 | >25 byte 0 grayscale, 8 | >25 byte 2 \b/color RGB, 9 | >25 byte 3 colormap, 10 | >25 byte 4 gray+alpha, 11 | >25 byte 6 \b/color RGBA, 12 | #>26 byte 0 deflate/32K, 13 | >28 byte 0 non-interlaced 14 | >28 byte 1 interlaced 15 | -------------------------------------------------------------------------------- /data/tests/db-images-png-precompiled.mgc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo9k/rust-magic/a6c55920460360e166631a8a6b3fac8e4cc4dc07/data/tests/db-images-png-precompiled.mgc -------------------------------------------------------------------------------- /data/tests/db-python: -------------------------------------------------------------------------------- 1 | 0 search/1/w #!\ /usr/bin/python Python script text executable 2 | !:mime text/x-python 3 | 0 search/1/w #!\ /usr/local/bin/python Python script text executable 4 | !:mime text/x-python 5 | 0 search/1 #!/usr/bin/env\ python Python script text executable 6 | !:mime text/x-python 7 | 0 search/1 #!\ /usr/bin/env\ python Python script text executable 8 | !:mime text/x-python 9 | -------------------------------------------------------------------------------- /data/tests/rust-logo-128x128-blk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo9k/rust-magic/a6c55920460360e166631a8a6b3fac8e4cc4dc07/data/tests/rust-logo-128x128-blk.png -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = [ 3 | "MIT", 4 | "Apache-2.0", 5 | "Unicode-DFS-2016", # see https://github.com/dtolnay/unicode-ident/pull/11#pullrequestreview-1066127930 6 | ] 7 | copyleft = "deny" 8 | 9 | [bans] 10 | wildcards = "deny" 11 | 12 | [sources] 13 | unknown-registry = "deny" 14 | unknown-git = "deny" 15 | -------------------------------------------------------------------------------- /examples/file-ish.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © The `magic` Rust crate authors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | //! Minimalist `file(1)` clone 5 | //! 6 | //! Prints the `libmagic` description of the file given as a command line argument: 7 | //! ```shell 8 | //! $ file data/tests/rust-logo-128x128-blk.png 9 | //! data/tests/rust-logo-128x128-blk.png: PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced 10 | //! 11 | //! $ cargo run --example file-ish -- data/tests/rust-logo-128x128-blk.png 12 | //! PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced 13 | //! ``` 14 | 15 | fn main() -> Result<(), Box> { 16 | // open a new configuration with flags 17 | let cookie = magic::Cookie::open(magic::cookie::Flags::ERROR)?; 18 | 19 | // load the system's default database 20 | let database = &Default::default(); 21 | let cookie = cookie.load(database)?; 22 | 23 | let file = std::env::args_os() 24 | .nth(1) 25 | .expect("One command line argument"); 26 | 27 | // analyze the file 28 | println!("{}", cookie.file(file)?); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | pr_labels = ["release"] 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.56.0" 3 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © The `magic` Rust crate authors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | //! Internal Foreign Function Interface module for `magic_sys` / `libmagic` 5 | //! 6 | //! Contains `unsafe` as a medium level binding. 7 | 8 | #![allow(unsafe_code)] 9 | 10 | use magic_sys as libmagic; 11 | 12 | #[derive(Debug)] 13 | // non-copy wrapper around raw pointer 14 | #[repr(transparent)] 15 | pub(crate) struct Cookie(libmagic::magic_t); 16 | 17 | impl Cookie { 18 | pub fn new(cookie: &mut Self) -> Self { 19 | Self(cookie.0) 20 | } 21 | } 22 | 23 | /// Error for opened `magic_t` instance 24 | #[derive(thiserror::Error, Debug)] 25 | #[error("magic cookie error ({}): {}", 26 | match .errno { 27 | Some(errno) => format!("OS errno: {}", errno), 28 | None => "no OS errno".to_string(), 29 | }, 30 | .explanation.to_string_lossy() 31 | )] 32 | pub(crate) struct CookieError { 33 | explanation: std::ffi::CString, 34 | errno: Option, 35 | } 36 | 37 | fn last_error(cookie: &Cookie) -> Option { 38 | let error = unsafe { libmagic::magic_error(cookie.0) }; 39 | let errno = unsafe { libmagic::magic_errno(cookie.0) }; 40 | 41 | if error.is_null() { 42 | None 43 | } else { 44 | let c_str = unsafe { std::ffi::CStr::from_ptr(error) }; 45 | Some(CookieError { 46 | explanation: c_str.into(), 47 | errno: match errno { 48 | 0 => None, 49 | _ => Some(std::io::Error::from_raw_os_error(errno)), 50 | }, 51 | }) 52 | } 53 | } 54 | 55 | fn api_violation(cookie: &Cookie, description: String) -> ! { 56 | panic!( 57 | "`libmagic` API violation for magic cookie {:?}: {}", 58 | cookie, description 59 | ); 60 | } 61 | 62 | fn expect_error(cookie: &Cookie, description: String) -> CookieError { 63 | match last_error(cookie) { 64 | Some(err) => err, 65 | _ => api_violation(cookie, description), 66 | } 67 | } 68 | 69 | pub(crate) fn close(cookie: &mut Cookie) { 70 | unsafe { libmagic::magic_close(cookie.0) } 71 | } 72 | 73 | /// # Panics 74 | /// 75 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error. 76 | pub(crate) fn file( 77 | cookie: &Cookie, 78 | filename: &std::ffi::CStr, // TODO: Support NULL 79 | ) -> Result { 80 | let filename_ptr = filename.as_ptr(); 81 | let res = unsafe { libmagic::magic_file(cookie.0, filename_ptr) }; 82 | 83 | if res.is_null() { 84 | Err(expect_error( 85 | cookie, 86 | "`magic_file()` did not set last error".to_string(), 87 | )) 88 | } else { 89 | let c_str = unsafe { std::ffi::CStr::from_ptr(res) }; 90 | Ok(c_str.into()) 91 | } 92 | } 93 | 94 | /// # Panics 95 | /// 96 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error. 97 | pub(crate) fn buffer(cookie: &Cookie, buffer: &[u8]) -> Result { 98 | let buffer_ptr = buffer.as_ptr(); 99 | let buffer_len = buffer.len() as libc::size_t; 100 | let res = unsafe { libmagic::magic_buffer(cookie.0, buffer_ptr, buffer_len) }; 101 | 102 | if res.is_null() { 103 | Err(expect_error( 104 | cookie, 105 | "`magic_buffer()` did not set last error".to_string(), 106 | )) 107 | } else { 108 | let c_str = unsafe { std::ffi::CStr::from_ptr(res) }; 109 | Ok(c_str.into()) 110 | } 111 | } 112 | 113 | pub(crate) fn setflags(cookie: &Cookie, flags: libc::c_int) -> Result<(), SetFlagsError> { 114 | let ret = unsafe { libmagic::magic_setflags(cookie.0, flags) }; 115 | match ret { 116 | -1 => Err(SetFlagsError { flags }), 117 | _ => Ok(()), 118 | } 119 | } 120 | 121 | #[derive(thiserror::Error, Debug)] 122 | #[error("could not set magic cookie flags {}", .flags)] 123 | pub(crate) struct SetFlagsError { 124 | flags: libc::c_int, 125 | } 126 | 127 | /// # Panics 128 | /// 129 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 130 | pub(crate) fn check(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> { 131 | let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr); 132 | let res = unsafe { libmagic::magic_check(cookie.0, filename_ptr) }; 133 | 134 | match res { 135 | 0 => Ok(()), 136 | -1 => Err(expect_error( 137 | cookie, 138 | "`magic_check()` did not set last error".to_string(), 139 | )), 140 | res => api_violation( 141 | cookie, 142 | format!("expected 0 or -1 but `magic_check()` returned {}", res), 143 | ), 144 | } 145 | } 146 | 147 | /// # Panics 148 | /// 149 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 150 | pub(crate) fn compile( 151 | cookie: &Cookie, 152 | filename: Option<&std::ffi::CStr>, 153 | ) -> Result<(), CookieError> { 154 | let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr); 155 | let res = unsafe { libmagic::magic_compile(cookie.0, filename_ptr) }; 156 | 157 | match res { 158 | 0 => Ok(()), 159 | -1 => Err(expect_error( 160 | cookie, 161 | "`magic_compile()` did not set last error".to_string(), 162 | )), 163 | res => api_violation( 164 | cookie, 165 | format!("Expected 0 or -1 but `magic_compile()` returned {}", res), 166 | ), 167 | } 168 | } 169 | 170 | /// # Panics 171 | /// 172 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 173 | pub(crate) fn list(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> { 174 | let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr); 175 | let res = unsafe { libmagic::magic_list(cookie.0, filename_ptr) }; 176 | 177 | match res { 178 | 0 => Ok(()), 179 | -1 => Err(expect_error( 180 | cookie, 181 | "`magic_list()` did not set last error".to_string(), 182 | )), 183 | res => api_violation( 184 | cookie, 185 | format!("Expected 0 or -1 but `magic_list()` returned {}", res), 186 | ), 187 | } 188 | } 189 | 190 | /// # Panics 191 | /// 192 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 193 | pub(crate) fn load(cookie: &Cookie, filename: Option<&std::ffi::CStr>) -> Result<(), CookieError> { 194 | let filename_ptr = filename.map_or_else(std::ptr::null, std::ffi::CStr::as_ptr); 195 | let res = unsafe { libmagic::magic_load(cookie.0, filename_ptr) }; 196 | 197 | match res { 198 | 0 => Ok(()), 199 | -1 => Err(expect_error( 200 | cookie, 201 | "`magic_load()` did not set last error".to_string(), 202 | )), 203 | res => api_violation( 204 | cookie, 205 | format!("Expected 0 or -1 but `magic_load()` returned {}", res), 206 | ), 207 | } 208 | } 209 | 210 | /// # Panics 211 | /// 212 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 213 | pub(crate) fn load_buffers(cookie: &Cookie, buffers: &[&[u8]]) -> Result<(), CookieError> { 214 | let mut ffi_buffers: Vec<*const u8> = Vec::with_capacity(buffers.len()); 215 | let mut ffi_sizes: Vec = Vec::with_capacity(buffers.len()); 216 | let ffi_nbuffers = buffers.len() as libc::size_t; 217 | 218 | for slice in buffers { 219 | ffi_buffers.push((*slice).as_ptr()); 220 | ffi_sizes.push(slice.len() as libc::size_t); 221 | } 222 | 223 | let ffi_buffers_ptr = ffi_buffers.as_mut_ptr() as *mut *mut libc::c_void; 224 | let ffi_sizes_ptr = ffi_sizes.as_mut_ptr(); 225 | 226 | let res = unsafe { 227 | libmagic::magic_load_buffers(cookie.0, ffi_buffers_ptr, ffi_sizes_ptr, ffi_nbuffers) 228 | }; 229 | 230 | match res { 231 | 0 => Ok(()), 232 | -1 => Err(expect_error( 233 | cookie, 234 | "`magic_load_buffers()` did not set last error".to_string(), 235 | )), 236 | res => api_violation( 237 | cookie, 238 | format!( 239 | "Expected 0 or -1 but `magic_load_buffers()` returned {}", 240 | res 241 | ), 242 | ), 243 | } 244 | } 245 | 246 | pub(crate) fn open(flags: libc::c_int) -> Result { 247 | let cookie = unsafe { libmagic::magic_open(flags) }; 248 | 249 | if cookie.is_null() { 250 | Err(OpenError { 251 | flags, 252 | // note that libmagic only really cares about MAGIC_PRESERVE_ATIME 253 | // invalid bits in `flags` still result in a valid cookie pointer 254 | errno: std::io::Error::last_os_error(), 255 | }) 256 | } else { 257 | Ok(Cookie(cookie)) 258 | } 259 | } 260 | 261 | #[derive(thiserror::Error, Debug)] 262 | #[error("could not open magic cookie with flags {}: {}", .flags, .errno)] 263 | pub(crate) struct OpenError { 264 | flags: libc::c_int, 265 | errno: std::io::Error, 266 | } 267 | 268 | impl OpenError { 269 | pub fn errno(&self) -> &std::io::Error { 270 | &self.errno 271 | } 272 | } 273 | 274 | pub(crate) fn version() -> libc::c_int { 275 | unsafe { libmagic::magic_version() } 276 | } 277 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © The `magic` Rust crate authors 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | //! # About 5 | //! 6 | //! This crate provides bindings for the `libmagic` C library, which recognizes the 7 | //! type of data contained in a file (or buffer). 8 | //! 9 | //! You might be familiar with `libmagic`'s command-line-interface; [`file`](https://www.darwinsys.com/file/): 10 | //! 11 | //! ```shell 12 | //! $ file data/tests/rust-logo-128x128-blk.png 13 | //! data/tests/rust-logo-128x128-blk.png: PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced 14 | //! ``` 15 | //! 16 | //! ## `libmagic` 17 | //! 18 | //! Understanding how the `libmagic` C library and thus this Rust crate works requires a bit of glossary. 19 | //! 20 | //! `libmagic` at its core can analyze a file or buffer and return a mostly unstructured text that describes the analysis result. 21 | //! There are built-in tests for special cases such as symlinks and compressed files 22 | //! and there are magic databases with signatures which can be supplied by the user for the generic cases 23 | //! ("if those bytes look like this, it's a PNG image file"). 24 | //! 25 | //! The analysis behaviour can be influenced by so-called flags and parameters. 26 | //! Flags are either set or unset and do not have a value, parameters have a value. 27 | //! 28 | //! Databases can be in text form or compiled binary form for faster access. They can be loaded from files on disk or from in-memory buffers. 29 | //! A regular `libmagic` / `file` installation contains a default database file that includes a plethora of file formats. 30 | //! 31 | //! Most `libmagic` functionality requires a configured instance which is called a "magic cookie". 32 | //! Creating a cookie instance requires initial flags and usually loaded databases. 33 | //! 34 | //! # Usage example 35 | //! 36 | //! ```rust 37 | //! # fn main() -> Result<(), Box> { 38 | //! # use std::convert::TryInto; 39 | //! // open a new configuration with flags 40 | //! let cookie = magic::Cookie::open(magic::cookie::Flags::ERROR)?; 41 | //! 42 | //! // load a specific database 43 | //! // (so exact test text assertion below works regardless of the system's default database version) 44 | //! let database = ["data/tests/db-images-png"].try_into()?; 45 | //! // you can instead load the default database 46 | //! //let database = Default::default(); 47 | //! 48 | //! let cookie = cookie.load(&database)?; 49 | //! 50 | //! // analyze a test file 51 | //! let file_to_analyze = "data/tests/rust-logo-128x128-blk.png"; 52 | //! let expected_analysis_result = "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced"; 53 | //! assert_eq!(cookie.file(file_to_analyze)?, expected_analysis_result); 54 | //! # Ok(()) 55 | //! # } 56 | //! ``` 57 | //! 58 | //! See further examples in [`examples/`](https://github.com/robo9k/rust-magic/tree/main/examples). 59 | //! 60 | //! # MIME example 61 | //! 62 | //! Return a MIME type with "charset" encoding parameter: 63 | //! 64 | //! ```shell 65 | //! $ file --mime data/tests/rust-logo-128x128-blk.png 66 | //! data/tests/rust-logo-128x128-blk.png: image/png; charset=binary 67 | //! ``` 68 | //! ```rust 69 | //! # fn main() -> Result<(), Box> { 70 | //! # use std::convert::TryInto; 71 | //! // open a new configuration with flags for mime type and encoding 72 | //! let flags = magic::cookie::Flags::MIME_TYPE | magic::cookie::Flags::MIME_ENCODING; 73 | //! let cookie = magic::Cookie::open(flags)?; 74 | //! 75 | //! // load a specific database 76 | //! let database = ["data/tests/db-images-png"].try_into()?; 77 | //! let cookie = cookie.load(&database)?; 78 | //! 79 | //! // analyze a test file 80 | //! let file_to_analyze = "data/tests/rust-logo-128x128-blk.png"; 81 | //! let expected_analysis_result = "image/png; charset=binary"; 82 | //! assert_eq!(cookie.file(file_to_analyze)?, expected_analysis_result); 83 | //! # Ok(()) 84 | //! # } 85 | //! ``` 86 | //! 87 | //! See [`magic::cookie::Flags::MIME`](crate::cookie::Flags::MIME). 88 | //! 89 | //! # Filename extensions example 90 | //! 91 | //! Return slash-separated filename extensions (the ".png" in "example.png") 92 | //! from file contents (the input filename is not used for detection): 93 | //! 94 | //! ```shell 95 | //! $ file --extension data/tests/rust-logo-128x128-blk.png 96 | //! data/tests/rust-logo-128x128-blk.png: png 97 | //! ``` 98 | //! ```rust 99 | //! # fn main() -> Result<(), Box> { 100 | //! # use std::convert::TryInto; 101 | //! // open a new configuration with flags for filename extension 102 | //! let flags = magic::cookie::Flags::EXTENSION; 103 | //! let cookie = magic::Cookie::open(flags)?; 104 | //! 105 | //! // load a specific database 106 | //! let database = ["data/tests/db-images-png"].try_into()?; 107 | //! let cookie = cookie.load(&database)?; 108 | //! 109 | //! // analyze a test file 110 | //! let file_to_analyze = "data/tests/rust-logo-128x128-blk.png"; 111 | //! let expected_analysis_result = "png"; 112 | //! assert_eq!(cookie.file(file_to_analyze)?, expected_analysis_result); 113 | //! # Ok(()) 114 | //! # } 115 | //! ``` 116 | //! 117 | //! See [`magic::cookie::Flags::EXTENSION`](crate::cookie::Flags::EXTENSION). 118 | //! 119 | //! # Further reading 120 | //! 121 | //! * [`Cookie::open()`][Cookie::open] 122 | //! * cookie [`Flags`](crate::cookie::Flags), in particular: 123 | //! * [`Flags::ERROR`](crate::cookie::Flags::ERROR) 124 | //! * [`Flags::MIME`](crate::cookie::Flags::MIME) 125 | //! * [`Flags::EXTENSION`](crate::cookie::Flags::EXTENSION) 126 | //! * [`Flags::CONTINUE`](crate::cookie::Flags::CONTINUE) 127 | //! * [`Flags::NO_CHECK_BUILTIN`](crate::cookie::Flags::NO_CHECK_BUILTIN) 128 | //! * [`Cookie::load()`](Cookie::load), [`Cookie::load_buffers()`](Cookie::load_buffers) 129 | //! * [`Cookie::file()`](Cookie::file), [`Cookie::buffer()`](Cookie::buffer) 130 | //! 131 | //! Note that while some `libmagic` functions return somewhat structured text, e.g. MIME types and file extensions, 132 | //! the `magic` crate does not attempt to parse them into Rust data types since the format is not guaranteed by the C FFI API. 133 | //! 134 | //! Check the [crate README](https://crates.io/crates/magic) for required dependencies and MSRV. 135 | //! 136 | //! # Safety 137 | //! 138 | //! This crate is a binding to the `libmagic` C library and as such subject to its security problems. 139 | //! Please note that `libmagic` has several CVEs, listed on e.g. [Repology](https://repology.org/project/file/cves). 140 | //! Make sure that you are using an up-to-date version of `libmagic` and ideally 141 | //! add additional security layers such as sandboxing (which this crate does _not_ provide) 142 | //! and __do not use it on untrusted input__ e.g. from users on the internet! 143 | //! 144 | //! The Rust code of this crate needs to use some `unsafe` for interacting with the `libmagic` C FFI. 145 | //! 146 | //! This crate has not been audited nor is it ready for production use. 147 | //! 148 | //! This Rust project / crate is not affiliated with the original `file` / `libmagic` C project. 149 | //! 150 | //! # Use cases 151 | //! 152 | //! `libmagic` can help to identify unknown content. It does this by looking at byte patterns, among other things. 153 | //! This does not guarantee that e.g. a file which is detected as a PNG image is indeed a valid PNG image. 154 | //! 155 | //! Maybe you just want a mapping from file name extensions to MIME types instead, e.g. ".png" ↔ "image/png"? 156 | //! In this case you do not even need to look at file contents and could use e.g. the [`mime_guess` crate](https://crates.io/crates/mime_guess). 157 | //! 158 | //! Maybe you want to be certain that a file is valid for a kown format, e.g. a PNG image? 159 | //! In this case you should use a parser for that format specifically, e.g. the [`image` crate](https://crates.io/crates/image). 160 | //! 161 | //! Maybe you want to know if a file contains other, malicious content? 162 | //! In this case you should use an anti-virus software, e.g. [ClamAV](https://www.clamav.net/), [Virus Total](https://www.virustotal.com/). 163 | //! 164 | //! Maybe you need to support platforms that are tricky with the `libmagic` C library, e.g. wasm32? 165 | //! In this case you could use pure Rust alternatives, e.g. the [`file_type` crate](https://crates.io/crates/file_type). 166 | 167 | #![deny(unsafe_code)] 168 | 169 | mod ffi; 170 | 171 | /// Returns the version of the `libmagic` C library as reported by itself. 172 | /// 173 | /// # Examples 174 | /// A version of "5.41" is returned as `541`. 175 | #[doc(alias = "magic_version")] 176 | pub fn libmagic_version() -> libc::c_int { 177 | crate::ffi::version() 178 | } 179 | 180 | /// Functionality for [`Cookie`] 181 | pub mod cookie { 182 | use std::convert::TryFrom; 183 | use std::ffi::CString; 184 | use std::path::Path; 185 | 186 | use magic_sys as libmagic; 187 | 188 | bitflags::bitflags! { 189 | /// Configuration bits for [`Cookie`] 190 | /// 191 | /// A bitflags instance is a combined set of individual flags. 192 | /// `cookie::Flags` are configuration bits for `Cookie` instances that specify how the cookie should behave. 193 | /// 194 | /// `cookie::Flags` influence several functions, e.g. [`Cookie::file()`](Cookie::file) 195 | /// but also [`Cookie::load()`](Cookie::load). 196 | /// 197 | /// Flags are initially set when a new cookie is created with [`Cookie::open()`](Cookie::open) 198 | /// and can be overwritten lateron with [`Cookie::set_flags()`](Cookie::set_flags). 199 | /// 200 | /// Flags of particular interest: 201 | /// - [`ERROR`](Flags::ERROR) 202 | /// - [`MIME`](Flags::MIME) 203 | /// - [`EXTENSION`](Flags::EXTENSION) 204 | /// - [`CONTINUE`](Flags::CONTINUE) 205 | /// - [`NO_CHECK_BUILTIN`](Flags::NO_CHECK_BUILTIN) 206 | /// 207 | /// # Examples 208 | /// 209 | /// ``` 210 | /// // default flags 211 | /// // `: Flags` type annotation is only needed for this example 212 | /// // if you pass it to Cookie::open() etc., Rust will figure it out 213 | /// let flags: magic::cookie::Flags = Default::default(); 214 | /// 215 | /// // custom flags combination 216 | /// let flags = magic::cookie::Flags::COMPRESS | magic::cookie::Flags::DEVICES; 217 | /// ``` 218 | /// 219 | /// # Errors 220 | /// 221 | /// Some flags might not be supported on all platforms, i.e. 222 | /// - [`Cookie::open()`](Cookie::open) might return a [`cookie::OpenError`](OpenError) 223 | /// - [`Cookie::set_flags()`](Cookie::set_flags) might return a [`cookie::SetFlagsError`](SetFlagsError) 224 | /// 225 | /// # Misc 226 | /// 227 | /// NOTE: The flag descriptions are mostly copied from `man libmagic 3`. 228 | /// 229 | /// `MAGIC_NONE` is the default, meaning "No special handling" and has no constant. 230 | /// ``` 231 | /// let default_flags: magic::cookie::Flags = Default::default(); 232 | /// assert_eq!(default_flags, magic::cookie::Flags::empty()); 233 | /// ``` 234 | #[derive(std::default::Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] 235 | pub struct Flags: libc::c_int { 236 | // MAGIC_NONE is 0/default, see https://docs.rs/bitflags/latest/bitflags/#zero-bit-flags 237 | 238 | // Define unnamed flag for all other bits https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags 239 | const _ = !0; 240 | 241 | /// Print debugging messages to `stderr` 242 | /// 243 | /// This is equivalent to the `file` CLI option `--debug`. 244 | /// 245 | /// NOTE: Those messages are printed by `libmagic` itself, no this Rust crate. 246 | #[doc(alias = "MAGIC_DEBUG")] 247 | #[doc(alias = "--debug")] 248 | const DEBUG = libmagic::MAGIC_DEBUG; 249 | 250 | /// If the file queried is a symlink, follow it 251 | /// 252 | /// This is equivalent to the `file` CLI option `--dereference`. 253 | #[doc(alias = "MAGIC_SYMLINK")] 254 | #[doc(alias = "--dereference")] 255 | const SYMLINK = libmagic::MAGIC_SYMLINK; 256 | 257 | /// If the file is compressed, unpack it and look at the contents 258 | /// 259 | /// This is equivalent to the `file` CLI option `--uncompress`. 260 | #[doc(alias = "MAGIC_COMPRESS")] 261 | #[doc(alias = "--uncompress")] 262 | const COMPRESS = libmagic::MAGIC_COMPRESS; 263 | 264 | /// If the file is a block or character special device, then open the device and try to look in its contents 265 | /// 266 | /// This is equivalent to the `file` CLI option `--special-files`. 267 | #[doc(alias = "MAGIC_DEVICES")] 268 | #[doc(alias = "--special-files")] 269 | const DEVICES = libmagic::MAGIC_DEVICES; 270 | 271 | /// Return a MIME type string, instead of a textual description 272 | /// 273 | /// See also: [`Flags::MIME`] 274 | /// 275 | /// This is equivalent to the `file` CLI option `--mime-type`. 276 | /// 277 | /// NOTE: `libmagic` uses non-standard MIME types for at least some built-in checks, 278 | /// e.g. `inode/*` (also see [`Flags::SYMLINK`], [`Flags::DEVICES`]): 279 | /// ```shell 280 | /// $ file --mime-type /proc/self/exe 281 | /// /proc/self/exe: inode/symlink 282 | /// 283 | /// $ file --mime-type /dev/sda 284 | /// /dev/sda: inode/blockdevice 285 | /// ``` 286 | #[doc(alias = "MAGIC_MIME_TYPE")] 287 | #[doc(alias = "--mime-type")] 288 | const MIME_TYPE = libmagic::MAGIC_MIME_TYPE; 289 | 290 | /// Return all matches, not just the first 291 | /// 292 | /// This is equivalent to the `file` CLI option `--keep-going`. 293 | #[doc(alias = "MAGIC_CONTINUE")] 294 | #[doc(alias = "--keep-going")] 295 | const CONTINUE = libmagic::MAGIC_CONTINUE; 296 | 297 | /// Check the magic database for consistency and print warnings to `stderr` 298 | /// 299 | /// NOTE: Those warnings are printed by `libmagic` itself, no this Rust crate. 300 | #[doc(alias = "MAGIC_CHECK")] 301 | const CHECK = libmagic::MAGIC_CHECK; 302 | 303 | /// On systems that support `utime(2)` or `utimes(2)`, attempt to preserve the access time of files analyzed 304 | /// 305 | /// This is equivalent to the `file` CLI option `--preserve-date`. 306 | #[doc(alias = "MAGIC_PRESERVE_ATIME")] 307 | #[doc(alias = "--preserve-date")] 308 | const PRESERVE_ATIME = libmagic::MAGIC_PRESERVE_ATIME; 309 | 310 | /// Don't translate unprintable characters to a `\\ooo` octal representation 311 | /// 312 | /// This is equivalent to the `file` CLI option `--raw`. 313 | #[doc(alias = "MAGIC_RAW")] 314 | #[doc(alias = "--raw")] 315 | const RAW = libmagic::MAGIC_RAW; 316 | 317 | /// Treat operating system errors while trying to open files and follow symlinks as real errors, instead of printing them in the magic buffer 318 | #[doc(alias = "MAGIC_ERROR")] 319 | const ERROR = libmagic::MAGIC_ERROR; 320 | 321 | /// Return a MIME encoding, instead of a textual description 322 | /// 323 | /// See also: [`Flags::MIME`] 324 | /// 325 | /// This is equivalent to the `file` CLI option `--mime-encoding`. 326 | /// 327 | /// NOTE: `libmagic` uses non-standard MIME `charset` values, e.g. for binary files: 328 | /// ```shell 329 | /// $ file --mime-encoding /proc/self/exe 330 | /// binary 331 | /// ``` 332 | #[doc(alias = "MAGIC_MIME_ENCODING")] 333 | #[doc(alias = "--mime-encoding")] 334 | const MIME_ENCODING = libmagic::MAGIC_MIME_ENCODING; 335 | 336 | /// A shorthand for `MIME_TYPE | MIME_ENCODING` 337 | /// 338 | /// See also: [`Flags::MIME_TYPE`], [`Flags::MIME_ENCODING`] 339 | /// 340 | /// This is equivalent to the `file` CLI option `--mime`. 341 | /// 342 | /// NOTE: `libmagic` returns a parseable MIME type with a `charset` field: 343 | /// ```shell 344 | /// $ file --mime /proc/self/exe 345 | /// /proc/self/exe: inode/symlink; charset=binary 346 | /// ``` 347 | #[doc(alias = "MAGIC_MIME")] 348 | #[doc(alias = "--mime")] 349 | const MIME = Self::MIME_TYPE.bits() 350 | | Self::MIME_ENCODING.bits(); 351 | 352 | /// Return the Apple creator and type 353 | /// 354 | /// This is equivalent to the `file` CLI option `--apple`. 355 | #[doc(alias = "MAGIC_APPLE")] 356 | #[doc(alias = "--apple")] 357 | const APPLE = libmagic::MAGIC_APPLE; 358 | 359 | /// Return a slash-separated list of extensions for this file type 360 | /// 361 | /// This is equivalent to the `file` CLI option `--extension`. 362 | /// 363 | /// NOTE: `libmagic` returns a list with one or more extensions without a leading "." (dot): 364 | /// ```shell 365 | /// $ file --extension example.jpg 366 | /// example.jpg: jpeg/jpg/jpe/jfif 367 | /// 368 | /// $ file --extension /proc/self/exe 369 | /// /proc/self/exe: ??? 370 | /// ``` 371 | #[doc(alias = "MAGIC_EXTENSION")] 372 | #[doc(alias = "--extension")] 373 | const EXTENSION = libmagic::MAGIC_EXTENSION; 374 | 375 | /// Don't report on compression, only report about the uncompressed data 376 | /// 377 | /// This is equivalent to the `file` CLI option `--uncompress-noreport`. 378 | #[doc(alias = "MAGIC_COMPRESS_TRANSP")] 379 | #[doc(alias = "--uncompress-noreport")] 380 | const COMPRESS_TRANSP = libmagic::MAGIC_COMPRESS_TRANSP; 381 | 382 | /// A shorthand for `EXTENSION | MIME | APPLE` 383 | #[doc(alias = "MAGIC_NODESC")] 384 | const NODESC = Self::EXTENSION.bits() 385 | | Self::MIME.bits() 386 | | Self::APPLE.bits(); 387 | 388 | /// Don't look inside compressed files 389 | /// 390 | /// This is equivalent to the `file` CLI option `--exclude compress`. 391 | #[doc(alias = "MAGIC_NO_CHECK_COMPRESS")] 392 | #[doc(alias = "--exclude compress")] 393 | const NO_CHECK_COMPRESS = libmagic::MAGIC_NO_CHECK_COMPRESS; 394 | 395 | /// Don't examine tar files 396 | /// 397 | /// This is equivalent to the `file` CLI option `--exclude tar`. 398 | #[doc(alias = "MAGIC_NO_CHECK_TAR")] 399 | #[doc(alias = "--exclude tar")] 400 | const NO_CHECK_TAR = libmagic::MAGIC_NO_CHECK_TAR; 401 | 402 | /// Don't consult magic files 403 | /// 404 | /// This is equivalent to the `file` CLI option `--exclude soft`. 405 | #[doc(alias = "MAGIC_NO_CHECK_SOFT")] 406 | #[doc(alias = "--exclude soft")] 407 | const NO_CHECK_SOFT = libmagic::MAGIC_NO_CHECK_SOFT; 408 | 409 | /// Check for EMX application type (only on EMX) 410 | /// 411 | /// This is equivalent to the `file` CLI option `--exclude apptype`. 412 | #[doc(alias = "MAGIC_NO_CHECK_APPTYPE")] 413 | #[doc(alias = "--exclude apptype")] 414 | const NO_CHECK_APPTYPE = libmagic::MAGIC_NO_CHECK_APPTYPE; 415 | 416 | /// Don't print ELF details 417 | /// 418 | /// This is equivalent to the `file` CLI option `--exclude elf`. 419 | #[doc(alias = "MAGIC_NO_CHECK_ELF")] 420 | #[doc(alias = "--exclude elf")] 421 | const NO_CHECK_ELF = libmagic::MAGIC_NO_CHECK_ELF; 422 | 423 | /// Don't check for various types of text files 424 | /// 425 | /// This is equivalent to the `file` CLI option `--exclude text`. 426 | #[doc(alias = "MAGIC_NO_CHECK_TEXT")] 427 | #[doc(alias = "--exclude text")] 428 | const NO_CHECK_TEXT = libmagic::MAGIC_NO_CHECK_TEXT; 429 | 430 | /// Don't get extra information on MS Composite Document Files 431 | /// 432 | /// This is equivalent to the `file` CLI option `--exclude cdf`. 433 | #[doc(alias = "MAGIC_NO_CHECK_CDF")] 434 | #[doc(alias = "--exclude cdf")] 435 | const NO_CHECK_CDF = libmagic::MAGIC_NO_CHECK_CDF; 436 | 437 | /// Don't examine CSV files 438 | /// 439 | /// This is equivalent to the `file` CLI option `--exclude csv`. 440 | #[doc(alias = "MAGIC_NO_CHECK_CSV")] 441 | #[doc(alias = "--exclude csv")] 442 | const NO_CHECK_CSV = libmagic::MAGIC_NO_CHECK_CSV; 443 | 444 | /// Don't look for known tokens inside ascii files 445 | /// 446 | /// This is equivalent to the `file` CLI option `--exclude tokens`. 447 | #[doc(alias = "MAGIC_NO_CHECK_TOKENS")] 448 | #[doc(alias = "--exclude tokens")] 449 | const NO_CHECK_TOKENS = libmagic::MAGIC_NO_CHECK_TOKENS; 450 | 451 | /// Don't check text encodings 452 | /// 453 | /// This is equivalent to the `file` CLI option `--exclude encoding`. 454 | #[doc(alias = "MAGIC_NO_CHECK_ENCODING")] 455 | #[doc(alias = "--exclude encoding")] 456 | const NO_CHECK_ENCODING = libmagic::MAGIC_NO_CHECK_ENCODING; 457 | 458 | /// Don't examine JSON files 459 | /// 460 | /// This is equivalent to the `file` CLI option `--exclude json`. 461 | #[doc(alias = "MAGIC_NO_CHECK_JSON")] 462 | #[doc(alias = "--exclude json")] 463 | const NO_CHECK_JSON = libmagic::MAGIC_NO_CHECK_JSON; 464 | 465 | /// No built-in tests; only consult the magic file 466 | #[doc(alias = "MAGIC_NO_CHECK_BUILTIN")] 467 | const NO_CHECK_BUILTIN = Self::NO_CHECK_COMPRESS.bits() 468 | | Self::NO_CHECK_TAR.bits() 469 | | Self::NO_CHECK_APPTYPE.bits() 470 | | Self::NO_CHECK_ELF.bits() 471 | | Self::NO_CHECK_TEXT.bits() 472 | | Self::NO_CHECK_CSV.bits() 473 | | Self::NO_CHECK_CDF.bits() 474 | | Self::NO_CHECK_TOKENS.bits() 475 | | Self::NO_CHECK_ENCODING.bits() 476 | | Self::NO_CHECK_JSON.bits(); 477 | } 478 | } 479 | 480 | impl std::fmt::Display for Flags { 481 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 482 | bitflags::parser::to_writer(self, f) 483 | } 484 | } 485 | 486 | /// Invalid [`DatabasePaths`] 487 | /// 488 | /// This is returned from [`DatabasePaths::new()`](DatabasePaths::new) 489 | #[derive(thiserror::Error, Debug)] 490 | #[error("invalid database files path")] 491 | pub struct InvalidDatabasePathError {} 492 | 493 | /// Magic database file paths 494 | /// 495 | /// `libmagic` requires database file paths for certain operations on a [`Cookie`] that must: 496 | /// - be a valid C string 497 | /// - not contain ":" (colon), since that is used to separate multiple file paths (on all platforms) 498 | /// 499 | /// Those operations are [`Cookie::load()`](Cookie::load), [`Cookie::compile()`](Cookie::compile), [`Cookie::check()`](Cookie::check), [`Cookie::list()`](Cookie::list).\ 500 | /// [`Cookie::file()`](Cookie::file) does not take database file paths but the single file to inspect instead. 501 | /// 502 | /// The default unnamed database can be constructed with [`Default::default()`](DatabasePaths::default). 503 | /// Explicit paths can be constructed manually with [`new()`](DatabasePaths::new) or by fallible conversion from an array, slice or Vec 504 | /// containing something convertible as [`std::path::Path`], or a single something. 505 | /// 506 | /// Note that this only ensures the paths themselves are valid. 507 | /// Operating on those database file paths can still fail, 508 | /// for example if they refer to files that do not exist, can not be opened or do not have the required format. 509 | /// 510 | /// # Examples 511 | /// 512 | /// ``` 513 | /// # use std::convert::TryInto; 514 | /// # use magic::cookie::DatabasePaths; 515 | /// # fn main() -> Result<(), Box> { 516 | /// // `: DatabasePaths` type annotation is only needed for these examples 517 | /// // if you pass it to Cookie::load() etc., Rust will figure it out 518 | /// 519 | /// // construct default unnamed database paths 520 | /// let database: DatabasePaths = Default::default(); 521 | /// 522 | /// // construct from single path 523 | /// let database: DatabasePaths = "first-directory/first-database".try_into()?; 524 | /// let database: DatabasePaths = 525 | /// std::path::Path::new("second-directory/second-database").try_into()?; 526 | /// 527 | /// // construct from multiple paths in array 528 | /// let array: [&'static str; 2] = [ 529 | /// "first-directory/first-database", 530 | /// "second-directory/second-database", 531 | /// ]; 532 | /// let database: DatabasePaths = array.try_into()?; 533 | /// 534 | /// // construct from multiple paths in slice 535 | /// let database: DatabasePaths = [ 536 | /// "first-directory/first-database".as_ref(), 537 | /// std::path::Path::new("second-directory/second-database"), 538 | /// ] 539 | /// .try_into()?; 540 | /// 541 | /// // construct from multiple paths in Vec 542 | /// let database: DatabasePaths = vec![ 543 | /// std::ffi::OsStr::new("first-directory/first-database"), 544 | /// "second-directory/second-database".as_ref(), 545 | /// ] 546 | /// .try_into()?; 547 | /// # Ok(()) 548 | /// # } 549 | /// ``` 550 | pub struct DatabasePaths { 551 | filenames: Option, 552 | } 553 | 554 | const DATABASE_FILENAME_SEPARATOR: &str = ":"; 555 | 556 | impl DatabasePaths { 557 | /// Create a new database paths instance 558 | /// 559 | /// Using one of the `TryFrom` implementations is recommended instead, see [`DatabasePaths`] examples. 560 | /// 561 | /// Empty `paths` returns [`Default::default()`](DatabasePaths::default). 562 | /// 563 | /// # Errors 564 | /// 565 | /// If the `paths` contain a ":" (colon), a [`cookie::InvalidDatabasePathError`](InvalidDatabasePathError) will be returned. 566 | /// 567 | pub fn new(paths: I) -> Result 568 | where 569 | I: IntoIterator, 570 | P: AsRef, 571 | { 572 | // this is not the most efficient nor correct for Windows, but consistent with previous behaviour 573 | 574 | let filename = paths 575 | .into_iter() 576 | .map(|f| f.as_ref().to_string_lossy().into_owned()) 577 | .collect::>() 578 | .join(DATABASE_FILENAME_SEPARATOR); 579 | 580 | Ok(Self { 581 | filenames: match filename.is_empty() { 582 | true => None, 583 | _ => Some(CString::new(filename).map_err(|_| InvalidDatabasePathError {})?), 584 | }, 585 | }) 586 | } 587 | } 588 | 589 | impl Default for DatabasePaths { 590 | /// Returns the path for the default unnamed database/s 591 | /// 592 | /// Note that the default database/s can be overwritten by setting the "MAGIC" environment variable 593 | /// to a colon-separated text of database file paths: 594 | /// ```shell 595 | /// $ export MAGIC='data/tests/db-python:data/tests/db-images-png-precompiled.mgc' 596 | /// $ # file-ish uses `DatabasePaths::default()` 597 | /// $ cargo run --example file-ish -- data/tests/rust-logo-128x128-blk.png 598 | /// ``` 599 | /// This is a feature of `libmagic` itself, not of this Rust crate. 600 | /// 601 | /// Note that the `file` CLI (which uses `libmagic`) prints the location of its default database with: 602 | /// ```shell 603 | /// $ file --version 604 | /// file-5.38 605 | /// magic file from /etc/magic:/usr/share/misc/magic 606 | /// 607 | /// $ export MAGIC='data/tests/db-python:data/tests/db-images-png-precompiled.mgc' 608 | /// $ file --version 609 | /// file-5.39 610 | /// magic file from data/tests/db-python:data/tests/db-images-png-precompiled.mgc 611 | /// ``` 612 | fn default() -> Self { 613 | Self { filenames: None } 614 | } 615 | } 616 | 617 | impl, const N: usize> TryFrom<[P; N]> for DatabasePaths { 618 | type Error = InvalidDatabasePathError; 619 | 620 | /// Invokes [`DatabasePaths::new()`](DatabasePaths::new) 621 | fn try_from(value: [P; N]) -> Result>::Error> { 622 | Self::new(value) 623 | } 624 | } 625 | 626 | impl> TryFrom> for DatabasePaths { 627 | type Error = InvalidDatabasePathError; 628 | 629 | /// Invokes [`DatabasePaths::new()`](DatabasePaths::new) 630 | fn try_from(value: Vec

) -> Result>>::Error> { 631 | Self::new(value) 632 | } 633 | } 634 | 635 | impl> TryFrom<&'_ [P]> for DatabasePaths { 636 | type Error = InvalidDatabasePathError; 637 | 638 | /// Invokes [`DatabasePaths::new()`](DatabasePaths::new) 639 | fn try_from(value: &[P]) -> Result>::Error> { 640 | Self::new(value) 641 | } 642 | } 643 | 644 | macro_rules! databasepaths_try_from_impl { 645 | ($t:ty) => { 646 | impl TryFrom<$t> for DatabasePaths { 647 | type Error = InvalidDatabasePathError; 648 | 649 | /// Invokes [`DatabasePaths::new()`](DatabasePaths::new) 650 | fn try_from(value: $t) -> Result>::Error> { 651 | DatabasePaths::new(std::iter::once(value)) 652 | } 653 | } 654 | }; 655 | } 656 | 657 | // missing for now are: 658 | // - Cow<'_, OsStr> 659 | // - std::path::Component<'_> 660 | // - std::path::Components<'_> 661 | // - std::path::Iter<'_> 662 | databasepaths_try_from_impl!(&str); 663 | databasepaths_try_from_impl!(&std::ffi::OsStr); 664 | databasepaths_try_from_impl!(std::ffi::OsString); 665 | databasepaths_try_from_impl!(&std::path::Path); 666 | databasepaths_try_from_impl!(std::path::PathBuf); 667 | databasepaths_try_from_impl!(String); 668 | 669 | /// Error within several [`Cookie`] functions 670 | /// 671 | /// Most functions on a [`Cookie`] can return an error from `libmagic`, 672 | /// which unfortunately is not very structured. 673 | #[derive(thiserror::Error, Debug)] 674 | #[error("magic cookie error in `libmagic` function {}", .function)] 675 | pub struct Error { 676 | function: &'static str, 677 | //#[backtrace] 678 | source: crate::ffi::CookieError, 679 | } 680 | 681 | #[doc(hidden)] 682 | #[derive(Debug)] 683 | pub enum Open {} 684 | 685 | #[doc(hidden)] 686 | #[derive(Debug)] 687 | pub enum Load {} 688 | 689 | mod private { 690 | pub trait Sealed {} 691 | 692 | impl Sealed for super::Open {} 693 | impl Sealed for super::Load {} 694 | } 695 | 696 | #[doc(hidden)] 697 | pub trait State: private::Sealed {} 698 | 699 | impl State for Open {} 700 | impl State for Load {} 701 | 702 | /// Combined configuration of [`Flags`], parameters and databases 703 | /// 704 | /// A "cookie" is `libmagic` lingo for a combined configuration of 705 | /// - [`cookie::Flags`](crate::cookie::Flags) 706 | /// - parameters (not implemented yet) 707 | /// - loaded datbases, e.g. [`cookie::DatabasePaths`](crate::cookie::DatabasePaths) 708 | /// 709 | /// A cookie advances through 2 states: opened, then loaded. 710 | /// ``` 711 | /// # fn main() -> Result<(), Box> { 712 | /// // create new cookie in initial opened state with given flags 713 | /// let cookie = magic::Cookie::open(magic::cookie::Flags::default())?; 714 | /// 715 | /// // advance cookie into loaded state 716 | /// let cookie = cookie.load(&magic::cookie::DatabasePaths::default())?; 717 | /// # Ok(()) 718 | /// # } 719 | /// ``` 720 | /// 721 | /// In either state, you can use operations that do not require 722 | /// already loaded magic databases: 723 | /// - [`Cookie::load()`](Cookie::load), [`Cookie::load_buffers()`](Cookie::load_buffers) to load databases and transition into the loaded state 724 | /// - [`Cookie::set_flags()`](Cookie::set_flags) to overwrite the initial flags given in [`Cookie::open()`](Cookie::open) 725 | /// - [`Cookie::compile()`](Cookie::compile), [`Cookie::check()`](Cookie::check), [`Cookie::list()`](Cookie::list) to operate on magic database files 726 | /// 727 | /// Once in the loaded state, you can perform magic "queries": 728 | /// - [`Cookie::file()`](Cookie::file), [`Cookie::buffer()`](Cookie::buffer) 729 | #[derive(Debug)] 730 | #[doc(alias = "magic_t")] 731 | #[doc(alias = "magic_set")] 732 | pub struct Cookie { 733 | cookie: crate::ffi::Cookie, 734 | marker: std::marker::PhantomData, 735 | } 736 | 737 | /// Error within [`Cookie::load()`](Cookie::load) or [`Cookie::load_buffers()`](Cookie::load_buffers) 738 | /// 739 | /// This is like [`cookie:Error`](Error) but also has the cookie in its original state. 740 | /// 741 | /// # Examples 742 | /// 743 | /// ``` 744 | /// # use std::convert::TryInto; 745 | /// # fn main() -> Result<(), Box> { 746 | /// let cookie = magic::Cookie::open(Default::default())?; 747 | /// let database = "data/tests/db-images-png".try_into()?; 748 | /// // try to load an existing database, consuming and returning early 749 | /// let cookie = cookie.load(&database)?; 750 | /// 751 | /// let database = "doesntexist.mgc".try_into()?; 752 | /// // load a database that does not exist 753 | /// let cookie = match cookie.load(&database) { 754 | /// Err(err) => { 755 | /// println!("whoopsie: {:?}", err); 756 | /// // recover the loaded cookie without dropping it 757 | /// err.cookie() 758 | /// }, 759 | /// Ok(cookie) => cookie, 760 | /// }; 761 | /// 762 | /// let database = "data/tests/db-python".try_into()?; 763 | /// // try to load another existing database 764 | /// let cookie = cookie.load(&database)?; 765 | /// # Ok(()) 766 | /// # } 767 | /// ``` 768 | #[derive(thiserror::Error, Debug)] 769 | #[error("magic cookie error in `libmagic` function {}", .function)] 770 | pub struct LoadError { 771 | function: &'static str, 772 | //#[backtrace] 773 | source: crate::ffi::CookieError, 774 | cookie: Cookie, 775 | } 776 | 777 | impl LoadError { 778 | /// Returns the cookie in its original state 779 | pub fn cookie(self) -> Cookie { 780 | self.cookie 781 | } 782 | } 783 | 784 | impl Drop for Cookie { 785 | /// Closes the loaded magic database files and deallocates any resources used 786 | #[doc(alias = "magic_close")] 787 | fn drop(&mut self) { 788 | crate::ffi::close(&mut self.cookie); 789 | } 790 | } 791 | 792 | /// Operations that are valid in the `Open` state 793 | /// 794 | /// A new cookie created with [`Cookie::open`](Cookie::open) does not have any databases [loaded](Cookie::load). 795 | impl Cookie { 796 | /// Creates a new configuration cookie, `flags` specify how other operations on this cookie should behave 797 | /// 798 | /// This does not [`load()`](Cookie::load) any databases yet. 799 | /// 800 | /// The `flags` can be changed lateron with [`set_flags()`](Cookie::set_flags). 801 | /// 802 | /// # Examples 803 | /// 804 | /// ``` 805 | /// # fn main() -> Result<(), Box> { 806 | /// // open a new cookie with default flags 807 | /// let cookie = magic::Cookie::open(Default::default())?; 808 | /// 809 | /// // open a new cookie with custom flags 810 | /// let flags = magic::cookie::Flags::COMPRESS | magic::cookie::Flags::DEVICES; 811 | /// let cookie = magic::Cookie::open(flags)?; 812 | /// # Ok(()) 813 | /// # } 814 | /// ``` 815 | /// 816 | /// # Errors 817 | /// 818 | /// If there was an `libmagic` internal error allocating a new cookie, a [`cookie::OpenError`](OpenError) will be returned. 819 | /// 820 | /// If the given `flags` are unsupported on the current platform, a [`cookie::OpenError`](OpenError) will be returned. 821 | #[doc(alias = "magic_open")] 822 | pub fn open(flags: Flags) -> Result, OpenError> { 823 | match crate::ffi::open(flags.bits()) { 824 | Err(err) => Err(OpenError { 825 | flags, 826 | kind: match err.errno().kind() { 827 | std::io::ErrorKind::InvalidInput => OpenErrorKind::UnsupportedFlags, 828 | _ => OpenErrorKind::Errno, 829 | }, 830 | source: err, 831 | }), 832 | Ok(cookie) => { 833 | let cookie = Cookie { 834 | cookie, 835 | marker: std::marker::PhantomData, 836 | }; 837 | Ok(cookie) 838 | } 839 | } 840 | } 841 | } 842 | 843 | /// Operations that are valid in the `Load` state 844 | /// 845 | /// An opened cookie with [loaded](Cookie::load) databases can inspect [files](Cookie::file) and [buffers](Cookie::buffer). 846 | impl Cookie { 847 | /// Returns a textual description of the contents of the file `filename` 848 | /// 849 | /// Requires to [`load()`](Cookie::load) databases before calling. 850 | /// 851 | /// # Examples 852 | /// 853 | /// ``` 854 | /// # fn main() -> Result<(), Box> { 855 | /// // open a new cookie with default flags and database 856 | /// let cookie = magic::Cookie::open(Default::default())?.load(&Default::default())?; 857 | /// 858 | /// let file_description = cookie.file("data/tests/rust-logo-128x128-blk.png"); 859 | /// # Ok(()) 860 | /// # } 861 | /// ``` 862 | /// 863 | /// # Errors 864 | /// 865 | /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned. 866 | /// 867 | /// # Panics 868 | /// 869 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error. 870 | #[doc(alias = "magic_file")] 871 | pub fn file>(&self, filename: P) -> Result { 872 | let c_string = CString::new(filename.as_ref().to_string_lossy().into_owned()).unwrap(); 873 | match crate::ffi::file(&self.cookie, c_string.as_c_str()) { 874 | Ok(res) => Ok(res.to_string_lossy().to_string()), 875 | Err(err) => Err(Error { 876 | function: "magic_file", 877 | source: err, 878 | }), 879 | } 880 | } 881 | 882 | /// Returns a textual description of the contents of the `buffer` 883 | /// 884 | /// Requires to [`load()`](Cookie::load) databases before calling. 885 | /// 886 | /// # Examples 887 | /// 888 | /// ``` 889 | /// # fn main() -> Result<(), Box> { 890 | /// // open a new cookie with default flags and database 891 | /// let cookie = magic::Cookie::open(Default::default())?.load(&Default::default())?; 892 | /// 893 | /// let buffer = b"%PDF-\xE2\x80\xA6"; 894 | /// let buffer_description = cookie.buffer(buffer); 895 | /// # Ok(()) 896 | /// # } 897 | /// ``` 898 | /// 899 | /// # Errors 900 | /// 901 | /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned. 902 | /// 903 | /// # Panics 904 | /// 905 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error. 906 | #[doc(alias = "magic_buffer")] 907 | pub fn buffer(&self, buffer: &[u8]) -> Result { 908 | match crate::ffi::buffer(&self.cookie, buffer) { 909 | Ok(res) => Ok(res.to_string_lossy().to_string()), 910 | Err(err) => Err(Error { 911 | function: "magic_buffer", 912 | source: err, 913 | }), 914 | } 915 | } 916 | } 917 | 918 | /// Operations that are valid in any state 919 | impl Cookie { 920 | /// Loads the given database `filenames` for further queries 921 | /// 922 | /// Adds ".mgc" to the database filenames as appropriate. 923 | /// 924 | /// Calling `load()` or [`load_buffers()`](Cookie::load_buffers) replaces the previously loaded database/s. 925 | /// 926 | /// This is equivalent to the using the `file` CLI: 927 | /// ```shell 928 | /// $ file --magic-file 'data/tests/db-images-png:data/tests/db-python' --version 929 | /// file-5.39 930 | /// magic file from data/tests/db-images-png:data/tests/db-python 931 | /// ``` 932 | /// 933 | /// # Examples 934 | /// ```rust 935 | /// # use std::convert::TryInto; 936 | /// # fn main() -> Result<(), Box> { 937 | /// // open a new cookie with default flags 938 | /// let cookie = magic::Cookie::open(Default::default())?; 939 | /// 940 | /// // load the default unnamed database 941 | /// let database = Default::default(); 942 | /// let cookie = cookie.load(&database)?; 943 | /// 944 | /// // load databases from files 945 | /// let databases = ["data/tests/db-images-png", "data/tests/db-python"].try_into()?; 946 | /// let cookie = cookie.load(&databases)?; 947 | /// 948 | /// // load precompiled database from file 949 | /// let database = "data/tests/db-images-png-precompiled.mgc".try_into()?; 950 | /// let cookie = cookie.load(&database)?; 951 | /// # Ok(()) 952 | /// # } 953 | /// ``` 954 | /// 955 | /// # Errors 956 | /// 957 | /// If there was an `libmagic` internal error, a [`cookie::LoadError`](LoadError) will be returned, 958 | /// which contains the cookie in its original state. 959 | /// 960 | /// # Panics 961 | /// 962 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 963 | #[doc(alias = "magic_load")] 964 | #[doc(alias = "--magic-file")] 965 | pub fn load(self, filenames: &DatabasePaths) -> Result, LoadError> { 966 | match crate::ffi::load(&self.cookie, filenames.filenames.as_deref()) { 967 | Err(err) => Err(LoadError { 968 | function: "magic_load", 969 | source: err, 970 | cookie: self, 971 | }), 972 | Ok(_) => { 973 | let mut cookie = std::mem::ManuallyDrop::new(self); 974 | 975 | let cookie = Cookie { 976 | cookie: crate::ffi::Cookie::new(&mut cookie.cookie), 977 | marker: std::marker::PhantomData, 978 | }; 979 | Ok(cookie) 980 | } 981 | } 982 | } 983 | 984 | /// Loads the given compiled databases `buffers` for further queries 985 | /// 986 | /// Databases need to be compiled with a compatible `libmagic` version. 987 | /// 988 | /// This function can be used in environments where `libmagic` does 989 | /// not have direct access to the filesystem, but can access the magic 990 | /// database via shared memory or other IPC means. 991 | /// 992 | /// Calling `load_buffers()` or [`load()`](Cookie::load) replaces the previously loaded database/s. 993 | /// 994 | /// # Errors 995 | /// 996 | /// If there was an `libmagic` internal error, a [`cookie::LoadError`](LoadError) will be returned, 997 | /// which contains the cookie in its original state. 998 | /// 999 | /// # Panics 1000 | /// 1001 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 1002 | #[doc(alias = "magic_load_buffers")] 1003 | pub fn load_buffers(self, buffers: &[&[u8]]) -> Result, LoadError> { 1004 | match crate::ffi::load_buffers(&self.cookie, buffers) { 1005 | Err(err) => Err(LoadError { 1006 | function: "magic_load_buffers", 1007 | source: err, 1008 | cookie: self, 1009 | }), 1010 | Ok(_) => { 1011 | let mut cookie = std::mem::ManuallyDrop::new(self); 1012 | 1013 | let cookie = Cookie { 1014 | cookie: crate::ffi::Cookie::new(&mut cookie.cookie), 1015 | marker: std::marker::PhantomData, 1016 | }; 1017 | Ok(cookie) 1018 | } 1019 | } 1020 | } 1021 | 1022 | /// Sets the `flags` to use for this configuration 1023 | /// 1024 | /// Overwrites any previously set flags, e.g. those from [`load()`](Cookie::load). 1025 | /// 1026 | /// # Examples 1027 | /// ```rust 1028 | /// # fn main() -> Result<(), Box> { 1029 | /// // open a new cookie with initial default flags 1030 | /// let cookie = magic::Cookie::open(Default::default())?; 1031 | /// 1032 | /// // overwrite the initial flags 1033 | /// let flags = magic::cookie::Flags::COMPRESS | magic::cookie::Flags::DEVICES; 1034 | /// cookie.set_flags(flags)?; 1035 | /// # Ok(()) 1036 | /// # } 1037 | /// ``` 1038 | /// 1039 | /// # Errors 1040 | /// 1041 | /// If the given `flags` are unsupported on the current platform, an [`cookie::SetFlagsError`](SetFlagsError) will be returned. 1042 | #[doc(alias = "magic_setflags")] 1043 | pub fn set_flags(&self, flags: Flags) -> Result<(), SetFlagsError> { 1044 | let ret = crate::ffi::setflags(&self.cookie, flags.bits()); 1045 | match ret { 1046 | // according to `libmagic` man page this is the only flag that could be unsupported 1047 | Err(err) => Err(SetFlagsError { 1048 | flags: Flags::PRESERVE_ATIME, 1049 | source: err, 1050 | }), 1051 | Ok(_) => Ok(()), 1052 | } 1053 | } 1054 | 1055 | // TODO: check, compile, list and load mostly do the same, refactor! 1056 | 1057 | /// Compiles the given database files `filenames` for faster access 1058 | /// 1059 | /// The compiled files created are named from the `basename` of each file argument with ".mgc" appended to it. 1060 | /// 1061 | /// This is equivalent to the following `file` CLI command: 1062 | /// ```shell 1063 | /// $ file --compile --magic-file data/tests/db-images-png:data/tests/db-python 1064 | /// ``` 1065 | /// 1066 | /// # Errors 1067 | /// 1068 | /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned. 1069 | /// 1070 | /// # Panics 1071 | /// 1072 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 1073 | #[doc(alias = "magic_compile")] 1074 | #[doc(alias = "--compile")] 1075 | pub fn compile(&self, filenames: &DatabasePaths) -> Result<(), Error> { 1076 | match crate::ffi::compile(&self.cookie, filenames.filenames.as_deref()) { 1077 | Err(err) => Err(Error { 1078 | function: "magic_compile", 1079 | source: err, 1080 | }), 1081 | Ok(_) => Ok(()), 1082 | } 1083 | } 1084 | 1085 | /// Checks the validity of entries in the database files `filenames` 1086 | /// 1087 | /// # Errors 1088 | /// 1089 | /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned. 1090 | /// 1091 | /// # Panics 1092 | /// 1093 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 1094 | #[doc(alias = "magic_check")] 1095 | pub fn check(&self, filenames: &DatabasePaths) -> Result<(), Error> { 1096 | match crate::ffi::check(&self.cookie, filenames.filenames.as_deref()) { 1097 | Err(err) => Err(Error { 1098 | function: "magic_check", 1099 | source: err, 1100 | }), 1101 | Ok(_) => Ok(()), 1102 | } 1103 | } 1104 | 1105 | /// Dumps all magic entries in the given database files `filenames` in a human readable format 1106 | /// 1107 | /// This is equivalent to the following `file` CLI command: 1108 | /// ```shell 1109 | /// $ file --checking-printout --magic-file data/tests/db-images-png:data/tests/db-python 1110 | /// ``` 1111 | /// 1112 | /// # Errors 1113 | /// 1114 | /// If there was an `libmagic` internal error, a [`cookie::Error`](Error) will be returned. 1115 | /// 1116 | /// # Panics 1117 | /// 1118 | /// Panics if `libmagic` violates its API contract, e.g. by not setting the last error or returning undefined data. 1119 | #[doc(alias = "magic_list")] 1120 | #[doc(alias = "--checking-printout")] 1121 | pub fn list(&self, filenames: &DatabasePaths) -> Result<(), Error> { 1122 | match crate::ffi::list(&self.cookie, filenames.filenames.as_deref()) { 1123 | Err(err) => Err(Error { 1124 | function: "magic_list", 1125 | source: err, 1126 | }), 1127 | Ok(_) => Ok(()), 1128 | } 1129 | } 1130 | } 1131 | 1132 | /// Error within [`Cookie::open()`](Cookie::open) 1133 | /// 1134 | /// Note that a similar [`cookie::SetFlagsError`](SetFlagsError) can also occur 1135 | #[derive(thiserror::Error, Debug)] 1136 | #[error("could not open magic cookie: {}", 1137 | match .kind { 1138 | OpenErrorKind::UnsupportedFlags => format!("unsupported flags {}", .flags), 1139 | OpenErrorKind::Errno => "other error".to_string(), 1140 | } 1141 | )] 1142 | pub struct OpenError { 1143 | flags: Flags, 1144 | kind: OpenErrorKind, 1145 | //#[backtrace] 1146 | source: crate::ffi::OpenError, 1147 | } 1148 | 1149 | /// Kind of [`OpenError`] 1150 | #[derive(Debug)] 1151 | enum OpenErrorKind { 1152 | /// Unsupported flags given 1153 | UnsupportedFlags, 1154 | /// Other kind 1155 | Errno, 1156 | } 1157 | 1158 | /// Error within [`Cookie::set_flags()`](Cookie::set_flags) 1159 | /// 1160 | /// Note that a similar [`cookie::OpenError`](OpenError) can also occur 1161 | #[derive(thiserror::Error, Debug)] 1162 | #[error("could not set magic cookie flags {}", .flags)] 1163 | pub struct SetFlagsError { 1164 | flags: Flags, 1165 | //#[backtrace] 1166 | source: crate::ffi::SetFlagsError, 1167 | } 1168 | } // mod cookie 1169 | 1170 | pub use crate::cookie::Cookie; 1171 | 1172 | #[cfg(test)] 1173 | mod tests { 1174 | use super::cookie::Flags; 1175 | use super::Cookie; 1176 | use std::convert::TryInto; 1177 | 1178 | // Using relative paths to test files should be fine, since cargo doc 1179 | // https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script 1180 | // states that cwd == CARGO_MANIFEST_DIR 1181 | 1182 | #[test] 1183 | fn file() { 1184 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1185 | let databases = &["data/tests/db-images-png"].try_into().unwrap(); 1186 | let cookie = cookie.load(databases).unwrap(); 1187 | 1188 | let path = "data/tests/rust-logo-128x128-blk.png"; 1189 | 1190 | assert_eq!( 1191 | cookie.file(path).ok().unwrap(), 1192 | "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced" 1193 | ); 1194 | 1195 | cookie.set_flags(Flags::MIME_TYPE).unwrap(); 1196 | assert_eq!(cookie.file(path).ok().unwrap(), "image/png"); 1197 | 1198 | cookie 1199 | .set_flags(Flags::MIME_TYPE | Flags::MIME_ENCODING) 1200 | .unwrap(); 1201 | assert_eq!(cookie.file(path).ok().unwrap(), "image/png; charset=binary"); 1202 | } 1203 | 1204 | #[test] 1205 | fn buffer() { 1206 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1207 | let databases = &["data/tests/db-python"].try_into().unwrap(); 1208 | let cookie = cookie.load(databases).unwrap(); 1209 | 1210 | let s = b"#!/usr/bin/env python\nprint('Hello, world!')"; 1211 | assert_eq!( 1212 | cookie.buffer(s).ok().unwrap(), 1213 | "Python script, ASCII text executable" 1214 | ); 1215 | 1216 | cookie.set_flags(Flags::MIME_TYPE).unwrap(); 1217 | assert_eq!(cookie.buffer(s).ok().unwrap(), "text/x-python"); 1218 | } 1219 | 1220 | #[test] 1221 | fn file_error() { 1222 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1223 | let cookie = cookie.load(&Default::default()).unwrap(); 1224 | 1225 | let ret = cookie.file("non-existent_file.txt"); 1226 | assert!(ret.is_err()); 1227 | } 1228 | 1229 | #[test] 1230 | fn load_default() { 1231 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1232 | assert!(cookie.load(&Default::default()).is_ok()); 1233 | } 1234 | 1235 | #[test] 1236 | fn load_one() { 1237 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1238 | let databases = &["data/tests/db-images-png"].try_into().unwrap(); 1239 | assert!(cookie.load(databases).is_ok()); 1240 | } 1241 | 1242 | #[test] 1243 | fn load_multiple() { 1244 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1245 | let databases = &["data/tests/db-images-png", "data/tests/db-python"] 1246 | .try_into() 1247 | .unwrap(); 1248 | assert!(cookie.load(databases).is_ok()); 1249 | } 1250 | 1251 | // TODO: 1252 | //static_assertions::assert_impl_all!(Cookie: std::fmt::Debug); 1253 | 1254 | #[test] 1255 | fn load_buffers_file() { 1256 | let cookie = Cookie::open(Flags::ERROR).unwrap(); 1257 | // file --compile --magic-file data/tests/db-images-png 1258 | let magic_database = std::fs::read("data/tests/db-images-png-precompiled.mgc").unwrap(); 1259 | let buffers = vec![magic_database.as_slice()]; 1260 | let cookie = cookie.load_buffers(&buffers).unwrap(); 1261 | 1262 | let path = "data/tests/rust-logo-128x128-blk.png"; 1263 | assert_eq!( 1264 | cookie.file(path).ok().unwrap(), 1265 | "PNG image data, 128 x 128, 8-bit/color RGBA, non-interlaced" 1266 | ); 1267 | } 1268 | 1269 | #[test] 1270 | fn libmagic_version() { 1271 | let version = super::libmagic_version(); 1272 | 1273 | assert!(version > 500); 1274 | } 1275 | } 1276 | 1277 | #[cfg(doctest)] 1278 | #[doc=include_str!("../README-crate.md")] 1279 | mod readme {} 1280 | --------------------------------------------------------------------------------