Table of Contents

Class ResourceFetchContext

Namespace
NetPdf
Assembly
NetPdf.dll

Per Phase D D-1 — per-render state threaded through every SafeResourceLoader fetch. Tracks the cumulative byte + fetch counts that MaxResourcesPerRender + MaxTotalResourceBytes bound.

One instance lives per HtmlPdf.ConvertAsync invocation; sharing across renders would let attacker-A's traffic exhaust attacker-B's budget. The pipeline allocates this in HtmlParsingHost.ParseAsync (or whoever owns the conversion's top-level entry point) + passes it down through every CSS / image / font resource lookup.

Per PR #18 review #6 — concurrency-safe. Reservations use Interlocked so a future Phase 5 implementation can fan out parallel image/font/style fetches without races. Pre-fix concurrent callers could each read a stale FetchedCount, find it under the cap, increment from that stale value, and collectively exceed the cap. Post-fix the count + byte ledger maintain their invariants under concurrent reservation.

public sealed class ResourceFetchContext
Inheritance
ResourceFetchContext
Inherited Members

Constructors

ResourceFetchContext(SecurityPolicy, Uri?, CancellationToken)

public ResourceFetchContext(SecurityPolicy policy, Uri? baseUri, CancellationToken cancellationToken)

Parameters

policy SecurityPolicy
baseUri Uri
cancellationToken CancellationToken

Properties

BaseUri

The conversion's BaseUri. Loaders use this to resolve relative URIs + to anchor the AllowFileSchemeUnderBaseUri path-prefix check that UriSafetyValidator defers.

public Uri? BaseUri { get; }

Property Value

Uri

CancellationToken

The conversion-level cancellation token.

public CancellationToken CancellationToken { get; }

Property Value

CancellationToken

FetchedBytes

Running cumulative bytes returned across every successful fetch. Written via Add(ref long, long) in TryAddBytes(long); reads are volatile.

public long FetchedBytes { get; }

Property Value

long

FetchedCount

Running count of resources successfully fetched. Read is volatile per Interlocked semantics; writes go through TryReserveSlot()'s atomic CAS.

public int FetchedCount { get; }

Property Value

int

Policy

The active SecurityPolicy. Captured once at construction so a hostile loader can't swap policies mid-render.

public SecurityPolicy Policy { get; }

Property Value

SecurityPolicy

Methods

TryAddBytes(long)

Charge bytes against the per-render byte budget. Used after a successful fetch when the actual byte count is known; does not increment the slot counter (the caller already reserved one via TryReserveSlot()). Returns a non-null reason when the byte budget would be exceeded; null on success.

 <p>Per PR #18 review #6 — concurrency-safe via CAS.
 <xref href="System.Threading.Interlocked.Add(System.Int64%40%2cSystem.Int64)" data-throw-if-not-resolved="false"></xref> alone would
 over-charge: two concurrent calls could each see a value under
 the cap, both add their bytes, and end above the cap. The CAS
 loop reserves the byte count atomically.</p>
public string? TryAddBytes(long bytes)

Parameters

bytes long

Returns

string

TryReserveSlot()

Reserve one fetch slot against the per-render count cap. Returns a non-null reason when the slot budget is exhausted; null when the reservation succeeded.

 <p>Per PR #18 review #6 — uses CAS (<xref href="System.Threading.Interlocked.CompareExchange(System.Int32%40%2cSystem.Int32%2cSystem.Int32)" data-throw-if-not-resolved="false"></xref>)
 so concurrent callers can race the increment without exceeding
 the cap. The CAS loop reads the current count, computes the
 next value, attempts to swap; on contention re-reads + retries.</p>

 <p>Byte budget enforcement is split out into <xref href="NetPdf.ResourceFetchContext.TryAddBytes(System.Int64)" data-throw-if-not-resolved="false"></xref>
 so the caller can reserve a slot at fetch START (when the byte count
 is unknown) + charge the actual byte count at fetch END (without
 re-incrementing the slot counter).</p>
public string? TryReserveSlot()

Returns

string