File: MS\Internal\XpsLoadingContext.cs
Web Access
Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// Unified XPS package-boundary enforcement and ambient loading context.
//
// Provides three entry points for XPS same-package containment checks:
//
//   1. IsUriAllowedInCurrentContext(uri)
//      Ambient check using AsyncLocal — used by PresentationCore sinks
//      (BitmapDecoder, PixelShader) that have no element context.
//
//   2. IsUriAllowedAgainstPackage(origin, uri)
//      Origin-tagged check — used for deferred/lazy resource loading
//      (FontSource, BitmapDownload, ColorContext) where the ambient context
//      is no longer active but the originating package URI was captured.
//
//   3. IsAllowedPackageRelativeUri(parentUri, resolvedUri)
//      Explicit check — used by PresentationFramework sinks
//      (DocumentReference, PageContent, Glyphs, ResourceDictionary) that
//      have access to the parent element's BaseUri.
//
// Set by PresentationFramework's XpsValidatingLoader during XPS content parsing.
//
// Kill switch (default off):
//   Switch.System.Windows.DisableXpsPackageBoundaryRestriction
//
// Security note:
//   Do NOT relax these checks without security review.
//   Fail-closed semantics are intentional.
 
using System;
using System.IO;
using System.IO.Packaging;
using System.Threading;
 
namespace MS.Internal
{
    internal static class XpsLoadingContext
    {
        private static readonly AsyncLocal<Uri> s_activePackageUri = new();
 
        /// <summary>
        /// Gets or sets the package URI of the XPS document currently being loaded
        /// on this async flow. Null when not inside an XPS load.
        /// </summary>
        internal static Uri ActivePackageUri
        {
            get => s_activePackageUri.Value;
            set => s_activePackageUri.Value = value;
        }
 
        /// <summary>
        /// Returns true when the current async flow is inside an XPS document load.
        /// </summary>
        internal static bool IsActive => s_activePackageUri.Value != null;
 
        // ----------------------------------------------------------------
        //  Ambient check — PresentationCore sinks
        // ----------------------------------------------------------------
 
        /// <summary>
        /// Checks whether the given URI is allowed in the current XPS loading context.
        /// When no XPS load is active, all URIs are allowed (non-XPS app scenario).
        /// When an XPS load is active, only pack:// URIs from the same package are allowed.
        /// </summary>
        internal static bool IsUriAllowedInCurrentContext(Uri uri)
        {
            return IsUriAllowedAgainstPackage(s_activePackageUri.Value, uri);
        }
 
        // ----------------------------------------------------------------
        //  Origin-tagged check — deferred/lazy sinks
        // ----------------------------------------------------------------
 
        /// <summary>
        /// Checks whether the given URI is allowed relative to a specific XPS package.
        /// Use this overload for deferred/lazy resource loading where the ambient
        /// context may no longer be active but the originating package URI was
        /// captured at object creation time.
        /// When xpsPackageOrigin is null, all URIs are allowed (non-XPS scenario).
        /// </summary>
        internal static bool IsUriAllowedAgainstPackage(Uri xpsPackageOrigin, Uri uri)
        {
            if (CoreAppContextSwitches.DisableXpsPackageBoundaryRestriction)
            {
                return true;
            }
 
            if (xpsPackageOrigin == null)
            {
                return true; // Not in XPS context — allow everything
            }
 
            return IsSamePackageUri(xpsPackageOrigin, uri);
        }
 
        // ----------------------------------------------------------------
        //  Explicit check — PresentationFramework sinks
        // ----------------------------------------------------------------
 
        /// <summary>
        /// Returns true when the resolved URI is allowed to be fetched as part
        /// of loading a child of an XPS package. The rules are:
        ///
        ///   * If the opt-out AppContext switch is set, return true (legacy).
        ///   * The resolved URI MUST be non-null.
        ///   * If the parent URI is null or NOT an XPS package context, the
        ///     call site is not inside an XPS package and the resolved
        ///     URI is allowed (legacy behavior outside XPS). This preserves
        ///     compatibility for plain ResourceDictionary loads from http://,
        ///     file://, application URIs, etc.
        ///   * If we are inside an active XPS load but parentUri is null,
        ///     fall through to same-package enforcement using the ambient
        ///     package URI.
        ///   * If the parent URI IS an absolute pack:// URI, the resolved URI
        ///     MUST also be absolute pack:// AND its package authority MUST
        ///     equal that of the parent.
        ///
        /// Any failure path returns false (fail-closed).
        /// </summary>
        internal static bool IsAllowedPackageRelativeUri(Uri parentUri, Uri resolvedUri)
        {
            if (CoreAppContextSwitches.DisableXpsPackageBoundaryRestriction)
            {
                return true;
            }
 
            if (resolvedUri == null)
            {
                return false;
            }
 
            if (!IsXpsPackageContext(parentUri))
            {
                if (parentUri == null && IsActive)
                {
                    parentUri = s_activePackageUri.Value;
                    if (parentUri == null)
                    {
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
 
            return IsSamePackageUri(parentUri, resolvedUri);
        }
 
        /// <summary>
        /// Throws <see cref="FileFormatException"/> when
        /// <see cref="IsAllowedPackageRelativeUri"/> returns false.
        /// </summary>
        internal static void EnforcePackageRelativeUri(Uri parentUri, Uri resolvedUri)
        {
            if (!IsAllowedPackageRelativeUri(parentUri, resolvedUri))
            {
                throw new FileFormatException(SR.Resource_XpsPackageBoundaryViolation);
            }
        }
 
        /// <summary>
        /// Returns true when <paramref name="uri"/> is an absolute pack:// URI
        /// whose authority encodes a real XPS package (i.e. an escaped package
        /// file URI). Returns false for null / non-pack URIs and for the two
        /// WPF-internal pack authorities "application:" and "siteoforigin:",
        /// which are not XPS packages.
        /// </summary>
        internal static bool IsXpsPackageContext(Uri uri)
        {
            if (uri == null || !uri.IsAbsoluteUri)
            {
                return false;
            }
 
            if (!string.Equals(uri.Scheme, PackUriHelper.UriSchemePack, StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
 
            string authority = uri.Authority ?? string.Empty;
            if (authority.StartsWith("application:", StringComparison.OrdinalIgnoreCase)
                || authority.StartsWith("siteoforigin:", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
 
            return true;
        }
 
        // ----------------------------------------------------------------
        //  Shared validation logic
        // ----------------------------------------------------------------
 
        /// <summary>
        /// Core same-package containment check shared by all entry points.
        /// Returns true if <paramref name="uri"/> is a pack:// URI belonging
        /// to the same package as <paramref name="packageOrigin"/>, or if
        /// <paramref name="uri"/> is null/relative.
        /// </summary>
        private static bool IsSamePackageUri(Uri packageOrigin, Uri uri)
        {
            if (uri == null || !uri.IsAbsoluteUri)
            {
                return true; // Relative URIs are resolved later; allow them
            }
 
            // In XPS context, only pack:// URIs from the same package are allowed
            if (!string.Equals(uri.Scheme, PackUriHelper.UriSchemePack, StringComparison.OrdinalIgnoreCase))
            {
                return false; // http, https, file, UNC — all blocked in XPS context
            }
 
            // Reject WPF internal pack authorities (application:, siteoforigin:)
            // which point outside the current XPS package
            string authority = uri.Authority ?? string.Empty;
            if (authority.StartsWith("application:", StringComparison.OrdinalIgnoreCase)
                || authority.StartsWith("siteoforigin:", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
 
            try
            {
                Uri resolvedPackage = PackUriHelper.GetPackageUri(uri);
                Uri originPackage;
 
                try
                {
                    originPackage = PackUriHelper.GetPackageUri(packageOrigin);
                }
                catch (ArgumentException)
                {
                    originPackage = packageOrigin;
                }
 
                return resolvedPackage != null
                    && originPackage != null
                    && originPackage.Equals(resolvedPackage);
            }
            catch (ArgumentException)
            {
                return false;
            }
            catch (InvalidOperationException)
            {
                return false;
            }
            catch (UriFormatException)
            {
                return false;
            }
        }
    }
}