Table of Contents

Class SafeResourceLoader

Namespace
NetPdf
Assembly
NetPdf.dll

Per Phase D D-1 — the only valid pipeline entry point for resource fetches. Wraps a user-supplied IResourceLoader + applies every safety check the NetPdf threat model requires:

  1. Per-render budget via ResourceFetchContext: MaxResourcesPerRender (count cap), MaxTotalResourceBytes (cumulative byte cap).
  2. URI safety via Validate(Uri, SecurityPolicy): scheme allowlist (file/data/http/https per SecurityPolicy), IP blocklist for HTTP(S) (loopback, private, link-local incl. AWS/GCE/Azure metadata 169.254.169.254, IPv6 ULA, IPv4-mapped), AllowedHosts filter.
  3. file: base-path check when the URI returns RequiresBasePathCheck: the resolved file path is verified to lie under BaseUri's directory subtree (canonicalized to defeat ../ traversal).
  4. Per-resource size cap from MaxResourceBytes (post-fetch validation; pre-fetch byte estimate not always available from the user loader).
  5. MIME allowlist per ResourceKind so an <img src=...> serving text/html can't route through the image decoder.
  6. Per-fetch timeout via ResourceTimeout linked to the conversion's cancellation token.

Why this wrapper exists. The user-facing IResourceLoader contract is intentionally narrow (raw byte fetch). If any caller in the pipeline went directly to ResourceLoader, that caller would bypass every defense above. SafeResourceLoader is the only pipeline-internal entry point — when Phase 5 wires resource fetching, every consumer (CSS parser for url() / @import / @font-face, image decoder, font resolver) calls THIS, never the underlying IResourceLoader directly.

NetPdf v1 ships no default loader. When ResourceLoader is null, every fetch returns a ResourceFailure immediately without invoking any logic — the wrapper still exists so the contract is consistent for Phase 5's wireup.

public sealed class SafeResourceLoader
Inheritance
SafeResourceLoader
Inherited Members

Constructors

SafeResourceLoader(IResourceLoader?, ResourceFetchContext)

public SafeResourceLoader(IResourceLoader? inner, ResourceFetchContext context)

Parameters

inner IResourceLoader
context ResourceFetchContext

Methods

CreateWithSafeHttp(ResourceFetchContext)

Per post-Task-7 review (recommendation P1 #1) — factory that constructs a SafeHttpResourceLoader using the context's SecurityPolicy + wraps it in a SafeResourceLoader bound to the same context. Single source of truth for the policy across both layers; eliminates the divergence risk entirely.

 <p>The returned wrapper does NOT take ownership of the
 returned HTTP loader's lifetime. Callers who construct via this
 factory should track the underlying loader separately if they
 need <xref href="System.IDisposable.Dispose" data-throw-if-not-resolved="false"></xref> on shutdown — typically
 by reading the <code>UnderlyingHttpLoader</code> property off the
 returned <xref href="NetPdf.SafeResourceLoaderWithHttp" data-throw-if-not-resolved="false"></xref> bundle. (For
 most v1 use cases the loader lives for the process lifetime.)</p>
public static SafeResourceLoaderWithHttp CreateWithSafeHttp(ResourceFetchContext context)

Parameters

context ResourceFetchContext

Returns

SafeResourceLoaderWithHttp

FetchAsync(Uri, ResourceKind)

Fetch a resource with all the Phase D defenses applied. Returns the loaded bytes on success; a ResourceFailure shape on rejection. Slot reservation happens AFTER the URI safety + base-path checks so a fast-rejected fetch doesn't consume a budget slot — an attacker could otherwise probe the allowlist by firing N+1 obviously-invalid URIs to lock out legitimate fetches.

public ValueTask<SafeResourceResult> FetchAsync(Uri uri, ResourceKind kind)

Parameters

uri Uri
kind ResourceKind

Returns

ValueTask<SafeResourceResult>