# Restricting Resource Loading for Untrusted HTML

When PD4ML converts a document, it loads everything the HTML refers to: stylesheets, images, web fonts, attachments. If the HTML is not entirely yours — written by users, edited by customers, received from another system — those references are chosen by someone else, and PD4ML fetches them with the network access and file permissions of your server. `addAllowedResourceLocation(...)` limits loading to locations you trust; everything else is not loaded.

```java
PD4ML pd4ml = new PD4ML();
pd4ml.addAllowedResourceLocation("https://cdn.example.com/assets/");
pd4ml.addAllowedResourceLocation("file:/opt/app/templates/");
pd4ml.readHTML(new ByteArrayInputStream(userHtml.getBytes(StandardCharsets.UTF_8)),
        new URL("https://cdn.example.com/assets/"), "UTF-8");
pd4ml.writePDF(out);
```

{% hint style="version" %}
`addAllowedResourceLocation(...)` and `*` patterns are available from **PD4ML v4.2.0**, with both layout engines. The `pd4ml.allowed.resource.location` parameter it builds on (`Constants.PD4ML_ALLOWED_RESOURCE_LOCATION`) exists in earlier versions as a comma-separated list of URL prefixes.
{% endhint %}

## Why it matters

A reference in HTML is an instruction to your server. Without a restriction, untrusted HTML can make PD4ML:

- **read local files**: `<img src="file:/etc/...">`, `<link rel="stylesheet" href="file:/C:/app/config/...">` — the server reads them with the permissions of the process running PD4ML;
- **call internal services**: `<img src="http://10.0.0.5/admin/...">`, `http://169.254.169.254/...` (the metadata endpoint of cloud servers) — requests that come from inside your network, where a firewall no longer protects the target. This is known as server-side request forgery (SSRF);
- **put what it fetched into the PDF**: an image or a web font that was loaded ends up in the output document, which goes back to whoever supplied the HTML;
- **wait for slow or hostile servers**: every reference is a connection your conversion waits for.

Use `addAllowedResourceLocation(...)` whenever the HTML, or any part of it, comes from a source you do not fully control: user-submitted content, templates customers can edit, HTML from e-mails or third-party APIs, or your own templates with user data inserted without escaping.

## How it works

`addAllowedResourceLocation(locations)` takes a comma-separated list of locations. A resource is loaded only if its URL starts with one of them:

| Location | Allows |
|---|---|
| `https://cdn.example.com/assets/` | everything under that folder of that server |
| `file:/opt/app/templates/` | local files under `/opt/app/templates/` |
| `/opt/app/templates/`, `C:/app/templates/` | the same: a local path stands for its `file:` URL |
| `https://*.example.com/` | every subdomain of `example.com` |
| `https://cdn.example.com/*/img/` | the `img` folder of every folder directly under `/` |

- **Accumulating**: each call adds its locations to the ones added before, so different parts of an application can contribute their own.
- **Case**: the comparison ignores case.
- **Patterns**: `*` matches any run of characters except `/`. It stays within one host name or one path segment: `https://*.example.com/` does not match `https://evil.com/x.example.com/`.
- **`..`**: a resource path containing `..` is rejected, whatever the locations.
- **No locations, no restriction**: `null` or `""` adds nothing, and as long as no location has been added, resources load from anywhere.
- **A rejected resource is skipped**: the document is still converted, without it (an image is left out, a stylesheet is not applied); `setLogLevel(Constants.LOG_CACHE)` logs which resource was rejected and why. With `setErrorPolicy(ErrorPolicy.PEDANTIC)` or `STRICT`, a rejected resource stops the conversion with an `IOException` naming it instead.

The same restriction can be set as a parameter; the method adds to a value set this way:

```java
pd4ml.setParam(Constants.PD4ML_ALLOWED_RESOURCE_LOCATION, "https://cdn.example.com/, /opt/app/templates/");
```

## What is checked

The restriction applies to everything PD4ML loads while converting, including:

- the HTML document itself, when it is read from a URL with `readHTML(URL)`;
- stylesheets (`<link rel="stylesheet">`, `@import`) and the images they refer to (`background-image`, `list-style-image`);
- images (`<img>`, `<input type="image">`, SVG images);
- web fonts declared with `@font-face` (v42 engine);
- attachments (`<pd4ml:attachment>`).

It does **not** apply to:

- inline `data:` URIs — their content is part of the HTML already;
- classpath resources (`java:`), such as PD4ML's own default resources;
- the font directories set with `useTTF(...)`, which you configure yourself.

## Recommendations

- **End a server location with `/`**: `https://cdn.example.com` (no slash) also matches `https://cdn.example.com.attacker.net/`, because the check compares the beginning of the URL. `https://cdn.example.com/` does not.
- **Allow servers you control.** Redirects of an allowed server are followed: a location is only as trustworthy as the server behind it.
- **Pass untrusted HTML as text, with a trusted base URL**: `readHTML(InputStream, URL, encoding)` with the base URL of your assets. Relative references resolve against it, and the document itself is not loaded from anywhere. When you read a document from a URL instead, that URL must be within the allowed locations too, or the document is not loaded.
- **Turn off what you don't need**: `setParam(Constants.PD4ML_DISABLE_EXTERNAL_ATTACHMENTS, "true")` stops loading `<pd4ml:attachment>` files altogether, and `setParam(Constants.PD4ML_SOCKET_TIMEOUT, "5000")` keeps a slow server from holding up the conversion (milliseconds; without it, the JVM's defaults apply, which may mean no timeout).
- **Allow-list, not block-list**: list the locations your documents need, rather than trying to exclude dangerous ones — there are too many ways to write an internal address.

## In the API reference

- [`PD4ML.addAllowedResourceLocation(String)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#addAllowedResourceLocation(java.lang.String))
- [`Constants.PD4ML_ALLOWED_RESOURCE_LOCATION`](https://pd4ml.com/javadoc/com/pd4ml/Constants.html#PD4ML_ALLOWED_RESOURCE_LOCATION), [`PD4ML_DISABLE_EXTERNAL_ATTACHMENTS`](https://pd4ml.com/javadoc/com/pd4ml/Constants.html#PD4ML_DISABLE_EXTERNAL_ATTACHMENTS), [`PD4ML_SOCKET_TIMEOUT`](https://pd4ml.com/javadoc/com/pd4ml/Constants.html#PD4ML_SOCKET_TIMEOUT)
