The `Exceptions` handler has several issues that surface during error and exception handling, causing fatal errors, potential sensitive data leaks, and poor isolation when rendering error views.

## Observed problems

1. **Fatal error when sending HTTP status headers in non-CLI mode**
   When `exceptionHandler()` runs for a web request, it calls `$this->response->getReason()`. That method does not exist – the Response class uses `getReasonPhrase()`. This results in an uncaught `Error` (Call to undefined method) that masks the original exception.

2. **Undefined variable `$viewFile` when the fallback error view is not found**
   Inside `render()`, if the configured error view path does not contain a matching template (e.g., `error_exception.php` is missing), `$viewFile` is never set. The code then tries to `include $viewFile`, producing a notice followed by a fatal error instead of a graceful failure.

3. **Variable scope pollution in error view rendering**
   The `render()` method calls `extract()` on collected variables directly in the method’s scope, which risks overwriting local variables of the `Exceptions` instance and can interfere with the view file’s expectations. This can break error view output or leak internal state.

4. **Sensitive data masking skipped when config is non-empty but falsy**
   `collectVars()` uses `empty()` to decide whether to call `maskSensitiveData()`. When `sensitiveDataInTrace` is set to a falsy array-like value (e.g., `0`, `false`, or an explicitly empty array), masking is skipped even if sensitive keys were intended to be masked, potentially exposing secrets in error traces.

5. **Trace masking fails on non-array iterable traces**
   `maskSensitiveData()` uses `is_iterable()` to loop over trace entries. If a trace element is a Traversable object (not an array), the recursive traversal may fail, skip the masking, or generate warnings, leaving sensitive data unmasked.

## Expected behavior

- The exception handler should correctly call `getReasonPhrase()` and set the header without crashing.
- When no error view file is available, a clear plain-text message should be echoed and the process should exit with a non-zero code, avoiding PHP notices and fatal errors.
- Error view rendering should isolate extracted variables so they cannot collide with the renderer’s own context.
- `collectVars()` should only skip masking when `sensitiveDataInTrace` is **strictly an empty array**; all other configured values should be treated as a list of keys to mask.
- `maskSensitiveData()` should operate only on arrays when recursing; objects should be converted to arrays first, and iteration should be array-only to avoid traversal of generic `Traversable` objects.

## Impact

Without these fixes, any unhandled exception or fatal error in a web context can trigger a secondary fatal error because of the `getReason()` call, completely obscuring the original problem. Missing error views produce confusing PHP errors instead of a helpful fallback message. Sensitive configuration data may leak into error traces, which is a security concern when error details are displayed or logged. Variable scope collisions can corrupt the error page output.


```markdown
---
title: "Remove scattered per-method testing booleans; introduce centralized testMode on Query Builder"
labels: [enhancement, refactoring, query-builder]
---

## Motivation

Several Query Builder execution methods (`get()`, `getWhere()`, `countAll()`, `countAllResults()`, `insert()`, `insertBatch()`, `update()`, `updateBatch()`, `replace()`, `emptyTable()`, `truncate()`, `delete()`) accept optional `$test` or `$returnSQL` boolean parameters. When set to `true`, these flags prevent actual database execution and instead return the compiled SQL string (or a row count for batch operations). This pattern:

- Clutters method signatures for what is essentially a global mode.
- Is inconsistent in naming and position across methods.
- Causes confusion for end-users and maintainers alike.

## Desired behavior

- Add a **`testMode(bool $mode = true)`** setter to `BaseBuilder` that toggles a new `testMode` property.
- When `testMode` is **active**, all execution methods (`get`, `getWhere`, `insert`, `insertBatch`, `update`, `updateBatch`, `replace`, `delete`, `emptyTable`, `truncate`, `countAll`, `countAllResults`) must **not** execute a real query against the database; instead they return the compiled SQL string (or in the case of batch inserts/updates, a sensible test output like the number of affected-row stubs).
- All the `$test` / `$returnSQL` parameters must be **removed** from the public method signatures.
- Calling `testMode(false)` must restore normal execution behavior and return `$this` for chaining.

## Safety and edge cases

- The `testMode` flag must not leak into other builder instances – it should be a per-instance property only.
- Methods that already have explicit `getCompiled*` versions (e.g., `getCompiledSelect()`) should not be modified; `testMode` only affects the “execute” family of methods.
- The change must not break subclass overrides (e.g., `Postgre\Builder`’s `replace()` and `delete()` must use the parent’s `testMode` behavior after the dedicated parameters are removed).
- Resetting the builder (via `resetQuery()`, `resetRun()`, etc.) must **not** clear the `testMode` flag – it should persist until explicitly toggled again.
```


A `Factories` caching feature request

**Problem**
In long-running contexts (CLI daemons, ReactPHP servers, preloading), `CodeIgniter\Config\Factories` re-resolves and instantiates config and model objects on every request cycle. There is no public API to:

- Export the current set of loaded component instances for caching.
- Restore those instances from a cache without re-executing `__callStatic` logic.
- Detect whether the in-memory set of instances has changed, in order to invalidate a cache.

This forces downstream caching layers to either ignore the shared-instance optimisation entirely or resort to fragile reflection hacks.

**Expected behaviour**
`Factories` should expose three new `@internal`-marked methods to support caching:

1. `getComponentInstances(string $component): array`
   Returns an array with `'aliases'` and `'instances'` keys that fully describes the current state of that factory component. If the component has never been touched, it returns empty arrays.

2. `setComponentInstances(string $component, array $data): void`
   Accepts a previously obtained component data array (with `aliases` and `instances`) and restores it into the factory, clearing any “dirty” flag for that component.

3. `isUpdated(string $component): bool`
   Returns `true` when the component has been modified (e.g., a new instance was created or `reset` was called) since it was last restored or since the process started; otherwise `false`.

**Usage scenario**
A developer would call `getComponentInstances('config')` after initial loading, store the result in a persistent cache, and on subsequent requests call `setComponentInstances('config', cached)` to avoid reloading. Before storing, they could check `isUpdated('config')` to decide whether the cache is stale.

**Edge cases**
- After `reset()`, `getComponentInstances()` must return empty arrays and `isUpdated()` should reflect the component’s initial (clean) state.
- Calling `setComponentInstances()` must clear the “updated” flag so that a subsequent `isUpdated()` returns `false` until a new instance is added or the component is reset.
- The methods should operate only on the targeted component; a change to `config` must not affect the `isUpdated` status of `models`.

**Impact**
Without this capability, Factories’ performance benefits cannot be extended across process boundaries, and every web request must repeat class location and instantiation. Adding these methods allows clean, selective caching without breaking the existing shared-instance semantics.


Update `spark filter:check` to include globally required filters in its output, so the full filter chain is visible for a route.

Currently the command only shows the route’s own before/after filters. Required filters (those that always run) are missing, leaving an incomplete picture. Users need to see:
- all before filters that will execute, with required before filters listed first, then route before filters.
- all after filters, with route after filters listed first, then required after filters.

Required filters should be visually distinct – render them in yellow – to separate them from route-specific filters. The behaviour should work for any HTTP method supplied to the command, while correctly reflecting execution order and always including the required set.

feat: `spark filter:check` shows "Required Filters"


When using model field casting (e.g., `'email' => 'json-array'` defined in `$casts`), the insert and update methods fail to convert the data before storing it if you pass an object (either `stdClass` or a custom class). For example, inserting an object with an array value for a `json-array` field stores the raw PHP array representation (like `['a','b']`) instead of correctly converting it to a JSON string via `DataConverter::toDataSource()`. The same applies to update operations. The conversions work correctly when the data is passed as a plain array.

**Expected behavior**
- `insert()` and `update()` should apply field casting to data in **all supported input shapes** (arrays, `stdClass` objects, custom class instances that are not Entities).
- After inserting/updating with any of these formats, the value stored in the database must match the target data source format defined by the model’s `$casts` rules—just as it already does for array input.
- The existing logic for extracting properties from custom objects (including respecting `$updateOnlyChanged` when validation is skipped) must still work in conjunction with the cast conversion.

**Current behavior / impact**
- Data passed as objects bypasses the `toDataSource()` conversion, leading to database values that are not properly formatted (e.g., arrays stored verbatim, wrong date representations, etc.).
- This can cause silent data corruption, unexpected query results, or hard errors depending on the database driver and field type.
- Developers who prefer to work with object representations for their models cannot rely on field casting when saving data.

feat: add Model field casting for insert()/update() methods


**Bug Report: `ResponseTrait` ignores the `$format` property, making `setFormat()` ineffective**

**Observed behavior**
When a controller (e.g., `ResourceController`) sets the desired output format via `$this->setFormat('xml')` or by setting the `$format` property directly, the `respond()` method still performs full content negotiation based on the request’s `Accept` header. As a result, the response may be JSON even though XML was explicitly requested, or vice versa.

**Expected behavior**
If a format (specifically `'json'` or `'xml'`) has been set on the trait-using class, `respond()` should honor that choice and bypass media-type negotiation. The content-type should be forced to the corresponding MIME type and the body formatted accordingly. The fallback to content negotiation should only happen when no explicit format has been set.

**Impact**
API authors cannot reliably control the response format of a resource controller. This prevents forcing XML output for legacy clients or ensuring that all responses from a given endpoint always use a single format regardless of the client’s preferences.

**Context**
- The `$format` property currently lives only on `ResourceController`, but not on `ResponseTrait` – other controllers using the trait cannot influence the output format.
- The `respond()` method (from `ResponseTrait`) always uses `$this->request->negotiate(...)` even when a format was set.

**Desired resolution**
- Move the `$format` property (default `'json'`) into `ResponseTrait` so it becomes available to any controller using the trait.
- In `ResponseTrait::format()`, when the property is set to `'json'` or `'xml'`, use that value to construct the content-type (`application/json` or `application/xml`). Keep content negotiation as a fallback for any other value (or when no explicit format is provided).

Fix ResourceController Issue, including Test Unit


When the CORS filter responds to any OPTIONS request—whether or not it is a CORS preflight—ensure the response always includes a `Vary: Origin` header.

**Acceptance criteria**
1. For **preflight OPTIONS requests** sent by the browser (those with an `Origin` header, an `Access-Control-Request-Method` header, and that trigger the preflight path), the response must contain `Vary: Origin`.
2. For **non-preflight OPTIONS requests** that do **not** include an `Origin` header (e.g., a simple `OPTIONS *` or a same‑origin OPTIONS call), the response must **also** contain `Vary: Origin`.
3. In both cases the header should be appended to any other `Vary` values already set, preserving whatever the response might have decided independently.
4. The change must not affect responses for non-OPTIONS requests (no extra `Vary` header should appear on GET, POST, etc.).

**Rationale / observable constraint**
Intermediate caches (reverse proxies, CDNs) may cache the response to an OPTIONS request. If that response lacks a `Vary: Origin` header, a cached response from a request that had no `Origin` header could later be served for a cross-origin request with a different origin. Adding `Vary: Origin` forces the cache to store and serve separate responses per origin, avoiding incorrect cross-origin responses.

feat: always add 'Vary: Origin' header to OPTIONS requests


Allow generator commands to ignore the `--suffix` option.

Currently, all generators that use `GeneratorTrait` will append the component name as a suffix when the user passes the `--suffix` flag. Some custom commands (e.g., `make:foo`) need the option to be truly optional—specifically, they need to be able to opt out of the automatic suffixing so that `--suffix` has no effect on the generated class name.

Provide a way to disable suffixing from within a generator command, such that when a generator opts out, the `--suffix` flag is ignored and the class name is used as-is. The default behavior for all existing generators must remain unchanged (suffixing still applies when `--suffix` is given, unless explicitly disabled by a child command).


---
name: Fix page caching after-filters and performance metrics order
about: After-filters and elapsed_time replacement run incorrectly when page caching is active
labels: bug, cache
---

### Description

When page caching is enabled (`$this->cachePage()` or `CodeIgniter::cache()` in a controller), the response is cached **before** the after-filters execute. This causes two problems:

1. **After-filter changes are lost**
   Any response headers (e.g. `X-Frame-Options` added by the built-in `secureheaders` after-filter) are not stored in the cached page. Subsequent requests served from the cache completely omit those headers, and any body modifications performed by after-filters are discarded.

2. **Performance metrics replacement order is reversed**
   Currently the `{elapsed_time}` placeholder is replaced with the total execution time, and then the fully-rendered page is cached. The intended design is the opposite: the cached file should keep the raw `{elapsed_time}` placeholder so that live speed updates still appear on cache hits.

Additionally, because the total execution time is measured inside `displayPerformanceMetrics()`, the debug toolbar and after-filters cannot reliably access the correct `total_execution` timing before they run.

### Expected Behaviour

- After-filters (any filter in `$filters->globals['after']` or route-specific after-filters) must run **before** the response is persisted to the page cache.
- The cached page must include all headers and body changes introduced by those after-filters.
- The cached page body must contain the literal `{elapsed_time}` placeholder – *not* the actual elapsed time – so that the placeholder can be replaced with the live execution time on each cache hit.
- `displayPerformanceMetrics()` should only perform placeholder substitution; it must **not** compute the total execution time (that should happen before filters and caching).

### Steps to Reproduce

1. Enable page caching in a route (e.g. `CodeIgniter::cache(3600)` inside a controller).
2. Register `secureheaders` (or any custom filter that sets a response header) as an after-filter global in `Config\Filters::$globals['after']`.
3. Visit the route twice (first visit populates the cache, second visit serves from cache).
4. Observe on the second response that the security header is missing and that the cached body may lack any body modifications performed by the after-filter.
5. Check cached file – it contains the fully replaced elapsed time string instead of the placeholder.

### Proposed Solution

Adjust the order of operations in the request handling pipeline (`handleRequest()` and `gatherOutput()` in `system/CodeIgniter.php`):

- Move the computation of `total_execution` timing **before** the after-filters are executed, so it is available to filters and the debug toolbar.
- Run the after-filters **before** `cachePage()` is called.
- Call `cachePage()` **after** filters, but **before** performance metrics replacement. This ensures the cached file contains all filter modifications and retains the `{elapsed_time}` placeholder.
- Perform the `{elapsed_time}` placeholder replacement only when building the final response, not during caching.

These changes guarantee that after-filters are always honoured in cached pages and that performance metrics stay dynamic.

fix: page cache saves Response before running after filters


Add an `is()` method to `CodeIgniter\HTTP\IncomingRequest` that simplifies common request inspection. The method should:

- Accept a single string argument `$value`.
- Return `true` if the request matches, otherwise `false`.
- When `$value` is a lowercase HTTP verb (`'get'`, `'post'`, `'put'`, `'delete'`, `'head'`, `'patch'`, `'options'`), match case-insensitively against the request method (e.g., `$request->is('post')` is true for both `POST` and `post`).
- When `$value` is `'json'`, return `true` if the `Content-Type` header contains `application/json`.
- Throw an `InvalidArgumentException` for any other `$value`.

The method should complement the existing `isCLI()`, `isAJAX()`, and `isSecure()` helpers.


**Title:** `insertBatch` method in Query Builder is unusable due to missing dependencies and SQL generation bugs

**Description**

The `insertBatch` method on the CodeIgniter Query Builder (in `BaseBuilder`) is currently broken and cannot be used to insert multiple rows at once.

**Observed problems**

1. **Fatal error when executing the query**
   Calling `$builder->insertBatch($data)` triggers a call to `$this->db->affectedRows()` inside the batch loop, but no such method exists on `BaseConnection` or any of its shipped drivers. This causes a fatal error:
   `Call to undefined method ...::affectedRows()`

2. **Incorrect SQL generation – values are escaped directly into the statement**
   Even if you work around the fatal error, the protected `_insertBatch` method builds INSERT statements by calling `$this->escape($value)` on every column value and placing the escaped result directly into the SQL string.
   - This bypasses proper prepared-statement binding and risks SQL syntax errors (e.g. with special characters or locale-sensitive escaping).
   - It prevents the use of the query builder’s bind mechanism, making the final SQL inconsistent with how other queries are built.

3. **Duplicate named binds cause incomplete replacement**
   When `_insertBatch` is eventually changed to use named binds (placeholders like `:column_name`), the same bound-name is reused for each row in a multi-row INSERT.
   `BaseQuery::compileBinds()` processes binds in the order they appear, but when an identical named bind appears multiple times (once per value row), only the first occurrence is replaced; later rows still contain the placeholder, resulting in an invalid query that either fails or behaves unexpectedly.

4. **Misleading error message when data is passed via argument**
   When `insertBatch()` is called with data as an argument, the validation logic incorrectly checks `$this->QBSet` first in some code paths and may emit a `DatabaseException` saying “You must use the "set" method …”, which is confusing because the data was provided directly.

**Expected behavior**

- Calling `$builder->insertBatch($set, $escape, $batch_size)` should insert the passed rows in batches, using **named parameter bindings** (placeholders) rather than raw escaped values.
- The query should be run via the connection’s `query()` method and should correctly retrieve the number of affected rows through a dedicated `affectedRows()` method on the connection.
- Duplicate bind names (reused across rows in the same INSERT statement) must be resolved so that every placeholder is replaced with its corresponding value.
- After execution, the builder’s internal write-related state (like `QBSet`, `QBKeys`, etc.) should be reset, as it is for single-row inserts.
- When an empty array is passed as `$set`, a clear error should be raised (and that is already handled). Otherwise the method should proceed without spurious “must use set” errors.

**Impact**

The `insertBatch` method is completely unusable in its current state – any attempt to use it for batch inserts results in either a fatal error or corrupt SQL. Developers are forced to hand-write INSERT … VALUES statements, losing the abstraction and bind-safety that the Query Builder is supposed to provide. This also breaks feature parity with previous CodeIgniter versions where `insertBatch` worked out of the box.


Add a `getForeignKeyData` method on the database connection to retrieve foreign key details for a given table, parallel to the existing `getIndexData` / `getFieldData` methods.

- On `BaseConnection`, add a public `getForeignKeyData(string $table)` that calls a protected driver-specific `_foreignKeyData`.
- PostgreSQL driver: implement `_foreignKeyData` to query `information_schema.table_constraints` and related views, returning an array of `stdClass` objects with keys `constraint_name`, `table_name`, `column_name`, `foreign_table_name`, `foreign_column_name`. Only include constraints of type `FOREIGN KEY`. Use proper escaping.
- MySQLi driver: implement `_foreignKeyData` as a stub returning an empty array (or the minimum needed to satisfy the signature).

No other behavioral changes.


When updating a record with the Model’s `save()` or `update()` method using only a subset of fields, validation rules are still executed for fields not present in the data. For example, a model with a `required` rule on `name` will fail validation even if only the `description` field is being updated and the `name` field is intentionally omitted from the data array. This prevents partial updates without disabling validation entirely or removing the rule, making it impractical to update specific columns while keeping validation active.

**Expected behavior:**
Validation should only run rules for fields that actually appear in the provided data. Fields not set in the data array should have their rules skipped entirely. If no validation rules match any provided fields, validation should pass. When a field key is present but its value is `null` or empty, the associated rules should still be enforced, since the explicit intent is to set that field.

**Impact:**
Developers are forced to either weaken validation rules (e.g., remove `required` constraints for fields that are mandatory on insert but optional on update) or manually toggle `skipValidation` before partial updates. This undermines the safety and convenience of model-level validation.

**Acceptance criteria:**
1. `validate()` (and therefore `save()`/`update()`) ignores validation rules for keys not in the data array.
2. If the data array is empty (or after filtering all rules are removed), validation returns `true`.
3. Rules with field placeholders (`{id}`) still work, only the presence of the key is checked.

Don't run validation rules for data that's not getting set. Fixes #1584


**Title:** Add built‑in URL helper plugins for the view parser

**Description:**
The view parser already supports plugins using the `{+ plugin_name [param=value ...] +}` syntax, but no core plugins are shipped out‑of‑the‑box. This forces every application to manually register common helpers like `current_url` or `mailto`, which is repetitive and makes the framework less ergonomic.

We should provide basic plugin bindings for the four most-used URL helpers so that template authors can write:

```
{+ current_url +}
{+ previous_url +}
{+ mailto email=foo@example.com title="Contact" +}
{+ safe_mailto email=bar@example.com title="Contact" attributes='class="link"' +}
```

**Requirements:**

1. **Plugin registration** – Register the following aliases in the parser’s core plugin configuration (currently an empty array `$corePlugins` in `Config\View`). Each alias should resolve to a static method that simply delegates to the corresponding URL helper function:
   - `current_url` → `current_url()`
   - `previous_url` → `previous_url()`
   - `mailto` → `mailto()`
   - `safe_mailto` → `safe_mailto()`

2. **Parameter handling for `mailto` and `safe_mailto`**
   - Accept named parameters: `email` (required), `title` and `attributes` (both optional, default to an empty string).
   - The plugins must load the URL helper if it hasn’t been loaded yet (using `function_exists` checks), so they work even when the helper isn’t explicitly loaded by user code.

3. **Parser parameter parsing extension**
   Currently the parser’s regex for plugin parameters only allows `[\w\d=-_:\+\s()]*` characters. Since email addresses contain `@` and `.`, and users might pass quoted attribute strings, the allowed character set must be expanded to include `@`, `.`, and double‑quote (`"`).
   - This ensures templates like `{+ mailto email=foo@example.com title="Silly" +}` are parsed correctly without mangling the parameter values.

4. **Behavioural invariants**
   - Existing user‑registered plugins must continue to work unchanged.
   - Plugins without parameters (e.g. `current_url`) should output the helper’s return value directly.
   - The plugins should be stateless and safe to call at any point during template rendering.

**Acceptance criteria (observable):**
- A template containing `{+ current_url +}` outputs the full current URL.
- A template containing `{+ previous_url +}` outputs the previous page URL.
- `{+ mailto email=user@domain.com title=“Mail” +}` produces a valid `mailto:` anchor tag.
- `{+ safe_mailto email=user@domain.com title=“Mail” attributes=‘class=“btn”’ +}` produces an obfuscated mailto link with the extra attributes.
- The same works when the URL helper has not been manually loaded before rendering.


**Feature Request: Array callable syntax for route handlers**

Currently routes accept a string like `'Controller::method'` or `'Namespace\Controller::method'` as the handler. Add the ability to pass an array that resolves to a controller/method callable, with optional parameter routing.

**Behavior:**

- Any routing method that accepts a `$to` (`add`, `get`, `post`, `match`, etc.) should also accept an array of the form `[Full\Class::class, 'method']`. Internally this must be stored as `'\Full\Class::method'` (leading backslash to indicate fully qualified), so the existing dispatch logic continues to work unchanged.
- If the route pattern contains placeholders (e.g. `(:num)/(:num)`), but no explicit parameter string is provided, the system must automatically append `/$1/$2` (one for each placeholder) to the stored handler string, preserving the original placeholder order.
- For cases where the default parameter mapping is incorrect (e.g., routes with custom regex, or when the method expects a different parameter order), allow a second form: `[[Full\Class::class, 'method'], '$2/$1']`. The second element is a parameter string that will be appended exactly as given (e.g. `'\Full\Class::method/$2/$1'`).

**Constraints:**

- The array must be callable (i.e. `is_callable($to, true, $callableName)` must return `true`), and `$callableName` must be used to build the `'\Class::method'` string.
- If the first form is a multidimensional array where `$to[0]` is callable and `$to[1]` is a string, interpret it as the explicit-params form.
- Placeholder detection should count occurrences of `(:…)` patterns in the route pattern (after any custom placeholder translation). This count drives automatic parameter generation.
- The conversion must happen inside the route collection, not in userland code, at the point where the route is stored, so that all route-listing features (e.g. `spark routes`) also reflect the normalized string form.

**Example usage (strawman):**

```php
$routes->add('product/(:num)/(:num)', [[Product::class, 'index'], '$2/$1']);
$routes->get('home', [Home::class, 'index']);
```

The feature does **not** change how dispatch resolves handlers; it only provides an alternative syntax for defining them.


### Feature Request: Show resolved filter class names in `spark filter:check` output

The `spark filter:check` command currently helps developers inspect which filters are configured for a given route. However, it only displays filter aliases or raw filter names as defined in config or route definitions. When aliases are used, or when filters are passed with arguments, it is not immediately obvious which concrete filter classes will be executed, and in what order.

A clear view of the actual filter class names, including their arguments, would make debugging and understanding the filter pipeline much easier.

**Proposed behavior:**

When running `spark filter:check <method> <path>`, the command should **additionally** output a list of **resolved filter class names** for both the “before” and “after” stages. The class names should be fully qualified (with namespace) and printed in the exact execution order, including any required global filters.

**Acceptance criteria:**

1. **Two new output sections**
   The command should print a “Before Filter Classes” section and an “After Filter Classes” section, each containing the ordered sequence of fully-qualified class names that will be instantiated when the route is matched.

2. **Resolved class names, not aliases**
   - If a filter is registered as an alias (e.g., `csrf`), the displayed value must be the actual class name (e.g., `CodeIgniter\Filters\CSRF`).
   - If a filter is already specified as a class name, it must appear as given.

3. **Arguments are preserved and appended**
   When a filter carries arguments (e.g., `honeypot:max_links,delay`), the output must show the class name followed by a colon and the comma-separated arguments (e.g., `CodeIgniter\Filters\Honeypot:max_links,delay`). Filters without arguments must not have a trailing colon or empty arguments.

4. **Execution order must be correct**
   The list must reflect the actual execution order, merging required (global) filters with route-specific filters according to the configured filter ordering rules, including the `oldFilterOrder` feature flag if enabled. For example, if a global “before” filter is CSRF and a route applies `honeypot:arg`, the “Before Filter Classes” output must show the CSRF class first, then the honeypot class with arguments. The “After Filter Classes” output must follow the reverse order (if applicable) including required after filters.

5. **Visual distinction**
   The filter class names should be **highlighted (e.g., colored)** to separate them from other output and make them stand out.

6. **Graceful handling of edge cases**
   - If the route triggers a redirect (no filter pipeline is run), the command should still complete without error, possibly showing empty filter class lists or a clear placeholder.
   - If the route results in a 404 (not found), the command should indicate that the filter class is unknown (`<unknown>`) or provide a similar safe fallback.

The existing table output may remain unchanged; the new class-name output is an additional, complementary view.

feat: `spark filter:check` shows filter classnames


Add support for the missing CSP Level 3 directives `script-src-attr`, `style-src-elem`, `style-src-attr`, and `worker-src` so they can be used like the already supported `script-src-elem`.
The config class (`app/Config/ContentSecurityPolicy.php`) should expose typed properties for each new directive, and the CSP service (`system/HTTP/ContentSecurityPolicy.php`) should offer corresponding `add*` methods (`addScriptSrcAttr`, `addStyleSrcElem`, `addStyleSrcAttr`, `addWorkerSrc`) that accept a URI list and an optional report‑only flag, matching the existing pattern.
The new directives need to be included in the internal directive mapping so they appear in the generated `Content-Security-Policy` header when values are set.
Defaults should follow the convention of the other source‑list directives.

feat: add support for more CSP3 directives (#9909)


**Title:** `Model::set()` should accept an object (stdClass or Entity) as its first argument

**Background:**
`Model::set()` currently only accepts an array or a field-name string with a value. However, the model’s `insert()` and `update()` methods both accept plain objects and Entity instances directly. When building a query chain, e.g. after retrieving a row with `find()`, users naturally want to do:

```php
$user = $model->find($id);
$user->name = 'New Name';
$model->where('id', $id)->set($user)->update();
```

This fails because `set()` does not understand objects.

**Request:**
Make `Model::set($key, …)` accept an object as the first parameter. When `$key` is an object:

- If it is an instance of `stdClass`, cast it to an array: `(array) $key`.
- If it is a `CodeIgniter\Entity\Entity` (or any other object that the model can handle via its existing `objectToArray()` helper), convert it using that method to preserve the entity’s internal rules (e.g. `onlyChanged` behavior).
- The resulting array should then be used exactly as if an array had been passed directly, going through the normal per-field processing (allowed fields, validation, escaping, etc.).

**Acceptance criteria:**
1. Calling `$model->set($stdClassObject)` produces the same query-set keys and values as calling `$model->set((array) $stdClassObject)`.
2. Calling `$model->set($entity)` works and respects entity configuration (e.g. only changed properties when the entity uses `setRaw()` or tracks changes).
3. The method’s DocBlock is updated to list `object` as an accepted type for `$key`.
4. Existing array/string behavior must remain unaffected.

fix: Model::set() does not accept object


Add support for verifying the CSRF token submitted in the raw request body (application/x-www-form-urlencoded or similar) for non‑POST methods such as PUT, PATCH, and DELETE. Currently it is only checked in `$_POST`, custom headers, and JSON‑encoded bodies. After successful verification, the token must be stripped from the body so it does not leak into application input.

fix: check for CSRF token in the raw body


Add an abstract `insertID()` method to `BaseConnection` that returns the last auto‑generated insert ID. Implement it in the MySQLi driver (`Connection`) by returning the native `insert_id` property from the connection resource. The method must return an integer.


When saving an entity via `Model::save()`, `insert()`, or `update()`, the entity's `toArray(true)` is used, which applies attribute casts and magic getter/setter methods. This can cause values to be written to the database in a transformed form rather than the raw internal state. The entity must support a raw serialization path that returns the underlying property values without any casting or magic accessor calls, and `Model` should use that for persistence.

- Add a method on `Entity` that returns an array of all public/protected properties as-is, bypassing `__get`, casts, and getters, with an option to return only properties that have changed since the entity was instantiated.
- Update `Model::classToArray` to prefer this raw-method if it exists, falling back to existing behavior otherwise.
- When extracting changed-only raw data, include the primary key unconditionally even if it hasn't changed, so that update operations continue to work.
- Adjust the changed-property detection (in both the new raw method and the existing `toArray`) to skip a property if its current value equals the original value stored in `_original`, not only when both are `null`.

Saving entities no longer runs it through the magic methods. Fixes #1646


We should detect when a filter alias is defined multiple times with conflicting argument lists in the Filters configuration, and raise a clear, descriptive exception at initialization time. This prevents silent misconfiguration where a developer accidentally reuses the same alias with different argument sets (like `role:admin,super` and then `role:super`) within the same request lifecycle.

**Acceptance criteria:**

1. **Conflicting arguments from filter config**
   If the `filters` config array contains two rules for the same alias, and the first rule supplies a non-empty argument list, any later rule for the same alias that also supplies arguments (even identical ones) must throw a `ConfigException` with a message indicating the alias and its already-registered arguments.
   *Example:* `'role:admin,super' => ['before' => ...]` followed by `'role:super' => ['before' => ...]` → exception.

2. **Null (argument-less) definitions allow later override**
   When an alias is first defined *without* arguments (e.g., just `'role'`), the registered arguments are effectively `null`. A subsequent definition that provides arguments (like `'role:super'`) is allowed and must replace the `null` value without throwing an exception.

3. **Same alias in both `before` and `after` sections is safe**
   When the same filter alias is used in both the `before` and `after` sections of a single filter rule (or across separate rules that apply to the same request), the `after` registration should **not** re-validate the arguments. The argument check is skipped for `after`, because the arguments were already set during the `before` registration. This avoids false positives for legitimate re-use within the same request.

4. **Programmatic filter enabling respects the same constraint**
   When calling `Filters::enableFilter($name)` with an alias that already has non-null arguments registered (e.g., via an earlier config rule or a prior `enableFilter` call), the method must throw a `ConfigException`. If the existing arguments are `null` (set via a no-argument `enableFilter` call or a bare alias definition), the new arguments are accepted.

The goal is to catch accidental argument mismatch early, while still allowing intentional overrides in the specific case where no arguments were previously bound.

feat: throws exception if the filter arguments already defined


When using `Connection::getFieldData()` with the OCI8 and SQLSRV drivers, the column `default` entries contain leftover formatting characters that differ from the normalised values returned by other drivers (e.g. MySQLi, Postgre). This inconsistency makes it hard to rely on default values in schema introspection and migration tools.

**Acceptance criteria**

1. For **OCI8**:
   – Trailing whitespace must be stripped from the `default` string (e.g. `'0 '` → `'0'`).
   – No other transformations should be applied; expression defaults that happen to have other whitespace should be left as-is apart from the trailing trim.

2. For **SQLSRV**:
   – Leading/trailing parentheses must be removed, including multiple nested pairs (e.g. `((0))` → `'0'`).
   – The literal `NULL` (case-insensitive, possibly wrapped in parentheses like `(NULL)`) must become PHP `null` in the field data.
   – String literals enclosed in single quotes (e.g. `('abc')`) must have the outer quotes removed, and embedded escaped quotes (`''`) must be unescaped to a single quote.
   – Expression defaults such as `getdate()`, `newid()`, `(1+1)`, `(complex_func(1, 2))` must lose their outer parentheses but keep their body unchanged (e.g. `'(getdate())'` → `'getdate()'`).
   – A plain value already without parentheses (e.g. `'plain_value'`) must not be altered.

**Constraints**
- The normalisation must happen inside the driver-specific `_fieldData()` methods and must not change how the database stores the defaults.
- The behaviour of other database drivers must remain untouched.
- Any tests that previously asserted the old raw values (e.g. `((0))` for SQLSRV, `'0 '` for OCI8) should be updated to reflect the cleaned defaults.

By applying these normalisations, `getFieldData()` will return predictable default values across all supported database backends.

fix: `Connection::getFieldData()` default value convention for `SQLSRV` and `OCI8` (#9680)


The `uploaded` file validation rule does not handle array file uploads correctly. When a form field accepts multiple files (e.g., `<input type="file" name="photos[]" multiple>`) and the validation rule `uploaded[photos]` is applied, the rule fails to validate each file in the array. It may return `false` even though all files were uploaded successfully, or only check the first file.

**Desired behavior:**
- When the field contains an array of files, the `uploaded` rule should return `true` only if **every** file in the array was uploaded without error (i.e., each file’s upload error code is `0`, or `isValid()` returns `true` in non-testing environments).
- If **any** file in the array has an upload error, the rule should return `false`.
- If the field is empty (no files selected), the rule should return `false`, consistent with single-file behavior.
- The behavior for single-file fields must remain unchanged.

This ensures that the `uploaded` rule works predictably regardless of whether the field contains a single file or an array of files, and that validation failures correctly reject incomplete or partially-failed uploads.

Allow array file uploads to be validated also by 'uploaded'. Fixes #3018


The template parser should treat whitespace between the delimiters and the key as optional, so that `{ title }` behaves identically to `{title}`.

- Single variable replacements must match when the opening delimiter, the variable name, or the closing delimiter are surrounded by whitespace (e.g., `{ title }`, `{ title}`, `{title }`).
- The same whitespace flexibility must apply to variable pair (loop) tags: both the opening `{ powers }` and the closing `{ /powers }` should be recognized, regardless of internal spacing.
- Inside a pair loop, any placeholders for nested data must also be space‑flexible (e.g., `{invisibility}` and `{ invisibility }` both work).
- The parser must still respect custom delimiters (left and right delimiters) and apply the whitespace leniency to any delimiter configuration.
- Only spaces (and possibly tabs, newlines) immediately inside the delimiters should be ignored; the variable name itself must not contain spaces.
- The behavior must be safe when a tag appears multiple times with different spacing, and when nested pairs are present.
- Existing functionality that does not rely on exact delimiter‑key‑delimiter matching must continue to work (e.g., conditional tags, comments, noparse sections).


SQLite3 Forge Cannot Create Tables with Composite Unique Keys

When using the Forge to build a table on SQLite3, adding a unique constraint that spans multiple columns (via `addUniqueKey` or `addKey` with the `unique` flag) does not work correctly. The resulting SQL either causes a database error or does not actually enforce uniqueness across the specified columns.

**Expected Behaviour**
- Calling `$forge->createTable()` after defining a composite unique key should successfully create the table.
- The table must enforce uniqueness on the given column combination (inserting two rows with identical values for those columns must be rejected by the database).
- The unique constraint must be implemented as a separate `CREATE UNIQUE INDEX` statement that is executed after the `CREATE TABLE` statement, since SQLite3 requires this approach for composite unique keys.
- The generated index name should be safely escaped and derived from the table name and the column names (e.g., `table_col1_col2`) to avoid naming collisions.

**Current Behaviour**
- The Forge attempts to embed the unique constraint directly in the `CREATE TABLE` definition using syntax that SQLite3 does not accept for composite keys, leading to a `DatabaseException` or a table without the intended uniqueness enforcement.

**Additional Constraints**
- The implementation should only affect SQLite3; no other database drivers should be changed.
- Existing APIs (`addUniqueKey`, `addKey`) must remain usable for specifying composite unique keys.

Please override the relevant index processing logic in `system/Database/SQLite3/Forge` so that SQLite3 tables correctly enforce composite unique constraints.


We need model write operations (`insert`, `delete`, `save`) to propagate the underlying database result properly, so callers can detect failures even when `DBDebug` is off. Currently these methods often discard the result object and return only a boolean, which hides error information.

Requirements:
- `insert()` when called with `$returnID = false` must return the `BaseResult` object from the query builder, not a boolean. The caller can then check `$result->resultID` (which is `false` on failure). For the internal `insertID` tracking, use `$result->resultID` as the success check.
- `save()` must unwrap any `BaseResult` returned by `insert()` and return a simple truthy/falsy value consistent with its `bool` return type (i.e., return `true` for success in the boolean path, but still extract the truthiness without breaking the method’s contract).
- `delete()` (hard delete, no soft-delete) must return the `BaseResult` object directly; soft-delete `delete()` calls still perform an update and should continue to return a plain boolean as before. The docblock return type should reflect the mixed return (`BaseResult|boolean`).

Ensure that all paths still respect the configured `DBDebug` setting – the result object must be returned regardless, so that callers can inspect it even when exceptions are suppressed.


**Bug Report: MathLib::isBin() does not handle GCC binary literal suffixes and accepts invalid patterns**

- **Component:** `MathLib::isBin()` in `lib/mathlib.cpp`
- **Severity:** Medium – causes both false positives and false negatives when recognizing binary constants.

**Problem**
The `isBin()` function is meant to validate GCC-style binary literals (see [GCC documentation](http://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html)). Currently it:

1. **Incorrectly rejects valid suffixed literals** – any binary constant followed by the standard integer suffixes (`u`, `l`, `ul`, `lu`, `ll`, `ull`, `llu`, case-insensitive) is reported as not being a binary number. For example, `"0b101u"`, `"0B11ULL"`, or `"+0b10lu"` all return `false`.
2. **Incorrectly accepts malformed strings** – a bare prefix without digits (`"0b"`, `"0B"`), a sign with prefix but no digits (`"+0B"`), or strings where extra characters appear among the allowed set are accepted as valid binary literals.

**Expected Behavior**
A string should be accepted as a binary literal **if and only if** it matches the following pattern (flexibly):

- Optional leading sign (`+` or `-`)
- Then the literal prefix `0b` or `0B`
- At least one binary digit (`0` or `1`)
- Optionally one of the integer suffixes: `u`, `l`, `ul`, `lu`, `ll`, `ull`, `llu` (any letter case combination). Suffixes with three `l`’s (e.g., `lll`) or other permutations (e.g., `lul`) are *not* allowed.
- No other characters are permitted; the function must reject empty strings, whitespace, and trailing extraneous characters (including after a valid suffix).

The implementation must be strict: a prefix alone, a prefix with a suffix but no digit, or an otherwise unallowed character sequence should all yield `false`.

**Impact**
Callers that rely on `isBin()` to classify tokens (e.g., in the tokenizer or value parsing) may misinterpret valid binary constants as something else, leading to false syntax warnings or incorrect value computations. The relaxed acceptance of malformed patterns can also hide real input errors.

MathLib::isBin(): fix missing detection of U and L suffix combinations. Added missing test cases.


The preprocessor incorrectly expands macros that are defined in terms of other macros when the parameter names collide.

**Steps to reproduce**
Consider a file containing:
```
#define B(a) A(a)
#define A(b) (b)
B(1)
```
After preprocessing, `B(1)` should expand to `(1)`, but cppcheck currently emits something different (e.g., the expansion fails, or the output contains an unexpected token).

Another common pattern that breaks:
```
#define DBG(fmt, args...) printf(fmt, ## args)
#define D(fmt, args...) DBG(fmt, ## args)
D("hello %d", 3)
```
Here `D("hello %d", 3)` should become `printf("hello %d", 3)`, but the preprocessor mishandles the nested expansion.

**Expected behavior**
The preprocessor should follow standard C preprocessor rules for nested macro expansion. When a macro body uses another macro-like expansion, the arguments of the outer macro must be fully expanded before substitution, and the parameter names of inner macros must not accidentally capture tokens from the outer expansion.  In particular, parameter name collisions between an outer and an inner macro must not distort the result.

**Observed behavior**
The preprocessor fails to expand such nested definitions correctly, often producing garbled output or leaving macros unexpanded.

**Impact**
Many real-world code patterns, such as debug wrappers (`#define D(...) DBG(__VA_ARGS__)`), are broken.  This leads to false positives and missed warnings during analysis because the preprocessed code passed to the static analyzer is incorrect.

Fix #667 (Preprocessor does not handle macro inside macro correctly)


Title: Use token `varId` instead of variable name as key in the variable usage checker

Description:
The `functionVariableUsage()` check currently tracks variable declarations and accesses using `std::string` (the variable name) as the key in its usage map. Because the same name can be reused in nested scopes, the code maintains a chain of `VariableScope` objects and searches parent scopes. This leads to confusion between distinct variables that happen to have the same name in different blocks, causing false positives (e.g., an outer variable reported as unused when an inner variable with the same name is used) and missed warnings.

Acceptance criteria:
1. The variable usage check shall identify each variable declaration by its unique `varId` (already assigned by the tokenizer) instead of its name.
2. A flat per-function map from `unsigned int` (varId) to `VariableUsage` shall be used; nested scope objects are no longer necessary and shall be removed.
3. Whenever the check encounters a token that declares, reads, writes, or modifies a variable, it shall look up or insert the entry using `tok->varId()`.
4. At the end of the function, the checker shall inspect every recorded variable independently according to the existing rules:
   - If a variable is never read, written, or modified → report “unused variable”.
   - If a variable is modified or used without an explicit write → report “unassigned variable”.
   - If a variable is written but never read → report “assigned value that is never used”.
5. Two variables with the same name in different scopes (e.g., an outer `int i` and an inner `int i`) shall be tracked and reported independently; no interference between them.
6. The check shall not enter struct/union/class definitions as before.
7. Existing variable‑usage warnings must remain accurate and the public error‑reporting API (`unusedVariableError`, `unreadVariableError`, `unassignedVariableError`) shall behave identically except for the corrected scope behaviour.

Non‑goal:
- No change to the tokenizer’s `varId` assignment logic.
- No change to other checks that may also walk variable usage.

Variable usage: Use variable id instead of variable name to identify variable


**Title:** Extend after-condition value flow past non‑noreturn `if` and `while` blocks

**Context:**
The `ValueFlow` analysis in `lib/valueflow.cpp` already propagates known variable values into the branches of `if` conditions (via `valueFlowForward`), but it stops at the closing brace of the conditional block. In many cases the value is logically guaranteed after the entire `if`/`else` or `while` statement as well, as long as no path exits the block early via a noreturn statement and no unknown reassignment occurs.

**Desired behaviour:**
After evaluating a condition (`==`, `!=`, `>=`, `<=`) inside a top-level `if` or `while`, and after successfully applying the resulting values to the body of the conditional construct, the analysis should *also* propagate those values to the code that follows the construct, **provided**:

1. The conditional block does **not** guarantee a noreturn exit on all paths.
   Recognised noreturn statements: `return`, `continue`, `break`, and `throw`.
2. The forward analysis inside the block completed without bailout (i.e. the variable was not modified inside an inner conditional, not passed to a function, etc.).

If either condition fails, values must not be written after the construct. Additionally, the propagation should stop at the end of the enclosing function body (scope), not bleed into unrelated code.

**Scope of change:**
- The condition detection in `valueFlowAfterCondition` should recognise `while` conditions identically to `if`, ignoring macro expansions.
- `valueFlowForward` must signal whether its traversal finished cleanly (no bailout), so the caller can decide whether to continue.
- The list of noreturn tokens inside `valueFlowForward` must be extended to include `throw`.
- Track the enclosing function/scope boundary to know where to stop after the conditional.

**Non‑goals:**
- This does not add support for `for` or `switch` constructs (only `if` and `while`).
- It does not attempt to merge values from different branches; only simple cases where the condition yields a single known value are considered.

ValueFlow: extend aftercondition analysis below conditional code


**Title:** Warn when unsigned division could produce a misleading result (e.g., when a negative result is expected)

**Description:**

Cppcheck currently does not flag cases where a division operation involves an unsigned integer operand and the result is intended to be negative. In C and C++, if one operand of `/` is unsigned, both operands are converted to unsigned before the division. This means an expression like `-2 / uvar` (where `uvar` is `unsigned int`) yields a large positive value instead of a negative one, which is rarely the programmer’s intention.

**Example that should be detected:**

```cpp
void f() {
    unsigned int uvar = 2;
    int result = -2 / uvar;   // result is large positive, not -1
}
```

A similar issue can happen when the variable is on the left side (`uvar / some_signed_value`).

**Expected behavior:**

A new check (e.g., named `CheckUnsignedDivision` or similar) should be added to the `CheckOther` family that:

1. Finds local variable declarations of the form `unsigned int varname;` (or `unsigned <type> varname;`).
2. Searches for `/ varname` or `varname / ...` after that declaration.
3. Emits a warning when such a division is found, indicating that the result may be wrong if it is expected to be negative because one operand is unsigned. The warning message can be similar to:
   `"If the result is negative it will be wrong because an operand is unsigned."`
4. Avoids false positives for simple casts: when the left side of the division appears to be a cast (e.g., `(int)/varname`), it should not warn, as the cast may be intentional. A basic heuristic would be to skip cases where the token immediately before the `/` is a `)`.

**Impact:**

This can hide serious logic bugs in embedded and systems code that performs arithmetic on mixed signed/unsigned types. An automated warning would help developers catch such mistakes early.

**Additional notes (if helpful):**

- The check should be executed *before* any token simplification that removes the `unsigned` keyword, since it needs to see the original `unsigned` declaration to identify the variable’s type.
- It does not need to be perfectly accurate in the first implementation – even a basic pattern-based detection (like looking for `unsigned int <name>` followed by a division using that name) would be useful. Refinements can follow later.

---
**Labels:** enhancement, type:warning, area:other-checks

CheckUnsignedDivision: Added check. Not very accurate yet.


The clang AST dump importer currently does not handle `ForStmt` nodes, so any translation unit containing a `for` loop is not tokenized correctly or causes a parse failure.  The importer must be extended to recognise `ForStmt` and produce the corresponding `for`-loop syntax, including:

- The keyword `for`, balanced parentheses, and the two separating semicolons.
- The initialisation part, which may be a `DeclStmt` containing a `VarDecl` with an optional initialiser.
- The condition and increment expressions (each may be absent, represented by a `<<<NULL>>>` child).
- The body statement, which must become a child scope of type `eFor`.

The semantics of `VarDecl` and `FieldDecl` tokenisation must be adjusted so that the terminating semicolon is not unconditionally emitted inside the declaration.  For top-level declarations and `RecordDecl` members the semicolon should be added automatically (as currently), but when a declaration appears as the init statement of a `for` loop no extra semicolon should be generated – the semicolon that separates the init from the condition belongs to the `ForStmt` itself.

The expected output for a minimal for-loop such as:
```
for (int i = 0; i < 10; ++i) {}
```
is a token list that includes the full `for` token sequence, with AST links connecting the `for` keyword, the parenthesised sub-expressions, and the body scope.  The nested `VarDecl` must introduce the variable into the correct scope and the `DeclRefExpr` references inside condition and increment must be linked back to it.


Refactor `CheckBufferOverrun::checkScope` to use variable IDs instead of variable name strings when a concrete variable is available. Currently the function matches the variable by name, which can produce false positives when different scopes contain variables with the same name. The check already obtains the variable ID (`varid`), so switch to ID-based matching where possible.

**Implementation details:**

- Make `checkScope` handle a null `varname` pointer gracefully (skip string-based matches when `varname` is null).
- For each existing name-based `Token::Match` call that looks for the variable (array-index checks, `strcpy`/`strcat` arguments, `[(,] varnames [,)]` position search), first test `varid > 0` and use a pattern with `%varid%` and the `varid` parameter. Only fall back to the old string-based match when `varid == 0`.
- In `checkGlobalAndLocalVariable`, stop populating the `varname` array; simply pass `0` for the `varname` argument and rely on the `varid` parameter.
- As a separate improvement to document missing functionality, add a new test function that checks multi-dimensional array index out-of-bounds (e.g., `a[2][1]`, `a[1][2]` for `char a[2][2]`) and mark the expectations with `TODO_ASSERT_EQUALS` since they are not yet detected.

Modify CheckBufferOverrun::checkGlobalAndLocalVariable() to use varid only.


We need ValueFlow to propagate constant values through array- and string-element accesses that use a compile‑time‑constant array and index.

## Desired Behavior
1) **Constant array of integers**
   Given a `const` array of integers initialized with a brace‑enclosed list, and a read of an element using a constant index within bounds, ValueFlow shall assign the resulting integer value to the expression `array[index]`.
   Example:
   ```c
   const int a[] = {43, 23, 12};
   int x = a[0];   // x should be tracked as 43
   int y = a[1];   // y should be 23
   ```

2) **Constant array of characters initialized with a string literal**
   When a `const char` array is initialized with a string literal, indexing with a constant, in‑bounds index shall yield the corresponding character value (as an integer).
   Example:
   ```c
   const char abcd[] = "abcd";
   int x = abcd[2];   // x should be 'c'
   ```

3) **Direct string literal indexing (optional/logical extension)**
   The same propagation should work when a string literal is indexed directly (e.g., `"hello"[1]`), though the primary target is named arrays.

4) **Integration with existing ValueFlow**
   The derived integer value shall be usable by downstream value‑flow analysis—for instance, in arithmetic expressions (`a[0] + 1`), conditions (`if (a[0] == 0)`), and assignments to variables.

## Constraints & Safety
- **Index must be known and in‑bounds**
  The index must be a compile‑time‑known integer value (already tracked by ValueFlow).
  If the index is out of the array bounds (e.g., negative or past the last element), **no value shall be produced**; the analysis must remain silent rather than inventing a value.

- **Array must be `const`**
  Only arrays declared `const` with a complete, analyzable initializer (brace‑enclosed list or string literal) are considered. Non‑`const` arrays, or arrays whose initializer cannot be fully resolved, must not trigger this analysis.

- **No false positives**
  The analysis must not assign values when:
  - The index is not a known constant (e.g., a runtime variable).
  - The initializer contains runtime elements (e.g., function calls).
  - The array is accessed through a non‑constant alias, pointer, or reference that could have been modified.

- **Handling of array‑to‑pointer decay**
  If the constant array is referred to by a variable that ValueFlow already tracks as having the array’s tokvalue, the propagation should work through that variable as if the array were used directly.

## External Observables
Users will notice that cppcheck can now:
- Prove more conditions true/false when they depend on array elements.
- Detect more dead code or unreachable branches guarded by such conditions.
- Report additional style/warning issues that rely on known values.

This request does not prescribe exact implementation mechanisms (e.g., a new `valueFlowArray` function or changes to `setTokenValue`) but the acceptance is purely behavioral: the above examples should work, and no regressions should appear for out‑of‑bounds or non‑const arrays.

ValueFlow: Improved when using '[' on arrays and strings


## Null-pointer dereference detection for standard functions: extend coverage and fix printf argument tracking

**Desired behavior**
The null-pointer dereference checker should flag cases where a null constant (or a variable that is provably null) is passed to a standard library function that will dereference that argument. The following concrete improvements are required:

### 1. Support `frexp` and `modf`
Both functions write through their second parameter (a `double *` or `float *`). Passing a null pointer (e.g., `frexp(1.0, 0)`) must be reported as a null-pointer dereference error. These functions do *not* need to also flag uninitialized data; only a concrete null value is an error here.

### 2. Robust printf/scanf format-string argument tracking
The existing logic already checks `printf`, `sprintf`, `fprintf`, `scanf`, etc. for null arguments matching `%s` and `%n` conversion specifiers. However, the argument-to-specifier mapping is currently fragile when the format string contains:

- Width or precision specifiers that consume an argument: `*` (e.g., `%*s`, `%*.*d`).
  The tool should correctly skip over the argument(s) corresponding to `*` so that a following null pointer for `%s` is still detected.
  *Example*: `printf("%*s", 5, 0)` must report a null-pointer dereference for the third argument.

- The glibc-specific `%m` specifier (writes `strerror(errno)`, consumes *no* argument).
  The tool must ignore `%m` when advancing through the argument list, so that a null pointer supplied for a later `%s` is not missed.
  *Example*: `printf("%m%s", 0)` must report a null-pointer dereference for the second argument (the argument for `%s`).

- Any combination of the above must work correctly, with multiple `*` specifiers each consuming one argument and `%m` consuming none.

### 3. Consistency and maintainability (non-functional)
The set of functions that are only flagged for a null pointer (as opposed to also being flagged for uninitialized data) should be clearly separated from the “always dereference first/second argument” lists. This makes the intent obvious and avoids accidental false positives when the checker is later extended.

### Constraints
- The detection must work for both literal `0` and for variables known to be null at that point (according to existing flow analysis).
- Existing correct warnings for standard functions (`memcpy`, `strcpy`, `printf`, `scanf`, etc.) must remain unchanged.
- Functions like `snprintf` with a non‑zero size argument must still be handled correctly (the zero‑size case is already special‑cased and need not change).

**Acceptance criteria (examples)**

- `frexp(1.0, p)` with `p = 0;` earlier → error.
- `modf(1.0, 0)` → error.
- `printf("%*s", 10, (char*)0)` → error (null dereference for `%s`).
- `printf("%m%s", (char*)0)` → error (null dereference for `%s`).
- `printf("%*.*f", 5, 2, 0.0)` → no null-pointer warning (no pointer argument).
- `sprintf(buf, "%s", 0)` → error (existing behavior must not regress).

Improvements to Nullpointer dereference on function call check:


Fix false positive buffer-overrun warnings for `strncpy`: the check currently warns when the size argument equals the destination buffer size (e.g. `strncpy(buf, src, sizeof(buf))`), but a copy of up to that many characters does not overflow the buffer.  Only report an overrun if the size argument is strictly greater than the total allocated size of the destination.  Ensure this works correctly for all call shapes (`strncpy(dst, src, n)` and `strncpy(dst, n, ...)`? — but the standard form uses the first argument as destination).  Also make sure the size extraction logic still works for the other functions (`memset`, `memcpy`, `memmove`, `memcmp`, `fgets`) without introducing regressions.  The fix should handle both first- and second-argument destination cases where applicable.

Fix #730 (False positive, buffer overrun with strncpy)


When a C/C++ function declaration is imported from a Clang AST dump, the resulting `SymbolDatabase` must faithfully represent each function parameter so that downstream checks (type analysis, value flow, and warnings that depend on argument types) work correctly.

**Acceptance Criteria**

1. For every `ParmVarDecl` child of a `FunctionDecl` in the AST dump, a `Variable` object shall be created and appended to the function’s argument list.
2. Each argument `Variable` shall have an index that matches its position in the parameter list (0-based, in declaration order).
3. If the parameter has a name (i.e., the dump includes a spelling such as `x`), a token for that name shall be generated and the `Variable`’s `nameToken()` shall point to it. The type tokens (e.g., `int`) shall appear before the name token in the token list. The `Variable` shall be associated with this name token.
4. The argument’s type, as extracted from the AST dump (e.g., `int`), shall be converted into the `Variable`’s value-type or type information, making it available through its `valueType()` (or equivalent type accessor). For a parameter declared as `int`, the variable must report a type of `INT`.
5. Unnamed parameters (those without a spelling) must still be added as `Variable` entries with the correct index and type information. In that case, `nameToken()` shall return `nullptr`, and no name token shall be added to the token list, but the type tokens shall still be emitted so that the token stream reflects the function’s signature.
6. The import must not introduce duplicate argument separators or break the token stream structure—commas between parameters shall be inserted only where needed.

If these criteria are met, the `SymbolDatabase` produced by `clangimport` will correctly model function arguments for both named and unnamed parameters, enabling accurate subsequent static analysis.

Clang import; Fixed function arguments in SymbolDatabase


**Title:** `CheckClass::initializeVarList` falsely assumes unknown external calls initialize all members

**Background**
In `CheckClass::initializeVarList`, when processing constructor bodies, calls to functions whose definitions are not available in the same translation unit are treated conservatively: all member variables are marked as initialized, which suppresses the “Member variable not initialized” warning. While this is reasonable for unknown *member* functions (including `friend`s and inherited virtuals) that may directly touch members, it is incorrect for calls to truly *external* (non-member) free functions that cannot implicitly initialize class members.

**Request**
Refine the fallback logic so that:

1. **For external non-member functions** that are not‑members, not‑`friend`s, and not inherited virtuals, Cppcheck must *not* assume all members are initialized. Instead, only those member variables that appear as arguments in the call should be considered potentially initialized (they could be modified through pointers/references). Members that are not passed to the call should remain flagged as uninitialized.

2. **For unknown functions that are members, `friend`s, or inherited virtuals** the existing behavior shall be kept: all member variables are assumed to be initialized (because the function has access to the class internals and could assign them).

**Context and constraints**
- The decision whether a function is a member of the class should be based on the class body at file scope (ignoring nested scopes like other function bodies). The presence of a `friend` declaration or a matching function name in the class body (declaration line) should mark it as a member‑like candidate, keeping the no‑warning behavior.
- The existing logic that resolves calls to inherited virtual functions higher up the call stack should continue to work; this fallback should only trigger for calls that are definitely external, not for functions that might be resolved through inheritance.

**Expected observable behavior**
```cpp
class Fred {
public:
  Fred() { Init(); }   // Init is a free function, not a member
private:
  int i;
};
```
should now produce: `Member variable not initialized in the constructor 'Fred::i'`

While:
```cpp
class Fred : private ABC {
public:
  Fred() { Init(); }  // Init could be a virtual function inherited from ABC
private:
  int i;
};
```
and
```cpp
class Fred {
  friend ABC;
  …
  Fred() { Init(); } // friend function may access internals
};
```
should remain silent (no warning).

Fixed #643 (Member variable not initialized not detected if any unknown function called)


Cppcheck crashes with a segmentation fault when analyzing a `switch` statement that appears inside a `while` loop inside a `try` block, when a case label contains a local variable declaration.

**Code that triggers the crash**

```cpp
void a() try
{
    {
        while (1) {
            switch (1) {
                case 1: {
                    int i;
                }
            }
        }
    } catch (...) {
    }
}
```

**Expected behavior**
Cppcheck should not crash. Analysis should complete normally and may report findings as usual, but there must be no segmentation fault.

**Notes**
The crash occurs inside `ExecutionPath::bailOutVar`. The problem was likely introduced while fixing another issue related to execution path handling.

Fixed #1725 (segfault in ExecutionPath::bailOutVar (probably caused by #1721 fix))


**Title:** http-client: ioloop panic ("No IOs or timeouts set") after reconnection failure with disconnected current peer

**Background**
The HTTP client maintains a queue of requests per peer address. When a connection succeeds, the peer becomes `queue->cur_peer`. Subsequent connection failures for the same peer are handled in `http_client_queue_connection_failure()`. The current logic checks whether `cur_peer` is set, and if so, asserts that `pending_peers` is empty and returns immediately, assuming the peer still has working connections that will service pending requests.

**Problem**
Under high connection churn, the original successful connection can drop, leaving `cur_peer` non-NULL but no longer connected. When a new connection to that same peer then fails, the function hits the early-return path because `cur_peer` is still set. No further action is taken: the failed peer is not unlinked, and no alternative peer (next IP address) is tried. If the queue has pending requests but no remaining I/O or timeout sources, the ioloop panics with:

```
Panic: file ioloop-epoll.c: line 189 (io_loop_handler_run_internal): assertion failed: (msecs >= 0)
Panic: BUG: No IOs or timeouts set. Not waiting for infinity.
```

**Expected behavior**
When a connection failure occurs and the queue’s `cur_peer` is no longer connected, the function must treat it as a peer failure: unlink the peer from the queue, clear `cur_peer`, and proceed with the normal peer-failure handling (which may attempt the next resolved IP address, or fail the entire queue if no addresses remain). The invariant that the event loop always has pending work when requests are outstanding must be preserved.

**Constraints**
- The fix must be localized to `http_client_queue_connection_failure()` in `src/lib-http/http-client-queue.c`.
- The existing logic for a *connected* `cur_peer` (where the peer still has live connections) must remain unchanged.
- When `cur_peer` is present but disconnected, the function must fall through to the same code path used when `cur_peer` is NULL, after unlinking the failed peer.
- No other assumptions about the state of the queue should be violated. The assertion that `pending_peers` is empty when `cur_peer` is set is valid only if the peer is still connected.

lib-http: client: Fixed peer reconnection failure handling.


### Bug: Crash when extracting filename from attachment parts without a filename or name parameter

**Observed behavior**
When a MIME part has `Content-Disposition: attachment` but neither a `filename` nor a `name` parameter, any operation that retrieves the part’s filename (e.g. `SEARCH MIMEPART FILENAME`, IMAP `FETCH BODYSTRUCTURE`, Sieve `filename` extensions) causes a crash because the internal filename pointer is left `NULL`.

**Expected behavior**
The system should never crash on missing filename parameters. For any part that is identified as an attachment, the filename extraction must always return a valid string. If no explicit filename or name parameter is present, the result should be an empty string (`""`) rather than `NULL`. This ensures that all callers can safely use the returned value without special-casing `NULL`.

**Acceptance criteria**

1. **Crash-free attachment without filename**
   Given a message containing a `Content-Disposition: attachment` part that has no `filename` or `name` parameter, performing a filename lookup (via any API or protocol command) does not crash.

2. **Empty string returned for missing filename**
   In the above scenario, the filename value returned to the caller is an empty string – never `NULL`. This applies regardless of whether the lookup considers the part an attachment based on disposition or other heuristics.

3. **Existing filename retrieval continues to work**
   Parts that do have a `filename` or `name` parameter continue to return the correct filename without change.

4. **No caller-side NULL-handling required**
   After the fix, callers can rely on the filename always being a valid `const char *` (empty or populated) when the part is treated as an attachment, so they no longer need to guard against `NULL`.

**Non‑obvious constraints**

- The fix must cover cases where the filename is absent both from `Content-Disposition` and `Content-Type` (e.g., `name` parameter), and only the disposition marks the part as an attachment.
- The change must not alter the filename extraction for inline parts or other non‑attachment contexts unless they are also susceptible to the same crash. If a similar `NULL`-filename situation can occur in non-attachment paths, those should be made safe as well, returning an empty string instead of `NULL`.

lib-mail: Fix potential crash with SEARCH MIMEPART FILENAME


```
Title: Fix signed int overflow in IMAP parser escape tracking

The IMAP parser stores the byte offset of the first backslash escape in a
quoted string in a signed int field (`str_first_escape`). This offset is
never used as a position; the only check is `>=0` to determine whether
unescaping is necessary. With a large `max_line_size` (e.g., > 32K), the
offset can exceed `INT_MAX`, leading to truncation and sign wrap, which
could incorrectly skip unescaping.

Change the field to a boolean flag that indicates whether any escape was
seen, and update all references accordingly. Also add test coverage for
quoted-string parsing with escapes (no escape, escaped quote, escaped
backslash, multiple escapes) to confirm unescaping is still performed
correctly.
```

lib-imap: imap-parser - Track quoted-string escape with a bool


Ensure `rfc822_parse_content_type` rejects malformed content-type values and leaves the output buffer unchanged on error.

Problems:
- The parser currently accepts empty type, empty subtype, missing `/`, or extraneous characters after the subtype (e.g. `text/plain/format`).
- On failure, partially parsed text may remain in the output string.

Required behavior:
- Return -1 for any input that does not match `type "/" subtype`, where `type` and `subtype` are each at least one `mime-token` character (no embedded NULs).
- After the subtype, the next character must be either `;` or end-of-input; anything else (including another `/`) is an error.
- Leading whitespace/comments around the tokens should be skipped as before, but must still result in non-empty tokens.
- On any failure, truncate the output string back to its length before the call.

lib-mail: rfc822-parser - Fix content-type parser to accept only valid values


**Feature request: Strict HTTP parsing must reject invalid `Date` headers**

When parsing an HTTP request or response with the strict flag enabled (`HTTP_REQUEST_PARSE_FLAG_STRICT` / `HTTP_RESPONSE_PARSE_FLAG_STRICT`), the library should validate the `Date` header value and treat a value that cannot be parsed as an HTTP-date (per RFC 7231’s IMF-fixdate format) as a fatal parse error.

*Acceptance criteria*

1. **Strict mode – requests**
   - Given an HTTP request with a `Date` header whose value is syntactically invalid (e.g., a misspelled month abbreviation like `"Mon, 09 Kul 2018 02:24:29 GMT"`), parsing with `HTTP_REQUEST_PARSE_FLAG_STRICT` must fail immediately.
   - The error returned must be `HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST` with a description that identifies the `Date` header as the cause (e.g., `"Invalid Date header"`).

2. **Strict mode – responses**
   - When parsing an HTTP response with `HTTP_RESPONSE_PARSE_FLAG_STRICT` and the response contains an invalid `Date` header (e.g., `"Sun, 07 Ocu 2012 19:52:03 GMT"`), the parser must fail.
   - The error must be the equivalent “broken response” code (`HTTP_RESPONSE_PARSE_ERROR_BROKEN_RESPONSE`) and an appropriate message.

3. **Lenient mode (no strict flag)**
   - Without the strict flag, an unparseable `Date` header must *not* cause a failure.
   - The existing behaviour is preserved: the header is silently ignored, the `date` field of the parsed message may be left unset (or set to `0`), and parsing of the remaining headers and body proceeds normally.

4. **Duplicates and unrelated headers**
   - The validation must apply to the first `Date` header value encountered (or, if there are duplicates, to the one that the parser normally uses).
   - Invalid `Date` must not interfere with detection of other problems (e.g., duplicate headers or missing required headers).
   - When a valid `Date` header is followed later by a duplicate, the strict-check must not reject the message solely because of the duplicate, if duplicates are otherwise allowed.

5. **Observable error path**
   - The parsing functions that return a status code (`http_request_parse_next`, `http_response_parse_next`) must set the error code and error string to the values described above, so callers can distinguish an invalid `Date` from other broken-message conditions.

The implementation should use the existing `http_date_parse()` function to validate the value; no new date-parsing logic is required.

lib-http: message parser: Reject messages with invalid Date header when HTTP_MESSAGE_PARSE_FLAG_STRICT flag is enabled.


The normalization functions (NFD, NFC, NFKC, NFKD) that enforce the UAX15-D4 Stream-Safe Text Process crash on input strings containing long sequences of composed combining characters whose canonical decompositions yield more than one non‑starter. The crash is an assertion failure in `unicode_nf_cp` with `ctx->pending_decomp == 0`.

**External behavior**

- **Expected:** Normalization of any valid Unicode string—including those with rare composed non‑starters like U+0344 COMBINING GREEK DIALYTIKA TONOS—should succeed. The output for NFD and similar normalizations that insert U+034F COMBINING GRAPHEME JOINER (CGJ) to break long non‑starter sequences must:
  - never crash inside the normalization code;
  - insert CGJ at the correct boundary (after every 30 non‑starters, counting decomposed code points);
  - correctly track the running non‑starter count after CGJ insertion, so that subsequent non‑starters are counted from the reset point rather than zeroed entirely, and the count continues to reflect only trailing non‑starters if a starter appeared earlier.

- **Observed:** A panic (assertion failure) occurs when processing a string that contains a sequence of many composed combining marks that each decompose into multiple non‑starters. This happens because the internal buffer can overflow twice without being fully drained, and a second overflow incorrectly assumes no pending decomposition is already waiting. After the crash is fixed, the CGJ insertion position may still be wrong because the non‑starter counter is reset to zero instead of being set to the number of trailing non‑starters after the CGJ.

**Constraints**

- The fix must not alter the established stream‑safe invariant: no sequence of more than 30 non‑starter code points appears without an intervening CGJ.
- It must correctly handle canonical decompositions of any length (up to `UNICODE_DECOMPOSITION_MAX_LENGTH`), including those that expand a single code point into multiple non‑starters.
- The output must remain identical for input that does **not** contain composed non‑starters (i.e., the common case).

lib: unicode-transform - Fix panic caused by Stream-Safe Text Process encountering composed non-starters


**Bug Report: `o_stream_send_istream()` assertion failure and offset corruption when used with `iostream-temp` and `IOSTREAM_TEMP_FLAG_TRY_FD_DUP`**

**Summary:**
When an input stream is sent to a temporary output stream created via `iostream_temp_create_sized` with the `IOSTREAM_TEMP_FLAG_TRY_FD_DUP` flag, the operation can hit an assertion inside `o_stream_send_istream()` complaining that the input stream hasn’t reached EOF. Additionally, the output stream’s offset is not updated correctly, which leads to data being written at the wrong position when the fd‑dup attempt fails and the stream falls back to an in‑memory buffer.

**Steps to reproduce (conceptual):**
1. Open a file‑backed input stream that contains some data.
2. Create a temporary output stream with `iostream_temp_create_sized()` using a path prefix that will cause the fd‑dup attempt to fail (e.g., a non‑existent directory) and enable `IOSTREAM_TEMP_FLAG_TRY_FD_DUP`.
3. Call `o_stream_send_istream()` on that output stream using the input stream from step 1.
4. (Optional) Write additional data or send another input stream to the same output stream.

**Observed behavior:**
- An assertion failure is triggered inside `o_stream_send_istream()`: *“eof isn’t set”*, even though the entire input stream has been read.
- The output stream’s `offset` field does not reflect the amount of data actually transferred; it often remains zero or is set to a value that does not match the input stream’s size.
- When the fd‑dup fallback path is taken (e.g., `o_stream_temp_dup_cancel` is invoked), the output stream is corrupted or only partially contains the expected data, because the intermediate duplicate stream is not properly reset and its reference is released too late.

**Expected behavior:**
- Sending an istream that has been fully read should succeed without any assertion failure, even when `IOSTREAM_TEMP_FLAG_TRY_FD_DUP` is used and the fd‑dup attempt fails.
- The output stream’s offset must be updated to reflect the total number of bytes written, matching the position that subsequent writes would target.
- The temporary stream must correctly copy all data from the duplicate istream when falling back from fd‑dup to a memory buffer, preserving the order and integrity of the data.

**Impact:**
Code that relies on `iostream-temp` with `TRY_FD_DUP` to lazily switch to a temporary file or memory buffer receives corrupted data or crashes due to the assertion failure. This makes the fd‑dup optimisation unsafe in practice when `o_stream_send_istream()` is involved, forcing callers to either disable the flag or avoid sending istreams entirely.

lib: iostream-temp: Fixed o_stream_send_istream()


Fix infinite loop in `path_normalize()` when a symlink target is truncated due to a buffer that is exactly the size of the link content. After `readlink()`, if `ret == lsize`, the target may have been truncated; the function must reallocate a larger link buffer and retry, otherwise it may loop endlessly when the original resize condition is not met because the accumulated path length alone does not trigger a reallocation.


Currently, when the mail-cache write buffer fills and triggers an intermediate flush (not the final commit), the cache record offsets are only written to the transaction log when the outer index transaction eventually commits.  For long‑lived transactions this means cached data for already‑committed messages remains invisible to other processes for an arbitrary duration.

Change the intermediate flush path so that, when the outer transaction holds **no** newly appended, not‑yet‑committed messages, the just‑flushed cache offsets are committed immediately via a separate internal transaction.  This makes the freshly cached fields visible to other readers without waiting for the outer transaction to finish.

If the outer transaction *has* added new (uncommitted) messages, the offsets must still be deferred, because those message sequences are not yet visible to other processes.

Ensure the early commit is safe with respect to outer‑transaction rollback: an early‑committed cache offset belongs to a message that already exists in the mailbox and will not be invalidated by a subsequent rollback of flag updates or other metadata.

lib-index: When flushing cache changes to disk, try to also write offsets to transaction log


Subject: SIGFPE / division-by-zero crash on corrupted index with small record_size (mmap path)

The `dovecot.index` header field `record_size` may be corrupted (e.g. zero).
When the index file is large enough to trigger the mmap-based access strategy, a `record_size` smaller than `sizeof(struct mail_index_record)` causes a division by zero and crashes the process. The code in `mail_index_check_header_compat()` already validates several other header fields (e.g. `base_header_size`, `header_size`) but completely omits `record_size`.

**Expected behavior**
Opening an index with a corrupted `record_size` must be rejected gracefully with an error message instead of crashing. The fix should extend `mail_index_check_header_compat()` to treat any `record_size < sizeof(struct mail_index_record)` as a fatal corruption and return an appropriate error (e.g. “record_size too small”).

**Impact**
A single bit-flip or a deliberately crafted index file can take down the entire process with SIGFPE, no panic backtrace, and no automatic recovery. This affects all Dovecot installations where the index may be stored on unstable media or is exposed to potential corruption.

**Reproduction sketch**
1. Create/obtain a `dovecot.index` large enough to be mmapped (`>= MAIL_INDEX_MMAP_MIN_SIZE`).
2. Overwrite the `record_size` field in the header with `0` (or any value below `sizeof(struct mail_index_record)`).
3. Open the index → immediate crash (division by zero).

lib-index: mail_index_check_header_compat() - Guard from divisions errors due to corrupted record_size


**Subject:** HTTP Message Parser Should Reject Messages with Both Transfer-Encoding and Content-Length

**Severity:** Minor (Security Improvement)

**Component:** `src/lib-http/http-message-parser.c`

**Description:**

RFC 7230, Section 3.3.3 states:

> If a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling (Section 9.5) or response splitting (Section 9.4) and ought to be handled as an error. A sender MUST remove the received Content-Length field prior to forwarding such a message downstream.

Currently, in `http_message_parse_body_encoded()`, when both headers are present, the code silently deletes the `Content-Length` header and proceeds with the Transfer-Encoding, as indicated by an existing `// FIXME: make this an error?` comment. This lenient handling could allow request smuggling attacks to pass through the parser.

**Expected Behavior:**
The parser should treat this condition as a broken message. Instead of removing the `Content-Length` header and continuing, it should:

- Set `parser->error_code` to `HTTP_MESSAGE_PARSE_ERROR_BROKEN_MESSAGE`.
- Set an appropriate error message (e.g., `"Message has both a Transfer-Encoding and a Content-Length header"`).
- Return `-1` immediately, before processing any Transfer-Encoding codings.

This would cause the HTTP server to reject such malformed requests with a 400 Bad Request response, aligning with the RFC’s security recommendation.

**Impact:**
Closes a potential request smuggling vector in the Dovecot HTTP library.

lib-http: http-message-parser - Fail parsing messages containing both a Content-Length and a Transfer-Encoding header


Move host, port and protocol into `dsasl_client_settings` as standard settings available to all mechanisms.
Currently only `OAUTHBEARER` uses host/port, but through a custom `set_parameter` callback that stores them in mechanism-private fields. Eliminate that callback, store the values directly in the per-client `set` structure, and read them from there in the OAUTHBEARER output logic.
When constructing the OAUTHBEARER initial response, validate that a non-NULL host string contains only characters permitted by `sasl_oauth2_kvpair_check_value`; fail the output step with an error if it contains unsupported characters.
The standard port field must be an `in_port_t` (0 means unset), handled by callers.

lib-sasl: dsasl-client - Add protocol, host and port as normal settings


Two bugs affect server metric updates and HTTP/2 push flushing when handlers run on a non-event-loop thread (e.g., a worker thread). Fix both.

1. When `request.routed(route)` is called from a non-event-loop thread, the route notification to metrics is lost, causing missing route information in server metric reports. Ensure route information is reliably propagated to metrics regardless of calling thread.

2. When `response.push(method, authority, path, headers)` is completed from a non-event-loop thread, the generated HTTP/2 PUSH_PROMISE frame may not be flushed immediately. It can wait indefinitely until the next write or flush, causing the pushed response to never be sent. Ensure the push promise is flushed promptly.

Review related stream accounting (`bytesRead`, `endWritten`) and write ordering across `writeHeaders`/`writeData`/`end` to ensure they remain correct when writes occur from non-event-loop contexts. The fixes should maintain existing behavior for event-loop threads and correctly defer or adapt work for non-event-loop threads.

Fix a few bugs related to non event-loop thread writes.


### Request: Context close should allow task execution

**Problem**
`Context#close()` currently prevents any further task execution: it discards all pending tasks from the queue, and any task submitted after close is rejected with a `RejectedExecutionException`. This makes it impossible to run cleanup logic after closing a context – for example, draining HTTP back-pressure or running final timers – because those tasks are simply refused.

**Desired behaviour**
A closed context should still execute tasks. Specifically:

- Pending tasks already queued but not yet executing must **not** be rejected or removed; they should execute normally.
- Tasks submitted after `close()` must be accepted and executed, not rejected.
- Suspended threads (such as those blocked waiting on a continuation) should still be interrupted as before.
- The shutdown sequence for worker task queues must not reject pending worker tasks; they should be allowed to run.

**Non-goals**
- Do not change the handling of suspended continuations or the interruption of threads that are already blocked inside a task.
- Do not change the context lifecycle guarantees otherwise (close should still be idempotent, final).


As a user deploying verticles programmatically, I need the ability to supply a dedicated `ClassLoader` per deployment through `DeploymentOptions`. When I set a custom class loader on the options, the verticle factory must receive that class loader when creating the verticle, and the resulting verticle's context must use it as the context class loader for all task executions. If no class loader is explicitly provided, the existing class loader resolution must remain unaffected: first, the isolation-group mechanism from `DeploymentOptions` should be consulted; then, a fallback to the current thread’s context class loader. This behavior must be consistent for both direct deployment of a `Callable<Verticle>` and identifier-based deployment (which goes through a `VerticleFactory`). The feature must also be supported equally on the Java 8 and Java 11 `DeploymentOptions` variants.

Verticle deployment class loader - fixes #3606


We need a natural, maintainer-style ticket describing the bug fix for `VertxConnection.doResume()` when a channel read is in progress and the transport (specifically io_uring) allows event loop tasks to run during the read. The ticket should highlight the broken assertion, the premature flush of writes, and the desired behavior: drain pending messages without ending the read if the read was already in progress, leaving the actual `endReadAndFlush()` to the ongoing `channelReadComplete` handler.

Below is the ticket as it might have appeared before the fix.

---

**Title:** VertxConnection resume() assertion failure and premature write flush when read is in progress

**Background:**
With io_uring transport, event loop tasks can be executed while a channel read is still in progress. This means code that assumes reads and resumes are mutually exclusive (e.g., `assert !read` when `pending != null`) is incorrect.

**Symptom:**
A connection is paused, a read event arrives and is queued into the `pending` list. Before the read completes, the application calls `resume()`. Currently this either:
- Trips an assertion (`assert !read`) when running with assertions enabled, or
- If the assertion is suppressed, calls `endReadAndFlush()` inside `doResume()`, which prematurely flushes any deferred writes that were supposed to be batched until after `channelReadComplete`.

**Expected behavior:**
When `resume()` is called while a read is already in progress:
- The pending messages must still be drained (so the handler sees them immediately), but
- The `endReadAndFlush()` call must be delayed until the read fully completes, i.e., until `channelReadComplete` is fired. Only the original read lifecycle should trigger flush; `doResume()` must not take over that role if it didn’t start the read.
- After draining, the auto-read state should be restored (if the channel was paused, auto-read was off; after resume it may need to be turned back on), but again without flushing.
- Writes issued after the pause and before the resume must remain buffered until the read completes, and then be flushed in the correct order.

Additionally, the code should guard against processing when `pending` is non-empty (e.g., `pending != null && !pending.isEmpty()`), and remove the invalid assertion.

**Edge cases:**
- If `pending` is `null` or empty, `doResume()` is effectively a no-op regarding message processing; only the `paused` flag needs updating.
- If `doResume()` is called multiple times, only the first call that actually drains pending should alter read state.
- The `autoRead` flag management must remain correct when the channel resume happens without triggering a flush.

**Acceptance criteria:**
- No assertion failure when `resume()` is called during an ongoing read.
- A write performed after `pause()` is not flushed until the read is complete (i.e., `outbound` remains empty until `channelReadComplete` is delivered).
- The pending read event is delivered to the connection’s handler immediately upon `resume()`.
- Existing non-io_uring transport behavior is unaffected.

Fix VertxConnection resume when there is a read in progress bug.


When creating a DNS client with an IPv6 server address such as `::1`, a `NumberFormatException` is thrown. The DNS server address parser must treat the *last* colon as the separator between the host and the port, so that IPv6 addresses (which contain colons) are handled correctly. No exception should be thrown for valid IPv6 DNS server addresses.

Make it so that an IPv6 address like `::1` can be used as a DNS server, with or without an explicit port.

NumberFormatException thrown when creating DNS Client with IPv6 address (#5753)