diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 673a6939..5cb95c27 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,11 +4,13 @@ libmagic-rs is a **pure-Rust implementation of libmagic** for file type identification. The project follows a **parser-evaluator architecture** with strict memory safety guarantees and zero unsafe code. -### Development Stage: MVP Phase (v0.1) +### Development Stage: v0.5.0 (Active Development) -- ✅ **Core AST and parser components** are complete with 98 unit tests -- 🔄 **Currently implementing**: Complete magic file rule parsing (`src/parser/mod.rs`) -- 📋 **Next**: Rule evaluation engine (`src/evaluator/`) and output formatters (`src/output/`) +- ✅ **Parser**: Complete with AST structures, grammar parsing, type handling, and hierarchy support +- ✅ **Evaluator**: Fully implemented with offset resolution, type interpretation, operator application, and strength calculation +- ✅ **Output**: Text and JSON formatters with comprehensive metadata +- ✅ **CLI**: Full-featured `rmagic` binary with multiple file support, stdin, built-in rules, and custom magic files +- 🔄 **Currently implementing**: Enhanced type support (regex, search patterns) and indirect/relative offset evaluation ## Architecture Patterns @@ -22,10 +24,21 @@ Target File → Memory Mapper → File Buffer ### Module Structure (Follow This Pattern) -- **`src/parser/`**: `ast.rs` (complete), `grammar.rs` (nom parsers), `mod.rs` (rule integration) -- **`src/io/`**: Memory-mapped FileBuffer with comprehensive bounds checking (complete) -- **`src/evaluator/`**: Offset resolution, type interpretation, operators (planned) -- **`src/output/`**: Text and JSON formatters (planned) +- **`src/parser/`**: Complete parsing system + - `ast.rs`: AST node definitions (MagicRule, TypeKind, Operator, Value, OffsetSpec) + - `grammar/`: nom-based parser combinators for magic file syntax + - `types.rs`: Type keyword parsing and validation + - `hierarchy.rs`: Hierarchical rule structure handling + - `loader.rs`: Magic file loading and preprocessing + - `codegen.rs`: Serialization for build-time rule compilation +- **`src/evaluator/`**: Rule evaluation engine + - `engine/`: Core evaluation logic and rule matching + - `offset/`: Offset resolution (absolute, from-end; indirect/relative stubs) + - `operators/`: Operator application (equality, comparison, bitwise) + - `types/`: Type interpretation with endianness handling + - `strength.rs`: Confidence scoring and strength modifiers +- **`src/io/`**: Memory-mapped FileBuffer with SafeBufferAccess trait for bounds checking +- **`src/output/`**: Result formatting (text.rs, json.rs) with metadata support ## Critical Development Practices @@ -77,7 +90,7 @@ pub struct MagicRule { ```bash cargo check # Fast syntax/type checking (use frequently) -cargo test # Run 98 unit tests (currently all passing) +cargo test # Run 1,068+ unit tests (currently all passing) cargo nextest run # Faster test execution (preferred) cargo clippy -- -D warnings # Required - zero warnings policy cargo fmt # Code formatting @@ -89,6 +102,7 @@ cargo fmt # Code formatting - **Property testing**: Use `proptest` for fuzzing-style tests - **Error case testing**: Validate all `Result` error paths - **Serialization testing**: All AST types use serde, test round-trip +- **Table-driven tests**: Consolidate related test cases with descriptive failure messages ### Performance Focus @@ -140,15 +154,25 @@ pub enum LibmagicError { ## Magic File Compatibility -### Supported Syntax (Implement in parser/mod.rs) +### Supported Syntax (Currently Implemented in v0.5.0) -- **Offsets**: `0x10`, `(0x20.l+4)`, `&0x30` -- **Types**: `byte`, `short`, `long`, `string`, `regex` with endianness (`be`, `le`) -- **Operators**: `=`, `!=`, `>`, `<`, `&` (bitwise AND), `^` (XOR) +- **Offsets**: Absolute, from-end (indirect and relative are parsed but not yet evaluated) +- **Types**: `byte`, `short`, `long`, `quad`, `float`, `double`, `string`, `pstring` with endianness support; unsigned variants `ubyte`, `ushort`/`ubeshort`/`uleshort`, `ulong`/`ubelong`/`ulelong`, `uquad`/`ubequad`/`ulequad`; float/double endian variants `befloat`/`lefloat`, `bedouble`/`ledouble`; 32-bit date/timestamp types `date`/`ldate`/`bedate`/`beldate`/`ledate`/`leldate`; 64-bit date/timestamp types `qdate`/`qldate`/`beqdate`/`beqldate`/`leqdate`/`leqldate` +- **Operators**: `=` (equal), `!=` (not equal), `<` (less than), `>` (greater than), `<=` (less equal), `>=` (greater equal), `&` (bitwise AND with optional mask), `^` (bitwise XOR), `~` (bitwise NOT), `x` (any value) - **Nesting**: Hierarchical rules with proper indentation handling +- **String Matching**: Exact string matching with null-termination +- **Directives**: `!:strength` modifier (parsed and applied) + +### Planned Features (v1.0+) + +- Regex type: Pattern matching with binary-safe regex support +- Search type: Multi-pattern string searching +- Additional directives: `!:mime`, `!:ext`, `!:apple` ### Binary-Safe Regex +> **Note:** The regex type is planned for future releases and is not yet implemented (#39). + ```rust // Use regex crate with bytes feature for binary-safe matching use regex::bytes::Regex; @@ -160,15 +184,20 @@ use regex::bytes::Regex; ### Completed (Don't Reimplement) - ✅ **AST structures** (`src/parser/ast.rs`) - fully tested with serde -- ✅ **Parser components** (`src/parser/grammar.rs`) - numbers, offsets, operators, values +- ✅ **Parser components** (`src/parser/grammar/`) - complete magic file syntax parsing +- ✅ **Type system** (`src/parser/types.rs`) - byte, short, long, quad, float, double, string, pstring, date types - ✅ **File I/O** (`src/io/mod.rs`) - memory-mapped FileBuffer with bounds checking -- ✅ **CLI framework** (`src/main.rs`) - clap-based argument parsing +- ✅ **CLI framework** (`src/main.rs`) - clap-based argument parsing with JSON output +- ✅ **Evaluator engine** (`src/evaluator/`) - complete rule evaluation with strength calculation +- ✅ **Output formatters** (`src/output/`) - text and JSON formatters with metadata ### Active Development (Contribute Here) -- 🔄 **Rule parsing** (`src/parser/mod.rs`) - integrate components into complete rules -- 📋 **Evaluator engine** (`src/evaluator/mod.rs`) - offset resolution, type interpretation -- 📋 **Output formatters** (`src/output/mod.rs`) - text and JSON result formatting +- 🔄 **Indirect offsets** (`src/evaluator/offset/indirect.rs`) - stub exists, needs implementation (#37) +- 🔄 **Relative offsets** (`src/evaluator/offset/relative.rs`) - stub exists, needs implementation (#38) +- 📋 **Regex type** - planned for future release (#39) +- 📋 **Search type** - planned for future release (#39) +- ✅ **Pascal strings** - implemented (#43) ## Code Quality Enforcement @@ -206,4 +235,31 @@ pedantic = { level = "warn", priority = -1 } - **FileBuffer → Evaluator**: Safe buffer access through trait methods - **Results → Output**: Structured match results for formatters +## Common Tasks and Patterns + +### Adding New Type Support + +> **Note:** Currently implemented types are `Byte`, `Short`, `Long`, `Quad`, `Float`, `Double`, `String`, `PString`, and date/timestamp variants. Regex and other advanced types are planned for future releases. + +1. Extend `TypeKind` enum in `src/parser/ast.rs` +2. Add keyword parsing in `src/parser/types.rs` (`parse_type_keyword` and `type_keyword_to_kind`) +3. Add value/operator parsing in `src/parser/grammar/mod.rs` if needed +4. Implement reading logic in `src/evaluator/types/` submodules +5. Update `serialize_type_kind()` in `src/parser/codegen.rs` +6. Add tests for the new type +7. Update documentation + +### Adding New Operators + +> **Note:** Currently implemented operators are `Equal`, `NotEqual`, `LessThan`, `GreaterThan`, `LessEqual`, `GreaterEqual`, `BitwiseAnd` (with `BitwiseAndMask`), `BitwiseXor`, `BitwiseNot`, and `AnyValue`. + +1. Extend `Operator` enum in `src/parser/ast.rs` +2. Add parsing logic in `src/parser/grammar/mod.rs` +3. Implement operator logic in `src/evaluator/operators/` submodule +4. Update `serialize_operator()` in `src/parser/codegen.rs` +5. Update strength calculation match in `src/evaluator/strength.rs` +6. Update `arb_operator()` in `tests/property_tests.rs` +7. Add tests for the new operator +8. Update documentation + This guide ensures AI agents understand the project's strict safety requirements, current development focus, and established patterns for immediate productivity. diff --git a/AGENTS.md b/AGENTS.md index 83d75dc6..08a6d932 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -208,15 +208,14 @@ cargo test --doc # Test documentation examples ### Currently Implemented (v0.1.0) - **Offsets**: Absolute and from-end specifications (indirect and relative are parsed but not yet evaluated) -- **Types**: `byte`, `short`, `long`, `quad`, `float`, `double`, `string` with endianness support; unsigned variants `ubyte`, `ushort`/`ubeshort`/`uleshort`, `ulong`/`ubelong`/`ulelong`, `uquad`/`ubequad`/`ulequad`; float/double endian variants `befloat`/`lefloat`, `bedouble`/`ledouble`; 32-bit date/timestamp types `date`/`ldate`/`bedate`/`beldate`/`ledate`/`leldate`; 64-bit date/timestamp types `qdate`/`qldate`/`beqdate`/`beqldate`/`leqdate`/`leqldate`; date values formatted as `"Www Mmm DD HH:MM:SS YYYY"` matching GNU `file` output; types are signed by default (libmagic-compatible) +- **Types**: `byte`, `short`, `long`, `quad`, `float`, `double`, `string`, `pstring` with endianness support; unsigned variants `ubyte`, `ushort`/`ubeshort`/`uleshort`, `ulong`/`ubelong`/`ulelong`, `uquad`/`ubequad`/`ulequad`; float/double endian variants `befloat`/`lefloat`, `bedouble`/`ledouble`; 32-bit date/timestamp types `date`/`ldate`/`bedate`/`beldate`/`ledate`/`leldate`; 64-bit date/timestamp types `qdate`/`qldate`/`beqdate`/`beqldate`/`leqdate`/`leqldate`; `pstring` is a Pascal string (length-prefixed byte followed by string data); date values formatted as `"Www Mmm DD HH:MM:SS YYYY"` matching GNU `file` output; types are signed by default (libmagic-compatible) - **Operators**: `=` (equal), `!=` (not equal), `<` (less than), `>` (greater than), `<=` (less equal), `>=` (greater equal), `&` (bitwise AND with optional mask), `^` (bitwise XOR), `~` (bitwise NOT), `x` (any value) - **Nested Rules**: Hierarchical rule evaluation with proper indentation -- **String Matching**: Exact string matching with null-termination +- **String Matching**: Exact string matching with null-termination and Pascal string (length-prefixed) support ### Planned Features (v1.0+) - Regex type: Pattern matching with binary-safe regex support -- Additional types: pascal strings - Search type: Multi-pattern string searching ### Future Enhancement: Binary-Safe Regex Handling @@ -240,7 +239,8 @@ impl BinaryRegex for regex::bytes::Regex { - No regex/search pattern matching - 64-bit integer types: `quad`/`uquad`, `bequad`/`ubequad`, `lequad`/`ulequad` are implemented; `qquad` (128-bit) is not yet supported -- String evaluation reads until first NUL or end-of-buffer by default; `max_length: Some(_)` is supported internally but no dedicated fixed-length string parser syntax exists yet +- String evaluation reads until first NUL or end-of-buffer by default; `pstring` reads a length-prefixed Pascal string; `max_length: Some(_)` is supported internally but no dedicated fixed-length string parser syntax exists yet +- `pstring` only supports the default 1-byte length prefix (`/B`); multi-byte length prefix variants (`pstring/H` for 2-byte, `pstring/L` for 4-byte) are not yet implemented ### Operators @@ -321,7 +321,7 @@ sample.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV) ### Adding New Type Support -> **Note:** Currently implemented types are `Byte`, `Short`, `Long`, `Quad`, and `String`. Regex and other advanced types are planned for future releases. +> **Note:** Currently implemented types are `Byte`, `Short`, `Long`, `Quad`, `Float`, `Double`, `Date`, `QDate`, `String`, and `PString`. Regex and search types are planned for future releases. 1. Extend `TypeKind` enum in `src/parser/ast.rs` 2. Add keyword parsing in `src/parser/types.rs` (`parse_type_keyword` and `type_keyword_to_kind`) diff --git a/docs/MAGIC_FORMAT.md b/docs/MAGIC_FORMAT.md index f7beddac..ef2e2eb1 100644 --- a/docs/MAGIC_FORMAT.md +++ b/docs/MAGIC_FORMAT.md @@ -170,7 +170,7 @@ Examples: 8 uquad >0x8000000000000000 (unsigned 64-bit check) ``` -### String Type +### String Types Match literal string data: @@ -186,6 +186,16 @@ String escape sequences: - `\t` - tab - `\\` - backslash +**Pascal String (pstring)** + +Length-prefixed string type where the first byte contains the string length (0-255), followed by that many bytes of string data. Unlike C strings, Pascal strings are not null-terminated. + +```text +0 pstring =JPEG JPEG image (Pascal string) +``` + +The length byte value determines how many bytes to read for the string data. If `max_length` is specified in the magic file (not shown in the basic syntax), it caps the length byte value to prevent reading excessive data. + ### String Flags | Flag | Description | @@ -525,7 +535,7 @@ Consider: - Relative offsets - Indirect offsets (basic) - Byte, short, long, quad types (8-bit, 16-bit, 32-bit, 64-bit integers) -- String type +- String types (`string`, `pstring`) - Date and timestamp types (32-bit and 64-bit Unix timestamps) - Comparison operators (`=`, `!`, `<`, `>`, `<=`, `>=`) - Bitwise AND operator @@ -542,6 +552,7 @@ Consider: ### Recently Added +- **Pascal string type**: `pstring` for length-prefixed strings - **Date/timestamp types**: `date` (32-bit) and `qdate` (64-bit) Unix timestamp types - **Comparison operators**: Full support for `<`, `>`, `<=`, `>=` operators - **Strength modifiers**: The `!:strength` directive for adjusting rule priority diff --git a/docs/src/api-reference.md b/docs/src/api-reference.md index 3b8b2ded..f3c2e950 100644 --- a/docs/src/api-reference.md +++ b/docs/src/api-reference.md @@ -229,6 +229,7 @@ use libmagic_rs::TypeKind; | `Float { endian }` | 32-bit IEEE 754 floating-point (added in v0.5.0) | | `Double { endian }` | 64-bit IEEE 754 double-precision floating-point (added in v0.5.0) | | `String { max_length }` | String data (discriminant changed from 4 to 6 in v0.5.0) | +| `PString { max_length }` | Pascal string - length-prefixed byte followed by string data (returns `Value::String`) | ### Operator diff --git a/docs/src/architecture.md b/docs/src/architecture.md index be4dc94a..6cb388fb 100644 --- a/docs/src/architecture.md +++ b/docs/src/architecture.md @@ -95,6 +95,7 @@ pub enum TypeKind { Long { endian: Endianness, signed: bool }, Quad { endian: Endianness, signed: bool }, String { max_length: Option }, + PString { max_length: Option }, // Pascal string (length-prefixed) } pub enum Operator { diff --git a/docs/src/ast-structures.md b/docs/src/ast-structures.md index 1e465c9b..b126e56d 100644 --- a/docs/src/ast-structures.md +++ b/docs/src/ast-structures.md @@ -184,6 +184,9 @@ pub enum TypeKind { /// String data String { max_length: Option }, + + /// Pascal string (length-prefixed) + PString { max_length: Option }, } ``` @@ -226,6 +229,39 @@ let string_type = TypeKind::String { }; ``` +### PString (Pascal String) + +Pascal-style length-prefixed strings where the first byte contains the string length. + +**Structure:** +- Length byte: 1 byte indicating string length (0-255) +- String data: The number of bytes specified by the length byte + +**Example:** +``` +0 pstring JPEG +``` +Reads one byte as length, then reads that many bytes as a string. + +**Behavior:** +- Returns `Value::String` containing the string data (without the length prefix) +- Performs bounds checking on both the length byte and the string data +- Supports all string comparison operators + +**Usage:** + +```rust +// Pascal string with no length limit +let pstring_type = TypeKind::PString { + max_length: None +}; + +// Pascal string with maximum 64-byte limit +let limited_pstring = TypeKind::PString { + max_length: Some(64) +}; +``` + ### Endianness Options ```rust @@ -419,7 +455,8 @@ let script_rule = MagicRule { 1. **Use `Byte { signed }`** for single-byte values and flags, specifying signedness 2. **Use `Short/Long/Quad`** with explicit endianness and signedness for multi-byte integers 3. **Use `String`** with length limits for text patterns -4. **Use `Bytes`** for exact binary sequences +4. **Use `PString`** for Pascal-style length-prefixed strings +5. **Use `Bytes`** for exact binary sequences ### Performance Considerations diff --git a/docs/src/magic-format.md b/docs/src/magic-format.md index 41e1f782..354dac18 100644 --- a/docs/src/magic-format.md +++ b/docs/src/magic-format.md @@ -209,7 +209,7 @@ Examples: 16 leqldate x \b, timestamp %s ``` -### String Type +### String Types Match literal string data: @@ -225,6 +225,20 @@ String escape sequences: - `\t` - tab - `\\` - backslash +### Pascal String Type + +Pascal string (pstring) is a length-prefixed string type. The first byte contains the string length (0-255), followed by that many bytes of string data. Unlike C strings, Pascal strings are not null-terminated. + +```text +0 pstring =JPEG JPEG image (Pascal string) +``` + +The evaluator reads the length byte, then reads that many bytes as string data. The optional max_length parameter caps the length byte value: + +```text +0 pstring x \b, name: %s +``` + ### String Flags (Not Yet Implemented) > **Note:** String flags are documented for libmagic compatibility reference but are not yet implemented in libmagic-rs. @@ -535,7 +549,7 @@ Consider: - Byte, short, long, quad types (8-bit, 16-bit, 32-bit, 64-bit integers) - Float and double types (32-bit and 64-bit IEEE 754 floating-point) - Date and qdate types (32-bit and 64-bit Unix timestamps) -- String type +- String and pstring types (null-terminated and length-prefixed strings) - Comparison operators (equal, not-equal, less-than, greater-than, less-equal, greater-equal) - Bitwise AND operator - Nested rules diff --git a/docs/src/parser.md b/docs/src/parser.md index 92a8906f..a2e69b02 100644 --- a/docs/src/parser.md +++ b/docs/src/parser.md @@ -184,6 +184,46 @@ Parsed literals are stored as `Value::Float(f64)` in the AST, regardless of whet **Note:** Float and double types do **not** have signed/unsigned variants. IEEE 754 handles sign internally via the sign bit, so all float types use a single `TypeKind` variant with only an `endian` field (no `signed: bool` field). +### Pascal String (pstring) Type + +The parser supports Pascal-style length-prefixed strings through the `pstring` keyword: + +**Type Keyword:** + +- `pstring` - Length-prefixed string (1-byte length + string data) → `TypeKind::PString { max_length: None }` + +**Format:** + +Pascal strings store the length as the first byte (0-255), followed by that many bytes of string data. Unlike C strings, they are not null-terminated. + +**Parser Implementation:** + +- Recognized by `parse_type_keyword()` in `src/parser/types.rs` +- Maps to `TypeKind::PString` in the AST +- Evaluator reads length prefix byte then that many bytes as string data +- Stored as `Value::String` for comparison with string operators +- Supports optional `max_length` field to cap the length byte value + +**Usage in Magic Rules:** + +```rust +// Basic pstring matching +0 pstring =Hello // Match if pstring equals "Hello" +0 pstring x // Match any pstring value + +// With max_length constraint (parsed separately) +0 pstring/64 x // Limit string read to 64 bytes +``` + +**Features:** + +- ✅ Single type keyword `pstring` +- ✅ Length-prefixed format (1 byte length, 0-255 bytes data) +- ✅ Bounds checking for both length byte and string data +- ✅ UTF-8 validation with replacement character for invalid sequences +- ✅ Optional `max_length` parameter to limit string reads +- ✅ String comparison operators work with pstring values + ### Date and Timestamp Types The parser supports date and timestamp types for parsing Unix timestamps (signed seconds since epoch). There are 12 type keywords: diff --git a/mise.lock b/mise.lock index 999b3dd1..fceea7b0 100644 --- a/mise.lock +++ b/mise.lock @@ -1,60 +1,208 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + [[tools.actionlint]] version = "1.7.11" backend = "aqua:rhysd/actionlint" -"platforms.linux-arm64" = { checksum = "sha256:21bc0dfb57a913fe175298c2a9e906ee630f747cb66d0a934d0d4b69f4ee1235", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_arm64.tar.gz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:21bc0dfb57a913fe175298c2a9e906ee630f747cb66d0a934d0d4b69f4ee1235", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_arm64.tar.gz"} -"platforms.linux-x64" = { checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz"} -"platforms.linux-x64-musl" = { checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:a21ba7366d8329e7223faee0ed69eb13da27fe8acabb356bb7eb0b7f1e1cb6d8", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_darwin_arm64.tar.gz"} -"platforms.macos-x64" = { checksum = "sha256:17ffc17fed8f0258ef6ad4aed932d3272464c7ef7d64e1cb0d65aa97c9752107", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_darwin_amd64.tar.gz"} -"platforms.macos-x64-baseline" = { checksum = "sha256:17ffc17fed8f0258ef6ad4aed932d3272464c7ef7d64e1cb0d65aa97c9752107", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_darwin_amd64.tar.gz"} -"platforms.windows-x64" = { checksum = "sha256:5414b7124a91f4b5abee62e5c9d84802237734f8d15b9b7032732a32c3ebffa3", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_windows_amd64.zip"} -"platforms.windows-x64-baseline" = { checksum = "sha256:5414b7124a91f4b5abee62e5c9d84802237734f8d15b9b7032732a32c3ebffa3", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_windows_amd64.zip"} + +[tools.actionlint."platforms.linux-arm64"] +checksum = "sha256:21bc0dfb57a913fe175298c2a9e906ee630f747cb66d0a934d0d4b69f4ee1235" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_arm64.tar.gz" + +[tools.actionlint."platforms.linux-arm64-musl"] +checksum = "sha256:21bc0dfb57a913fe175298c2a9e906ee630f747cb66d0a934d0d4b69f4ee1235" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_arm64.tar.gz" + +[tools.actionlint."platforms.linux-x64"] +checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz" + +[tools.actionlint."platforms.linux-x64-baseline"] +checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz" + +[tools.actionlint."platforms.linux-x64-musl"] +checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz" + +[tools.actionlint."platforms.linux-x64-musl-baseline"] +checksum = "sha256:900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_linux_amd64.tar.gz" + +[tools.actionlint."platforms.macos-arm64"] +checksum = "sha256:a21ba7366d8329e7223faee0ed69eb13da27fe8acabb356bb7eb0b7f1e1cb6d8" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_darwin_arm64.tar.gz" + +[tools.actionlint."platforms.macos-x64"] +checksum = "sha256:17ffc17fed8f0258ef6ad4aed932d3272464c7ef7d64e1cb0d65aa97c9752107" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_darwin_amd64.tar.gz" + +[tools.actionlint."platforms.macos-x64-baseline"] +checksum = "sha256:17ffc17fed8f0258ef6ad4aed932d3272464c7ef7d64e1cb0d65aa97c9752107" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_darwin_amd64.tar.gz" + +[tools.actionlint."platforms.windows-x64"] +checksum = "sha256:5414b7124a91f4b5abee62e5c9d84802237734f8d15b9b7032732a32c3ebffa3" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_windows_amd64.zip" + +[tools.actionlint."platforms.windows-x64-baseline"] +checksum = "sha256:5414b7124a91f4b5abee62e5c9d84802237734f8d15b9b7032732a32c3ebffa3" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.11/actionlint_1.7.11_windows_amd64.zip" [[tools.bun]] version = "1.3.10" backend = "core:bun" -"platforms.linux-arm64" = { checksum = "sha256:fa5ecb25cafa8e8f5c87a0f833719d46dd0af0a86c7837d806531212d55636d3", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-aarch64.zip"} -"platforms.linux-arm64-musl" = { checksum = "sha256:d2c81365a2e529b78a42330d3a0056e8dbd7896b4a6782c8e392b6532141e34d", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-aarch64-musl.zip"} -"platforms.linux-x64" = { checksum = "sha256:f57bc0187e39623de716ba3a389fda5486b2d7be7131a980ba54dc7b733d2e08", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64.zip"} -"platforms.linux-x64-baseline" = { checksum = "sha256:41201a8c5ee74a9dcbb1ce25a1104f1f929838b57a845aa78d98379b0ce7cde2", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-baseline.zip"} -"platforms.linux-x64-musl" = { checksum = "sha256:48a6c32277d343db0148ce066336472ffd380358a4d26bb1329714742492d824", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-musl.zip"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:a7bc4cdea1ef255a83adbf39c7aafcd30e09f2b8f74deec4b10ee318bc024d1f", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-musl-baseline.zip"} -"platforms.macos-arm64" = { checksum = "sha256:82034e87c9d9b4398ea619aee2eed5d2a68c8157e9a6ae2d1052d84d533ccd8d", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-darwin-aarch64.zip"} -"platforms.macos-x64" = { checksum = "sha256:c1d90bf6140f20e572c473065dc6b37a4b036349b5e9e4133779cc642ad94323", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-darwin-x64.zip"} -"platforms.macos-x64-baseline" = { checksum = "sha256:f9686c4e4e760db4cde77a0f1fad05e552648b9c9cbfa4f7fc9a7ec26b9f3267", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-darwin-x64-baseline.zip"} -"platforms.windows-x64" = { checksum = "sha256:7a77b3e245e2e26965c93089a4a1332e8a326d3364c89fae1d1fd99cdd3cd73d", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-windows-x64.zip"} -"platforms.windows-x64-baseline" = { checksum = "sha256:715709c69b176e20994533d3292bd0b7c32de9c0c5575b916746ec6b2aa38346", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-windows-x64-baseline.zip"} + +[tools.bun."platforms.linux-arm64"] +checksum = "sha256:fa5ecb25cafa8e8f5c87a0f833719d46dd0af0a86c7837d806531212d55636d3" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-aarch64.zip" + +[tools.bun."platforms.linux-arm64-musl"] +checksum = "sha256:d2c81365a2e529b78a42330d3a0056e8dbd7896b4a6782c8e392b6532141e34d" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-aarch64-musl.zip" + +[tools.bun."platforms.linux-x64"] +checksum = "sha256:f57bc0187e39623de716ba3a389fda5486b2d7be7131a980ba54dc7b733d2e08" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64.zip" + +[tools.bun."platforms.linux-x64-baseline"] +checksum = "sha256:41201a8c5ee74a9dcbb1ce25a1104f1f929838b57a845aa78d98379b0ce7cde2" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-baseline.zip" + +[tools.bun."platforms.linux-x64-musl"] +checksum = "sha256:48a6c32277d343db0148ce066336472ffd380358a4d26bb1329714742492d824" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-musl.zip" + +[tools.bun."platforms.linux-x64-musl-baseline"] +checksum = "sha256:a7bc4cdea1ef255a83adbf39c7aafcd30e09f2b8f74deec4b10ee318bc024d1f" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-musl-baseline.zip" + +[tools.bun."platforms.macos-arm64"] +checksum = "sha256:82034e87c9d9b4398ea619aee2eed5d2a68c8157e9a6ae2d1052d84d533ccd8d" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-darwin-aarch64.zip" + +[tools.bun."platforms.macos-x64"] +checksum = "sha256:c1d90bf6140f20e572c473065dc6b37a4b036349b5e9e4133779cc642ad94323" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-darwin-x64.zip" + +[tools.bun."platforms.macos-x64-baseline"] +checksum = "sha256:f9686c4e4e760db4cde77a0f1fad05e552648b9c9cbfa4f7fc9a7ec26b9f3267" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-darwin-x64-baseline.zip" + +[tools.bun."platforms.windows-x64"] +checksum = "sha256:7a77b3e245e2e26965c93089a4a1332e8a326d3364c89fae1d1fd99cdd3cd73d" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-windows-x64.zip" + +[tools.bun."platforms.windows-x64-baseline"] +checksum = "sha256:715709c69b176e20994533d3292bd0b7c32de9c0c5575b916746ec6b2aa38346" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-windows-x64-baseline.zip" [[tools.cargo-binstall]] version = "1.17.6" backend = "aqua:cargo-bins/cargo-binstall" -"platforms.linux-arm64" = { checksum = "sha256:e5f2c4b79b10370dff707b86a14e7a0ad399c5dc5853824e933432910741992c", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-aarch64-unknown-linux-musl.tgz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:e5f2c4b79b10370dff707b86a14e7a0ad399c5dc5853824e933432910741992c", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-aarch64-unknown-linux-musl.tgz"} -"platforms.linux-x64" = { checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz"} -"platforms.linux-x64-musl" = { checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz"} -"platforms.macos-arm64" = { checksum = "sha256:101447fa30a723ca8e1a13cec11bb1350b7179331b2aa7054d27bef7a3e19021", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-aarch64-apple-darwin.zip"} -"platforms.macos-x64" = { checksum = "sha256:cd07fd79e2848b13b994e3f83fa5377b631625b847f0734219f2706feb518258", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-apple-darwin.zip"} -"platforms.macos-x64-baseline" = { checksum = "sha256:cd07fd79e2848b13b994e3f83fa5377b631625b847f0734219f2706feb518258", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-apple-darwin.zip"} -"platforms.windows-x64" = { checksum = "sha256:5fcbddde2d415704d2432bbe606a5767ddaf1ef4ee2c16b7828f8be2ed1e5a5c", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-pc-windows-msvc.zip"} -"platforms.windows-x64-baseline" = { checksum = "sha256:5fcbddde2d415704d2432bbe606a5767ddaf1ef4ee2c16b7828f8be2ed1e5a5c", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-pc-windows-msvc.zip"} + +[tools.cargo-binstall."platforms.linux-arm64"] +checksum = "sha256:e5f2c4b79b10370dff707b86a14e7a0ad399c5dc5853824e933432910741992c" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-aarch64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.linux-arm64-musl"] +checksum = "sha256:e5f2c4b79b10370dff707b86a14e7a0ad399c5dc5853824e933432910741992c" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-aarch64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.linux-x64"] +checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.linux-x64-baseline"] +checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.linux-x64-musl"] +checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.linux-x64-musl-baseline"] +checksum = "sha256:f926d96e9f0822ded35c4ac2071ce190bd1311565695c49c45e295de0d685aaa" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.macos-arm64"] +checksum = "sha256:101447fa30a723ca8e1a13cec11bb1350b7179331b2aa7054d27bef7a3e19021" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-aarch64-apple-darwin.zip" + +[tools.cargo-binstall."platforms.macos-x64"] +checksum = "sha256:cd07fd79e2848b13b994e3f83fa5377b631625b847f0734219f2706feb518258" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-apple-darwin.zip" + +[tools.cargo-binstall."platforms.macos-x64-baseline"] +checksum = "sha256:cd07fd79e2848b13b994e3f83fa5377b631625b847f0734219f2706feb518258" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-apple-darwin.zip" + +[tools.cargo-binstall."platforms.windows-x64"] +checksum = "sha256:5fcbddde2d415704d2432bbe606a5767ddaf1ef4ee2c16b7828f8be2ed1e5a5c" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-pc-windows-msvc.zip" + +[tools.cargo-binstall."platforms.windows-x64-baseline"] +checksum = "sha256:5fcbddde2d415704d2432bbe606a5767ddaf1ef4ee2c16b7828f8be2ed1e5a5c" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.6/cargo-binstall-x86_64-pc-windows-msvc.zip" + +[[tools.cargo-binstall]] +version = "1.17.7" +backend = "aqua:cargo-bins/cargo-binstall" + +[tools.cargo-binstall."platforms.linux-x64"] +checksum = "sha256:29b5ecfb6e03c2511a617c77d312b06df0c54717644fbfda3d465ec8240532f0" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.7/cargo-binstall-x86_64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.macos-arm64"] +checksum = "sha256:1ad3c0c56fa3970634cce5009ed0ce61b943515f9115f8e480fd0e41d8d89085" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.7/cargo-binstall-aarch64-apple-darwin.zip" + +[tools.cargo-binstall."platforms.macos-x64"] +checksum = "sha256:aa7174fb938e668dea4b4c3d22fe6cefed97642cc3a7a419ba96d63d63fd729b" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.7/cargo-binstall-x86_64-apple-darwin.zip" + +[tools.cargo-binstall."platforms.windows-x64"] +checksum = "sha256:c5cb2444ee04480502a8ac73d96abd9f97af8300ec04ea1c1f2a9e959c02e4d6" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.7/cargo-binstall-x86_64-pc-windows-msvc.zip" [[tools.cargo-insta]] version = "1.46.3" backend = "aqua:mitsuhiko/insta" -"platforms.linux-x64" = { checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz"} -"platforms.linux-x64-musl" = { checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz"} -"platforms.macos-arm64" = { checksum = "sha256:1e620252db7964d876da6b4956872ad84d099ee281753ac7c850ae24413947df", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-aarch64-apple-darwin.tar.xz"} -"platforms.macos-x64" = { checksum = "sha256:d55ff42a08ad0fc6deed64bb9ab700c069da9c6da40947d9b658cc33fda3dcda", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-apple-darwin.tar.xz"} -"platforms.macos-x64-baseline" = { checksum = "sha256:d55ff42a08ad0fc6deed64bb9ab700c069da9c6da40947d9b658cc33fda3dcda", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-apple-darwin.tar.xz"} -"platforms.windows-x64" = { checksum = "sha256:fa0cd6810e393392cf347decacd8a710de9ac95b6747a753f037c46b649209aa", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-pc-windows-msvc.zip"} -"platforms.windows-x64-baseline" = { checksum = "sha256:fa0cd6810e393392cf347decacd8a710de9ac95b6747a753f037c46b649209aa", url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-pc-windows-msvc.zip"} + +[tools.cargo-insta."platforms.linux-x64"] +checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz" + +[tools.cargo-insta."platforms.linux-x64-baseline"] +checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz" + +[tools.cargo-insta."platforms.linux-x64-musl"] +checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz" + +[tools.cargo-insta."platforms.linux-x64-musl-baseline"] +checksum = "sha256:c738c47f8d7e834a0277dddb9410a1f7369d37818738fc6a380f22904a83f6e4" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz" + +[tools.cargo-insta."platforms.macos-arm64"] +checksum = "sha256:1e620252db7964d876da6b4956872ad84d099ee281753ac7c850ae24413947df" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-aarch64-apple-darwin.tar.xz" + +[tools.cargo-insta."platforms.macos-x64"] +checksum = "sha256:d55ff42a08ad0fc6deed64bb9ab700c069da9c6da40947d9b658cc33fda3dcda" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-apple-darwin.tar.xz" + +[tools.cargo-insta."platforms.macos-x64-baseline"] +checksum = "sha256:d55ff42a08ad0fc6deed64bb9ab700c069da9c6da40947d9b658cc33fda3dcda" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-apple-darwin.tar.xz" + +[tools.cargo-insta."platforms.windows-x64"] +checksum = "sha256:fa0cd6810e393392cf347decacd8a710de9ac95b6747a753f037c46b649209aa" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-pc-windows-msvc.zip" + +[tools.cargo-insta."platforms.windows-x64-baseline"] +checksum = "sha256:fa0cd6810e393392cf347decacd8a710de9ac95b6747a753f037c46b649209aa" +url = "https://github.com/mitsuhiko/insta/releases/download/1.46.3/cargo-insta-x86_64-pc-windows-msvc.zip" [[tools."cargo:cargo-audit"]] version = "0.22.1" @@ -139,30 +287,90 @@ backend = "cargo:release-plz" [[tools.just]] version = "1.46.0" backend = "aqua:casey/just" -"platforms.linux-arm64" = { checksum = "sha256:b81970c8247fa64cfb30d2a3da0e487e4253f9f2d01865ed5e7d53cdc7b02188", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-aarch64-unknown-linux-musl.tar.gz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:b81970c8247fa64cfb30d2a3da0e487e4253f9f2d01865ed5e7d53cdc7b02188", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-aarch64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64" = { checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64-musl" = { checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:438eaf6468a115aa7db93e501cc7e3272f453f6b7083be3863adfab546b23358", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-aarch64-apple-darwin.tar.gz"} -"platforms.macos-x64" = { checksum = "sha256:ec54dd60ac876261b7318f1852ef9c0319fede1e5a73c14f56d908a8edf595b8", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-apple-darwin.tar.gz"} -"platforms.macos-x64-baseline" = { checksum = "sha256:ec54dd60ac876261b7318f1852ef9c0319fede1e5a73c14f56d908a8edf595b8", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-apple-darwin.tar.gz"} -"platforms.windows-x64" = { checksum = "sha256:f0acf3f8ccbcf360b481baae9cae4c921774c89d5d932012481d3e0bda78ab39", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-pc-windows-msvc.zip"} -"platforms.windows-x64-baseline" = { checksum = "sha256:f0acf3f8ccbcf360b481baae9cae4c921774c89d5d932012481d3e0bda78ab39", url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-pc-windows-msvc.zip"} + +[tools.just."platforms.linux-arm64"] +checksum = "sha256:b81970c8247fa64cfb30d2a3da0e487e4253f9f2d01865ed5e7d53cdc7b02188" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-aarch64-unknown-linux-musl.tar.gz" + +[tools.just."platforms.linux-arm64-musl"] +checksum = "sha256:b81970c8247fa64cfb30d2a3da0e487e4253f9f2d01865ed5e7d53cdc7b02188" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-aarch64-unknown-linux-musl.tar.gz" + +[tools.just."platforms.linux-x64"] +checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz" + +[tools.just."platforms.linux-x64-baseline"] +checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz" + +[tools.just."platforms.linux-x64-musl"] +checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz" + +[tools.just."platforms.linux-x64-musl-baseline"] +checksum = "sha256:79966e6e353f535ee7d1c6221641bcc8e3381c55b0d0a6dc6e54b34f9db36eaa" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-unknown-linux-musl.tar.gz" + +[tools.just."platforms.macos-arm64"] +checksum = "sha256:438eaf6468a115aa7db93e501cc7e3272f453f6b7083be3863adfab546b23358" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-aarch64-apple-darwin.tar.gz" + +[tools.just."platforms.macos-x64"] +checksum = "sha256:ec54dd60ac876261b7318f1852ef9c0319fede1e5a73c14f56d908a8edf595b8" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-apple-darwin.tar.gz" + +[tools.just."platforms.macos-x64-baseline"] +checksum = "sha256:ec54dd60ac876261b7318f1852ef9c0319fede1e5a73c14f56d908a8edf595b8" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-apple-darwin.tar.gz" + +[tools.just."platforms.windows-x64"] +checksum = "sha256:f0acf3f8ccbcf360b481baae9cae4c921774c89d5d932012481d3e0bda78ab39" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-pc-windows-msvc.zip" + +[tools.just."platforms.windows-x64-baseline"] +checksum = "sha256:f0acf3f8ccbcf360b481baae9cae4c921774c89d5d932012481d3e0bda78ab39" +url = "https://github.com/casey/just/releases/download/1.46.0/just-1.46.0-x86_64-pc-windows-msvc.zip" [[tools.lychee]] version = "0.23.0" backend = "aqua:lycheeverse/lychee" -"platforms.linux-arm64" = { checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz"} -"platforms.linux-x64" = { checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64-musl" = { checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:4c8034900e11083b68ac6f6582c377ff1f704e268991999e09d717973e493e7f", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-arm64-macos.dmg"} -"platforms.windows-x64" = { checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe"} -"platforms.windows-x64-baseline" = { checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824", url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe"} + +[tools.lychee."platforms.linux-arm64"] +checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz" + +[tools.lychee."platforms.linux-arm64-musl"] +checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz" + +[tools.lychee."platforms.linux-x64"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.linux-x64-baseline"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.linux-x64-musl"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.linux-x64-musl-baseline"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.macos-arm64"] +checksum = "sha256:4c8034900e11083b68ac6f6582c377ff1f704e268991999e09d717973e493e7f" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-arm64-macos.dmg" + +[tools.lychee."platforms.windows-x64"] +checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe" + +[tools.lychee."platforms.windows-x64-baseline"] +checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe" [[tools.markdownlint-cli2]] version = "0.21.0" @@ -186,17 +394,50 @@ backend = "npm:prettier" [[tools.python]] version = "3.14.3" backend = "core:python" -"platforms.linux-arm64" = { checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.linux-x64" = { checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.linux-x64-musl" = { checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz"} -"platforms.macos-x64" = { checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz"} -"platforms.macos-x64-baseline" = { checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz"} -"platforms.windows-x64" = { checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"} -"platforms.windows-x64-baseline" = { checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"} + +[tools.python."platforms.linux-arm64"] +checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-arm64-musl"] +checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-x64"] +checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-x64-baseline"] +checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-x64-musl"] +checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-x64-musl-baseline"] +checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.macos-arm64"] +checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz" + +[tools.python."platforms.macos-x64"] +checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz" + +[tools.python."platforms.macos-x64-baseline"] +checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz" + +[tools.python."platforms.windows-x64"] +checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" + +[tools.python."platforms.windows-x64-baseline"] +checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" [[tools.rust]] version = "1.94.0" @@ -205,29 +446,99 @@ backend = "core:rust" [[tools.scorecard]] version = "5.4.0" backend = "aqua:ossf/scorecard" -"platforms.linux-arm64" = { checksum = "sha256:3f8b6354c62ec0287a8e9694481d834e16bff8451cf5b5dca435e8400ce5adaf", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_arm64.tar.gz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:3f8b6354c62ec0287a8e9694481d834e16bff8451cf5b5dca435e8400ce5adaf", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_arm64.tar.gz"} -"platforms.linux-x64" = { checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz"} -"platforms.linux-x64-musl" = { checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:2c672695a27d35537dd4054f690f31fa1d6a72b0957598f45181296487f537f4", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_darwin_arm64.tar.gz"} -"platforms.macos-x64" = { checksum = "sha256:2abfec13b8eecc9b730e3782c9b3a9544d31ae861ce21ea7fe6a369d887d7c89", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_darwin_amd64.tar.gz"} -"platforms.macos-x64-baseline" = { checksum = "sha256:2abfec13b8eecc9b730e3782c9b3a9544d31ae861ce21ea7fe6a369d887d7c89", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_darwin_amd64.tar.gz"} -"platforms.windows-x64" = { checksum = "sha256:f7d0ece0dde703e4baa5f96e9b6ed33e6e786138c90db8de2c4943f24015b9ff", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_windows_amd64.tar.gz"} -"platforms.windows-x64-baseline" = { checksum = "sha256:f7d0ece0dde703e4baa5f96e9b6ed33e6e786138c90db8de2c4943f24015b9ff", url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_windows_amd64.tar.gz"} + +[tools.scorecard."platforms.linux-arm64"] +checksum = "sha256:3f8b6354c62ec0287a8e9694481d834e16bff8451cf5b5dca435e8400ce5adaf" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_arm64.tar.gz" + +[tools.scorecard."platforms.linux-arm64-musl"] +checksum = "sha256:3f8b6354c62ec0287a8e9694481d834e16bff8451cf5b5dca435e8400ce5adaf" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_arm64.tar.gz" + +[tools.scorecard."platforms.linux-x64"] +checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz" +provenance = "slsa" + +[tools.scorecard."platforms.linux-x64-baseline"] +checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz" + +[tools.scorecard."platforms.linux-x64-musl"] +checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz" + +[tools.scorecard."platforms.linux-x64-musl-baseline"] +checksum = "sha256:e5183aeaa5aa548fbb7318a6deb3e1038be0ef9aca24e655422ae88dfbe67502" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_linux_amd64.tar.gz" + +[tools.scorecard."platforms.macos-arm64"] +checksum = "sha256:2c672695a27d35537dd4054f690f31fa1d6a72b0957598f45181296487f537f4" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_darwin_arm64.tar.gz" +provenance = "slsa" + +[tools.scorecard."platforms.macos-x64"] +checksum = "sha256:2abfec13b8eecc9b730e3782c9b3a9544d31ae861ce21ea7fe6a369d887d7c89" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_darwin_amd64.tar.gz" +provenance = "slsa" + +[tools.scorecard."platforms.macos-x64-baseline"] +checksum = "sha256:2abfec13b8eecc9b730e3782c9b3a9544d31ae861ce21ea7fe6a369d887d7c89" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_darwin_amd64.tar.gz" + +[tools.scorecard."platforms.windows-x64"] +checksum = "sha256:f7d0ece0dde703e4baa5f96e9b6ed33e6e786138c90db8de2c4943f24015b9ff" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_windows_amd64.tar.gz" +provenance = "slsa" + +[tools.scorecard."platforms.windows-x64-baseline"] +checksum = "sha256:f7d0ece0dde703e4baa5f96e9b6ed33e6e786138c90db8de2c4943f24015b9ff" +url = "https://github.com/ossf/scorecard/releases/download/v5.4.0/scorecard_5.4.0_windows_amd64.tar.gz" [[tools.shellcheck]] version = "0.11.0" backend = "aqua:koalaman/shellcheck" -"platforms.linux-arm64" = { checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz"} -"platforms.linux-arm64-musl" = { checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz"} -"platforms.linux-x64" = { checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"} -"platforms.linux-x64-baseline" = { checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"} -"platforms.linux-x64-musl" = { checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"} -"platforms.linux-x64-musl-baseline" = { checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"} -"platforms.macos-arm64" = { checksum = "sha256:56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz"} -"platforms.macos-x64" = { checksum = "sha256:3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz"} -"platforms.macos-x64-baseline" = { checksum = "sha256:3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz"} -"platforms.windows-x64" = { checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e740e", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip"} -"platforms.windows-x64-baseline" = { checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e740e", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip"} + +[tools.shellcheck."platforms.linux-arm64"] +checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz" + +[tools.shellcheck."platforms.linux-arm64-musl"] +checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz" + +[tools.shellcheck."platforms.linux-x64"] +checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz" + +[tools.shellcheck."platforms.linux-x64-baseline"] +checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz" + +[tools.shellcheck."platforms.linux-x64-musl"] +checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz" + +[tools.shellcheck."platforms.linux-x64-musl-baseline"] +checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz" + +[tools.shellcheck."platforms.macos-arm64"] +checksum = "sha256:56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz" + +[tools.shellcheck."platforms.macos-x64"] +checksum = "sha256:3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz" + +[tools.shellcheck."platforms.macos-x64-baseline"] +checksum = "sha256:3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz" + +[tools.shellcheck."platforms.windows-x64"] +checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e740e" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip" + +[tools.shellcheck."platforms.windows-x64-baseline"] +checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e740e" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip" diff --git a/src/build_helpers.rs b/src/build_helpers.rs index cde5fd3e..98cac75d 100644 --- a/src/build_helpers.rs +++ b/src/build_helpers.rs @@ -314,6 +314,19 @@ mod tests { assert_eq!(serialized2, "TypeKind::String { max_length: Some(256) }"); } + #[test] + fn test_serialize_type_kind_pstring() { + let typ1 = TypeKind::PString { max_length: None }; + let serialized1 = serialize_type_kind(&typ1); + assert_eq!(serialized1, "TypeKind::PString { max_length: None }"); + + let typ2 = TypeKind::PString { + max_length: Some(128), + }; + let serialized2 = serialize_type_kind(&typ2); + assert_eq!(serialized2, "TypeKind::PString { max_length: Some(128) }"); + } + #[test] fn test_serialize_operator() { assert_eq!(serialize_operator(&Operator::Equal), "Operator::Equal"); diff --git a/src/evaluator/engine/mod.rs b/src/evaluator/engine/mod.rs index 76cc9e4e..ed68ef32 100644 --- a/src/evaluator/engine/mod.rs +++ b/src/evaluator/engine/mod.rs @@ -197,7 +197,9 @@ pub fn evaluate_rules( ) | LibmagicError::IoError(_), ) => { - // Expected evaluation errors for individual rules -- skip gracefully + // Expected evaluation errors for individual rules -- skip gracefully. + // TODO: emit debug-level trace when a rule is skipped due to error, + // so users can distinguish "rule didn't match" from "rule failed internally". continue; } Err(e) => { diff --git a/src/evaluator/engine/tests.rs b/src/evaluator/engine/tests.rs index 09edcbdd..afcef3b4 100644 --- a/src/evaluator/engine/tests.rs +++ b/src/evaluator/engine/tests.rs @@ -1880,3 +1880,87 @@ fn test_mixed_valid_and_invalid_rules_yield_valid_matches() { let matches = evaluate_rules(&rules, buffer, &mut context).unwrap(); assert_eq!(matches.len(), 2); } + +// ============================================================ +// PString (Pascal string) Tests +// ============================================================ + +#[test] +fn test_evaluate_single_rule_pstring_match() { + // Pascal string: length byte (5) followed by "Hello" + let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::PString { max_length: None }, + op: Operator::Equal, + value: Value::String("Hello".to_string()), + message: "Pascal string detected".to_string(), + children: vec![], + level: 0, + strength_modifier: None, + }; + + let buffer = &[5, b'H', b'e', b'l', b'l', b'o']; + let result = evaluate_single_rule(&rule, buffer).unwrap(); + assert!( + result.is_some(), + "PString rule should match when buffer contains matching pascal string" + ); +} + +#[test] +fn test_evaluate_single_rule_pstring_no_match() { + // Pascal string in buffer is "Hello", rule expects "World" + let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::PString { max_length: None }, + op: Operator::Equal, + value: Value::String("World".to_string()), + message: "Should not match".to_string(), + children: vec![], + level: 0, + strength_modifier: None, + }; + + let buffer = &[5, b'H', b'e', b'l', b'l', b'o']; + let result = evaluate_single_rule(&rule, buffer).unwrap(); + assert!( + result.is_none(), + "PString rule should not match when strings differ" + ); +} + +#[test] +fn test_evaluate_single_rule_pstring_with_child_rule() { + // Parent: PString at offset 0 matches "ELF" + // Child: byte at offset 4 equals 0x02 + let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::PString { max_length: None }, + op: Operator::Equal, + value: Value::String("ELF".to_string()), + message: "Pascal ELF".to_string(), + children: vec![MagicRule { + offset: OffsetSpec::Absolute(4), + typ: TypeKind::Byte { signed: false }, + op: Operator::Equal, + value: Value::Uint(0x02), + message: "64-bit".to_string(), + children: vec![], + level: 1, + strength_modifier: None, + }], + level: 0, + strength_modifier: None, + }; + + // Buffer: length=3, "ELF", then 0x02 at offset 4 + let buffer = &[3, b'E', b'L', b'F', 0x02]; + let config = EvaluationConfig::default(); + let mut context = EvaluationContext::new(config); + let matches = evaluate_rules(&[rule], buffer, &mut context).unwrap(); + assert_eq!( + matches.len(), + 2, + "Both parent PString and child byte rules should match" + ); +} diff --git a/src/evaluator/strength.rs b/src/evaluator/strength.rs index 5baa7078..197fcb8e 100644 --- a/src/evaluator/strength.rs +++ b/src/evaluator/strength.rs @@ -71,7 +71,7 @@ pub fn calculate_default_strength(rule: &MagicRule) -> i32 { // Type contribution: more specific types get higher strength strength += match &rule.typ { // Strings are most specific (they match exact byte sequences) - TypeKind::String { max_length } => { + TypeKind::String { max_length } | TypeKind::PString { max_length } => { // Base string strength let base = 20; // Add bonus for limited-length strings (more constrained match) diff --git a/src/evaluator/types/mod.rs b/src/evaluator/types/mod.rs index 96becb13..4616983c 100644 --- a/src/evaluator/types/mod.rs +++ b/src/evaluator/types/mod.rs @@ -18,7 +18,7 @@ use date::format_timestamp_value; pub use date::{read_date, read_qdate}; pub use float::{read_double, read_float}; pub use numeric::{read_byte, read_long, read_quad, read_short}; -pub use string::read_string; +pub use string::{read_pstring, read_string}; /// Errors that can occur during type reading operations. #[derive(Debug, Error, PartialEq, Eq)] @@ -81,6 +81,7 @@ pub fn read_typed_value( TypeKind::Date { endian, utc } => read_date(buffer, offset, *endian, *utc), TypeKind::QDate { endian, utc } => read_qdate(buffer, offset, *endian, *utc), TypeKind::String { max_length } => read_string(buffer, offset, *max_length), + TypeKind::PString { max_length } => read_pstring(buffer, offset, *max_length), } } diff --git a/src/evaluator/types/string.rs b/src/evaluator/types/string.rs index 5c36a5d8..c273aed0 100644 --- a/src/evaluator/types/string.rs +++ b/src/evaluator/types/string.rs @@ -83,6 +83,100 @@ pub fn read_string( Ok(Value::String(string_value)) } +/// Reads a Pascal-style length-prefixed string from the buffer. +/// +/// Pascal strings store the length as the first byte (0-255), followed by +/// that many bytes of string data. Unlike C strings, they are not null-terminated. +/// +/// # Arguments +/// +/// * `buffer` - The byte buffer to read from +/// * `offset` - The offset position to start reading from +/// * `max_length` - Optional maximum length limit (caps the length byte value) +/// +/// # Returns +/// +/// Returns `Ok(Value::String(string))` if successful. Invalid UTF-8 byte sequences +/// are replaced with the Unicode replacement character (U+FFFD). +/// +/// # Security +/// +/// This function provides bounds checking to prevent reading beyond buffer limits. +/// When `max_length` is set, bounds are validated against the capped length, not the +/// raw length byte. This matches GNU `file` behavior: `max_length` is intended to +/// handle cases where the length byte may reference more data than actually exists. +/// +/// # Examples +/// +/// ``` +/// use libmagic_rs::evaluator::types::read_pstring; +/// use libmagic_rs::parser::ast::Value; +/// +/// let buffer = b"\x05Hello"; +/// let result = read_pstring(buffer, 0, None).unwrap(); +/// assert_eq!(result, Value::String("Hello".to_string())); +/// +/// let buffer = b"\x0aHelloWorld"; +/// let result = read_pstring(buffer, 0, Some(5)).unwrap(); +/// assert_eq!(result, Value::String("Hello".to_string())); +/// ``` +/// +/// # Errors +/// +/// Returns `TypeReadError::BufferOverrun` if: +/// - The offset is beyond buffer bounds (cannot read the length byte) +/// - The string data (length byte value) extends beyond the buffer +pub fn read_pstring( + buffer: &[u8], + offset: usize, + max_length: Option, +) -> Result { + // Check if we can read the length byte + let length_byte = *buffer.get(offset).ok_or(TypeReadError::BufferOverrun { + offset, + buffer_len: buffer.len(), + })?; + + let string_length = usize::from(length_byte); + + // Apply max_length limit if specified. + // NOTE: We intentionally validate bounds against actual_length (after capping), + // not against the raw length byte. This matches GNU file's behavior: if the + // length byte claims 10 bytes but max_length caps to 3 and 3+ bytes exist, + // the read succeeds. Validating against the raw length byte would reject + // valid magic rules where max_length is used precisely to handle truncated data. + let actual_length = if let Some(max_len) = max_length { + std::cmp::min(string_length, max_len) + } else { + string_length + }; + + // Check if we have enough bytes for the (possibly capped) string data + let string_start = offset.checked_add(1).ok_or(TypeReadError::BufferOverrun { + offset, + buffer_len: buffer.len(), + })?; + let string_end = + string_start + .checked_add(actual_length) + .ok_or(TypeReadError::BufferOverrun { + offset: usize::MAX, + buffer_len: buffer.len(), + })?; + + if string_end > buffer.len() { + return Err(TypeReadError::BufferOverrun { + offset: string_end, + buffer_len: buffer.len(), + }); + } + + let string_bytes = &buffer[string_start..string_end]; + let string_value = String::from_utf8_lossy(string_bytes).into_owned(); + + Ok(Value::String(string_value)) +} + #[cfg(test)] mod tests { use super::*; @@ -322,4 +416,186 @@ mod tests { } } } + + // ============================================================ + // read_pstring tests + // ============================================================ + + #[test] + fn test_read_pstring_basic() { + let buffer = b"\x05Hello"; + let result = read_pstring(buffer, 0, None).unwrap(); + assert_eq!(result, Value::String("Hello".to_string())); + } + + #[test] + fn test_read_pstring_at_offset() { + let buffer = b"PREFIX\x03Foo"; + let result = read_pstring(buffer, 6, None).unwrap(); + assert_eq!(result, Value::String("Foo".to_string())); + } + + #[test] + fn test_read_pstring_empty_string() { + let buffer = b"\x00trailing"; + let result = read_pstring(buffer, 0, None).unwrap(); + assert_eq!(result, Value::String(String::new())); + } + + #[test] + fn test_read_pstring_single_char() { + let buffer = b"\x01A"; + let result = read_pstring(buffer, 0, None).unwrap(); + assert_eq!(result, Value::String("A".to_string())); + } + + #[test] + fn test_read_pstring_max_length_limits() { + let buffer = b"\x0aHelloWorld"; + let result = read_pstring(buffer, 0, Some(5)).unwrap(); + assert_eq!(result, Value::String("Hello".to_string())); + } + + #[test] + fn test_read_pstring_max_length_larger_than_prefix() { + let buffer = b"\x03Foo"; + let result = read_pstring(buffer, 0, Some(100)).unwrap(); + assert_eq!(result, Value::String("Foo".to_string())); + } + + #[test] + fn test_read_pstring_max_length_zero() { + let buffer = b"\x05Hello"; + let result = read_pstring(buffer, 0, Some(0)).unwrap(); + assert_eq!(result, Value::String(String::new())); + } + + #[test] + fn test_read_pstring_buffer_overrun_offset_past_end() { + let buffer = b"Hello"; + let result = read_pstring(buffer, 10, None); + assert_eq!( + result.unwrap_err(), + TypeReadError::BufferOverrun { + offset: 10, + buffer_len: 5, + } + ); + } + + #[test] + fn test_read_pstring_buffer_overrun_empty_buffer() { + let buffer = b""; + let result = read_pstring(buffer, 0, None); + assert_eq!( + result.unwrap_err(), + TypeReadError::BufferOverrun { + offset: 0, + buffer_len: 0, + } + ); + } + + #[test] + fn test_read_pstring_buffer_overrun_length_exceeds_data() { + // Length byte says 10 but only 3 bytes follow + let buffer = b"\x0aFoo"; + let result = read_pstring(buffer, 0, None); + assert_eq!( + result.unwrap_err(), + TypeReadError::BufferOverrun { + offset: 11, // string_end = 1 (start) + 10 (length byte) + buffer_len: 4, + } + ); + } + + #[test] + fn test_read_pstring_length_byte_only() { + // Buffer has length byte but no string data, and length > 0 + let buffer = b"\x05"; + let result = read_pstring(buffer, 0, None); + assert_eq!( + result.unwrap_err(), + TypeReadError::BufferOverrun { + offset: 6, // string_end = 1 (start) + 5 (length byte) + buffer_len: 1, + } + ); + } + + #[test] + fn test_read_pstring_offset_overflow() { + let buffer = b"\x05Hello"; + let result = read_pstring(buffer, usize::MAX, None); + assert_eq!( + result.unwrap_err(), + TypeReadError::BufferOverrun { + offset: usize::MAX, + buffer_len: buffer.len(), + } + ); + } + + #[test] + fn test_read_pstring_max_length_caps_when_buffer_short() { + // Length byte says 10, only 5 data bytes follow, but max_length=5 caps the read + let buffer = b"\x0aHello"; + let result = read_pstring(buffer, 0, Some(5)).unwrap(); + assert_eq!(result, Value::String("Hello".to_string())); + } + + #[test] + fn test_read_pstring_utf8_valid() { + // "Café" in UTF-8 is 5 bytes: 43 61 66 c3 a9 + let buffer = b"\x05Caf\xc3\xa9"; + let result = read_pstring(buffer, 0, None).unwrap(); + assert_eq!(result, Value::String("Café".to_string())); + } + + #[test] + fn test_read_pstring_utf8_invalid() { + let buffer = b"\x03\xff\xfe\xfd"; + let result = read_pstring(buffer, 0, None).unwrap(); + if let Value::String(s) = result { + assert!(s.contains('\u{FFFD}')); + } else { + panic!("Expected Value::String"); + } + } + + #[test] + fn test_read_pstring_max_length_byte_value() { + // Length byte = 255, with exactly 255 bytes of data + let mut buffer = vec![0xFF]; + buffer.extend(std::iter::repeat_n(b'A', 255)); + let result = read_pstring(&buffer, 0, None).unwrap(); + assert_eq!(result, Value::String("A".repeat(255))); + } + + #[test] + fn test_read_pstring_consistency_with_typed_value() { + let buffer = b"\x04Test"; + let direct_result = read_pstring(buffer, 0, None).unwrap(); + + let type_kind = TypeKind::PString { max_length: None }; + let typed_result = read_typed_value(buffer, 0, &type_kind).unwrap(); + + assert_eq!(direct_result, typed_result); + assert_eq!(typed_result, Value::String("Test".to_string())); + } + + #[test] + fn test_read_pstring_consistency_with_max_length() { + let buffer = b"\x0aLongString"; + let direct_result = read_pstring(buffer, 0, Some(4)).unwrap(); + + let type_kind = TypeKind::PString { + max_length: Some(4), + }; + let typed_result = read_typed_value(buffer, 0, &type_kind).unwrap(); + + assert_eq!(direct_result, typed_result); + assert_eq!(typed_result, Value::String("Long".to_string())); + } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 6324c8a2..894321b9 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -180,6 +180,27 @@ pub enum TypeKind { /// Maximum length to read max_length: Option, }, + /// Pascal string (length-prefixed byte followed by string data) + /// + /// Pascal strings store the length as the first byte (0-255), followed by + /// that many bytes of string data. Unlike C strings, they are not + /// null-terminated. + /// + /// # Examples + /// + /// ``` + /// use libmagic_rs::parser::ast::TypeKind; + /// + /// let pstring = TypeKind::PString { max_length: None }; + /// assert_eq!(pstring, TypeKind::PString { max_length: None }); + /// + /// let limited = TypeKind::PString { max_length: Some(64) }; + /// assert_eq!(limited, TypeKind::PString { max_length: Some(64) }); + /// ``` + PString { + /// Maximum length to read (caps the length byte value) + max_length: Option, + }, } impl TypeKind { @@ -205,7 +226,7 @@ impl TypeKind { Self::Short { .. } => Some(16), Self::Long { .. } | Self::Float { .. } | Self::Date { .. } => Some(32), Self::Quad { .. } | Self::Double { .. } | Self::QDate { .. } => Some(64), - Self::String { .. } => None, + Self::String { .. } | Self::PString { .. } => None, } } } @@ -864,6 +885,10 @@ mod tests { TypeKind::String { max_length: Some(128), }, + TypeKind::PString { max_length: None }, + TypeKind::PString { + max_length: Some(64), + }, ]; for typ in types { diff --git a/src/parser/codegen.rs b/src/parser/codegen.rs index 658c8577..922af9ae 100644 --- a/src/parser/codegen.rs +++ b/src/parser/codegen.rs @@ -211,6 +211,12 @@ pub fn serialize_type_kind(typ: &TypeKind) -> String { } None => "TypeKind::String { max_length: None }".to_string(), }, + TypeKind::PString { max_length } => match max_length { + Some(value) => { + format!("TypeKind::PString {{ max_length: Some({value}) }}") + } + None => "TypeKind::PString { max_length: None }".to_string(), + }, } } diff --git a/src/parser/grammar/mod.rs b/src/parser/grammar/mod.rs index fa587fa0..e6e187c5 100644 --- a/src/parser/grammar/mod.rs +++ b/src/parser/grammar/mod.rs @@ -731,6 +731,7 @@ pub fn parse_type_and_operator(input: &str) -> IResult<&str, (TypeKind, Option IResult<&str, &str> { tag("ledate"), tag("date"), )), - // String types (1 branch, will grow with pstring/search/regex) - tag("string"), + // String types + alt((tag("pstring"), tag("string"))), )) .parse(input) } @@ -292,8 +292,12 @@ pub fn type_keyword_to_kind(type_name: &str) -> TypeKind { utc: false, }, - // STRING type + // STRING types "string" => TypeKind::String { max_length: None }, + // NOTE: GNU libmagic also supports pstring/B (1-byte, default), pstring/H + // (2-byte LE), and pstring/L (4-byte LE) length-prefix variants. Only the + // default 1-byte prefix is supported here; /B, /H, /L suffixes are not yet parsed. + "pstring" => TypeKind::PString { max_length: None }, _ => unreachable!("type_keyword_to_kind called with unknown type: {type_name}"), } @@ -462,6 +466,19 @@ mod tests { ); } + #[test] + fn test_parse_type_keyword_pstring() { + assert_eq!(parse_type_keyword("pstring rest"), Ok((" rest", "pstring"))); + } + + #[test] + fn test_type_keyword_to_kind_pstring() { + assert_eq!( + type_keyword_to_kind("pstring"), + TypeKind::PString { max_length: None } + ); + } + #[test] fn test_roundtrip_all_keywords() { // Verify that every keyword parsed by parse_type_keyword can be @@ -471,7 +488,7 @@ mod tests { "long", "ulong", "lelong", "ulelong", "belong", "ubelong", "quad", "uquad", "lequad", "ulequad", "bequad", "ubequad", "float", "befloat", "lefloat", "double", "bedouble", "ledouble", "date", "ldate", "bedate", "beldate", "ledate", "leldate", "qdate", - "qldate", "beqdate", "beqldate", "leqdate", "leqldate", "string", + "qldate", "beqdate", "beqldate", "leqdate", "leqldate", "pstring", "string", ]; for keyword in keywords { let (rest, parsed) = parse_type_keyword(keyword).unwrap(); diff --git a/tests/evaluator_tests.rs b/tests/evaluator_tests.rs index 6a7cd89c..443fec6f 100644 --- a/tests/evaluator_tests.rs +++ b/tests/evaluator_tests.rs @@ -355,6 +355,80 @@ fn test_evaluate_float_rule_less_than() { ); } +#[test] +fn test_evaluate_pstring_rule_match() { + // Pascal string: length byte (3) followed by "PDF" + let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::PString { max_length: None }, + op: Operator::Equal, + value: Value::String("PDF".to_string()), + message: "Pascal PDF marker".to_string(), + children: vec![], + level: 0, + strength_modifier: None, + }; + + let buffer: &[u8] = &[3, b'P', b'D', b'F', 0x00, 0x00]; + let config = EvaluationConfig::default(); + let mut context = EvaluationContext::new(config); + let matches = evaluate_rules(&[rule], buffer, &mut context).unwrap(); + assert_eq!(matches.len(), 1, "PString rule should match pascal string"); + assert_eq!(matches[0].message, "Pascal PDF marker"); +} + +#[test] +fn test_evaluate_pstring_rule_no_match() { + let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::PString { max_length: None }, + op: Operator::Equal, + value: Value::String("ZIP".to_string()), + message: "Should not match".to_string(), + children: vec![], + level: 0, + strength_modifier: None, + }; + + let buffer: &[u8] = &[3, b'P', b'D', b'F']; + let config = EvaluationConfig::default(); + let mut context = EvaluationContext::new(config); + let matches = evaluate_rules(&[rule], buffer, &mut context).unwrap(); + assert!( + matches.is_empty(), + "PString rule should not match when strings differ" + ); +} + +#[test] +fn test_evaluate_pstring_with_max_length() { + // Pascal string in buffer has length=10, but max_length caps at 3 + let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::PString { + max_length: Some(3), + }, + op: Operator::Equal, + value: Value::String("Hel".to_string()), + message: "Truncated pascal string".to_string(), + children: vec![], + level: 0, + strength_modifier: None, + }; + + let buffer: &[u8] = &[ + 10, b'H', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', + ]; + let config = EvaluationConfig::default(); + let mut context = EvaluationContext::new(config); + let matches = evaluate_rules(&[rule], buffer, &mut context).unwrap(); + assert_eq!( + matches.len(), + 1, + "PString with max_length should truncate and match" + ); +} + #[test] fn test_evaluate_float_rule_no_match() { // Buffer contains 1.0f32 LE, rule expects == 2.0 -- should NOT match diff --git a/tests/property_tests.rs b/tests/property_tests.rs index 4b3ef852..dfa9d23d 100644 --- a/tests/property_tests.rs +++ b/tests/property_tests.rs @@ -48,6 +48,9 @@ fn arb_type_kind() -> impl Strategy { (0usize..256usize).prop_map(|len| TypeKind::String { max_length: Some(len), }), + (0usize..256usize).prop_map(|len| TypeKind::PString { + max_length: Some(len), + }), ] }