# HashPolicy
The limits [[Painting/API/Hash/PackHasher]] enforces. Built through `HashPolicy.builder()`.
Defaults are the strict ones — no private-network URLs, no file outside the roots handed in, 256 MiB cap. Painting builds two: a config policy for URLs the admin wrote themselves (private hosts allowed, since a LAN CDN or a self-hosted pack is an ordinary setup), and a stricter API policy for sources handed in by another plugin, driven by the `hash-security` block in the config.
---
## Source
```java
package gg.lode.paintingapi.api.hash;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Limits applied to every hash {@link PackHasher} computes.
*
* <p>The defaults are the strict ones: no private-network URLs, no file outside
* the roots handed in, and a hard byte cap. Callers acting on their own config
* (the plugin hashing its own {@code resourcepacks:} block) relax them
* deliberately; callers acting on input from another plugin should not.
*/
public final class HashPolicy {
/** 256 MiB — comfortably above any client-loadable pack. */
public static final long DEFAULT_MAX_BYTES = 256L * 1024L * 1024L;
private final List<Path> allowedRoots;
private final long maxBytes;
private final boolean allowPrivateHosts;
private final int maxRedirects;
private final int connectTimeoutMs;
private final int readTimeoutMs;
private HashPolicy(Builder b) {
this.allowedRoots = Collections.unmodifiableList(new ArrayList<>(b.allowedRoots));
this.maxBytes = b.maxBytes;
this.allowPrivateHosts = b.allowPrivateHosts;
this.maxRedirects = b.maxRedirects;
this.connectTimeoutMs = b.connectTimeoutMs;
this.readTimeoutMs = b.readTimeoutMs;
}
public static @NotNull Builder builder() {
return new Builder();
}
/**
* Directories a file source may resolve inside, after symlinks are resolved.
* A relative path is tried against each in order. Empty means no file source
* is accepted at all.
*/
public @NotNull List<Path> allowedRoots() { return allowedRoots; }
public long maxBytes() { return maxBytes; }
/**
* Whether a URL may resolve to a loopback, link-local, site-local, or
* otherwise non-public address. False keeps a URL handed in by another
* plugin from reaching the host's own network.
*/
public boolean allowPrivateHosts() { return allowPrivateHosts; }
public int maxRedirects() { return maxRedirects; }
public int connectTimeoutMs() { return connectTimeoutMs; }
public int readTimeoutMs() { return readTimeoutMs; }
public static final class Builder {
private final List<Path> allowedRoots = new ArrayList<>();
private long maxBytes = DEFAULT_MAX_BYTES;
private boolean allowPrivateHosts = false;
private int maxRedirects = 5;
private int connectTimeoutMs = 15_000;
private int readTimeoutMs = 30_000;
public @NotNull Builder allowRoot(@NotNull Path root) {
allowedRoots.add(root);
return this;
}
public @NotNull Builder maxBytes(long maxBytes) {
this.maxBytes = maxBytes > 0 ? maxBytes : DEFAULT_MAX_BYTES;
return this;
}
public @NotNull Builder allowPrivateHosts(boolean allow) {
this.allowPrivateHosts = allow;
return this;
}
public @NotNull Builder maxRedirects(int maxRedirects) {
this.maxRedirects = Math.max(0, maxRedirects);
return this;
}
public @NotNull Builder timeouts(int connectTimeoutMs, int readTimeoutMs) {
this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs;
return this;
}
public @NotNull HashPolicy build() {
return new HashPolicy(this);
}
}
}
```
---
## Related Pages
- [[Painting/API/Hash/PackHasher]] — applies the policy
- [[Painting/Server Owners/Configuration]] — the `hash-security` block that fills it in