Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
* [Overview](overview.md)
# Joomla Filter Package — Documentation

`InputFilter` sanitises incoming data and backs every `Joomla\Input\Input::get()` call;
`OutputFilter` prepares strings for output.

## Guide

* [Overview](overview.md) — the filter types, HTML filtering, `OutputFilter`, and what to use instead where

## Upgrading

* [Updating from v1 to v2](v1-to-v2-update.md)
* [Updating from v2 to v3](v2-to-v3-update.md)
* [Updating from v3 to v4](v3-to-v4-update.md)
* [Updating from v2 to v3](v2-to-v3-update.md) — PHP 8.1, PSR-12, no API changes
* [Updating from v3 to v4](v3-to-v4-update.md) — PHP 8.3, `InputFilter::decode()` removed
165 changes: 163 additions & 2 deletions docs/overview.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,164 @@
# Filter package
# Overview

TODO
The Filter package cleans data. `InputFilter` sanitises values coming **in** — it backs every
`Joomla\Input\Input::get()` call — and `OutputFilter` prepares strings going **out**.

```bash
composer require joomla/filter
```

## `InputFilter`

### Filtering by type

`clean($source, $type)` returns the value coerced and cleaned according to `$type`:

```php
use Joomla\Filter\InputFilter;

$filter = new InputFilter();

$filter->clean($value, 'int'); // first integer found, or 0
$filter->clean($value, 'uint'); // as above, absolute
$filter->clean($value, 'float'); // first float found, or 0.0
$filter->clean($value, 'bool'); // (bool) cast
$filter->clean($value, 'word'); // A-Z and underscore only
$filter->clean($value, 'alnum'); // A-Z and 0-9 only
$filter->clean($value, 'cmd'); // A-Z, 0-9, _ . - with leading dots stripped
$filter->clean($value, 'base64'); // base64 alphabet
$filter->clean($value, 'string'); // entity-decoded, then tags stripped
$filter->clean($value, 'html'); // tags stripped, entities left alone
$filter->clean($value, 'path'); // a restricted file path, or ''
$filter->clean($value, 'trim'); // trimmed, including non-breaking spaces
$filter->clean($value, 'array'); // (array) cast
$filter->clean($value, 'raw'); // returned unchanged
```

An unknown type falls through to the `string` behaviour. Arrays are filtered element-wise;
objects are filtered **in place** and returned, so the caller's object is modified.

### Filtering HTML

The constructor configures what survives the tag and attribute filters:

```php
// Allow only these tags and attributes, drop everything else.
$filter = new InputFilter(
['p', 'strong', 'em', 'a'],
['href', 'title'],
InputFilter::ONLY_ALLOW_DEFINED_TAGS,
InputFilter::ONLY_ALLOW_DEFINED_ATTRIBUTES
);

$safe = $filter->clean($userHtml, 'html');
```

The `$tagsMethod` and `$attrMethod` constants invert the meaning:

| Constant | Value | Meaning |
|---|---|---|
| `ONLY_ALLOW_DEFINED_TAGS` | `0` | The list is an allowlist |
| `ONLY_BLOCK_DEFINED_TAGS` | `1` | The list is a blocklist |
| `ONLY_ALLOW_DEFINED_ATTRIBUTES` | `0` | The list is an allowlist |
| `ONLY_BLOCK_DEFINED_ATTRIBUTES` | `1` | The list is a blocklist |

The default — empty lists with both methods set to allow — strips every tag, which is the safe
default.

The fifth constructor argument, `$xssAuto`, defaults to `1` and enables the built-in blocklists for
tags such as `script`, `iframe`, `object` and `style`, and for `on*` event handler attributes.
Do not turn it off.

### A caution about the HTML filter

`InputFilter` parses HTML with string operations rather than a parser, which means it makes
different assumptions about the document than the browser that will render it. That gap is where
sanitiser bypasses live, and this implementation has known ones:

* **URL schemes are not checked beyond a few names.** `javascript:`, `vbscript:`, `livescript:`
and `mocha:` are blocked; `data:` is **not**. If `a`/`href` or `img`/`src` is on your allowlist,
`<a href="data:text/html;base64,…">` gets through.
* Attributes without a value — `checked`, `disabled`, `required` — are always dropped, even when
explicitly allowed.

For untrusted HTML from users, prefer a parser-based sanitiser such as
[`ezyang/htmlpurifier`](https://github.com/ezyang/htmlpurifier) or
[`symfony/html-sanitizer`](https://symfony.com/doc/current/html_sanitizer.html), and keep
`InputFilter` for the scalar types, where it is doing simple, predictable work.

### The scalar filters are extraction, not validation

`cleanInt()` and friends search for the first match anywhere in the string:

```php
$filter->clean('abc123', 'int'); // 123, not 0
$filter->clean('x-5y', 'int'); // -5
```

So a filtered value is never a statement that the input was well-formed. Validate separately when
that matters:

```php
$id = $input->getUint('id');

if ($id === 0 || (string) $id !== $input->get('id', '', 'raw')) {
throw new \InvalidArgumentException('id must be a positive integer');
}
```

The same applies to `path`: it accepts absolute paths (`/etc/passwd` passes) and returns `''` for
anything containing a space or a non-ASCII character, so it neither confines a path to a directory
nor round-trips ordinary filenames.

## `OutputFilter`

```php
use Joomla\Filter\OutputFilter;

OutputFilter::objectHtmlSafe($object); // htmlspecialchars every scalar property, by reference
OutputFilter::stringUrlSafe('Ein schöner Titel'); // 'ein-schoner-titel'
OutputFilter::stringUrlUnicodeSlug('Ein Titel'); // keeps unicode characters
OutputFilter::stringJSSafe($string); // \uXXXX-escapes every character
OutputFilter::ampReplace($html); // bare & to &amp;amp;
OutputFilter::linkXhtmlSafe($html); // & inside href to &amp;amp;
OutputFilter::cleanText($text); // strip markup, then escape
OutputFilter::stripImages($html);
OutputFilter::stripIframes($html);
```

`stringUrlSafe()` transliterates. Without a `Language` instance it uses the built-in en-GB
transliteration; give it one for language-specific rules:

```php
OutputFilter::setLanguage($language);
OutputFilter::stringUrlSafe($title, 'de-DE');
```

That setter stores the language **statically**, so it affects every later call in the process.

### What `OutputFilter` does not give you

There is no general-purpose escaping method for a string. `objectHtmlSafe()` handles objects, and
`stringJSSafe()` handles JavaScript, but for the ordinary case — escaping one value for HTML — use
PHP directly:

```php
echo htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
```

Note also that `cleanText()` escapes with `ENT_COMPAT`, which leaves single quotes untouched — its
output is not safe inside a single-quoted attribute. And `stripImages()`/`stripIframes()` match
without the `s` modifier, so a tag containing a newline is not removed; treat them as cosmetic, not
as a security control.

## Where this package is used

`joomla/input` requires it and applies `cmd` by default to every `Input::get()`. Reading a value
with a deliberate filter is usually better than relying on that default:

```php
$title = $input->getString('title');
$id = $input->getUint('id');
$html = $input->get('body', '', 'html');
$raw = $input->get('payload', '', 'raw');
```
38 changes: 34 additions & 4 deletions docs/v2-to-v3-update.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
## Updating from v2 to v3
# Updating from v2 to v3

The following changes were made to the Filter package between v2 and v3.
Release 3.0.0 raises the PHP requirement and reformats the codebase. **No public or protected
method signature changed**, so code written against 2.x keeps working on PHP 8.1.

### Minimum supported PHP version raised
## At a glance

All Framework packages now require PHP 8.1 or newer.
| | v2 (2.0.x) | v3 (3.0.0) |
|---|---|---|
| PHP | `^7.2.5` | `^8.1.0` |
| Public API | — | unchanged |
| Coding style | Joomla Coding Standard | PSR-12 |

## Minimum supported PHP version raised

All Framework packages now require **PHP 8.1** or newer.

## No API changes

`InputFilter` and `OutputFilter` have the same signatures in 3.0.0 as in 2.0.0. The protected
`InputFilter::decode()` deprecated earlier is still present; it was removed in 4.0.0 — see
[Updating from v3 to v4](v3-to-v4-update.md).

## Codebase converted to PSR-12

The package was reformatted from the Joomla Coding Standard to PSR-12. This touches nearly every
line and changes no behaviour.

## Dependency changes

| Package | v2 (2.0.x) | v3 (3.0.0) |
|---|---|---|
| `php` | `^7.2.5` | `^8.1.0` |
| `joomla/string` | `^1.3 \| ^2.0` | `^3.0` |

`joomla/language` remains optional, in `suggest`, for `OutputFilter::stringUrlSafe()` with
language-specific transliteration.
56 changes: 50 additions & 6 deletions docs/v3-to-v4-update.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,55 @@
## Updating from v3 to v4
# Updating from v3 to v4

The following changes were made to the Filter package between v3 and v4.
Release 4.0.0 raises the PHP requirement and removes one deprecated protected method.

### Minimum supported PHP version raised
## At a glance

All Framework packages now require PHP 8.3 or newer.
| | v3 (3.0.3) | v4 (4.0.0) |
|---|---|---|
| PHP | `^8.1.0` | `^8.3.0` |
| `InputFilter::decode()` | deprecated, works | **removed** |
| Public API | — | unchanged |

### Removed `InputFilter::decode()`
## Minimum supported PHP version raised

The internal method `InputFilter::deccode()` was removed. Use `html_entity_decode($source, \ENT_QUOTES, 'UTF-8');` directly.
All Framework packages now require **PHP 8.3** or newer.

## `InputFilter::decode()` was removed

The protected helper was a one-line wrapper deprecated since the PHP 5.3 era:

```php
// Removed in 4.0.0
protected function decode($source)
{
return html_entity_decode($source, \ENT_QUOTES, 'UTF-8');
}
```

Because it was `protected`, this only affects subclasses of `InputFilter` that called or overrode
it. Call the function directly:

```php
// Before
$plain = $this->decode($source);

// After
$plain = html_entity_decode($source, \ENT_QUOTES, 'UTF-8');
```

To find the call sites:

```bash
grep -rn -- '->decode(' src/
```

No public method changed, so code that only uses `clean()` needs no work.

## Dependency changes

| Package | v3 (3.0.3) | v4 (4.0.0) |
|---|---|---|
| `php` | `^8.1.0` | `^8.3.0` |
| `joomla/string` | `^3.0` | `^4.0` |

`joomla/language` remains optional and moved to `^4.0`.
Loading