|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Text;
namespace System.IO.Packaging
{
/// <summary>
/// This class has the utility methods for composing and parsing an Uri of pack:// scheme
/// </summary>
public static partial class PackUriHelper
{
// We need to perform Escaping for the following - '%'; '@'; ',' and '?'
// !!Important!! - The order is important - The '%' sign should be escaped first.
// If any more characters need to be added to the array below they should be added at the end.
// All of these arrays must maintain the same ordering.
private static readonly char[] s_specialCharacterChars = { '%', '@', ',', '?' };
#region Public Methods
/// <summary>
/// This method is used to create a valid pack Uri
/// </summary>
/// <param name="packageUri">This is the uri that points to the entire package.
/// This parameter should be an absolute Uri. This parameter cannot be null or empty
/// This method will create a valid pack uri that references the entire package</param>
/// <returns>A Uri with the "pack://" scheme</returns>
/// <exception cref="ArgumentNullException">If packageUri parameter is null</exception>
/// <exception cref="ArgumentException">If packageUri parameter is not an absolute Uri</exception>
public static Uri Create(Uri packageUri)
{
return Create(packageUri, null, null);
}
/// <summary>
/// This method is used to create a valid pack Uri
/// </summary>
/// <param name="packageUri">This is the uri that points to the entire package.
/// This parameter should be an absolute Uri. This parameter cannot be null or empty </param>
/// <param name="partUri">This is the uri that points to the part within the package
/// This parameter should be a relative Uri.
/// This parameter can be null in which case we will create a valid pack uri
/// that references the entire package</param>
/// <returns>A Uri with the "pack://" scheme</returns>
/// <exception cref="ArgumentNullException">If packageUri parameter is null</exception>
/// <exception cref="ArgumentException">If packageUri parameter is not an absolute Uri</exception>
/// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
public static Uri Create(Uri packageUri, Uri? partUri)
{
return Create(packageUri, partUri, null);
}
/// <summary>
/// This method is used to create a valid pack Uri
/// </summary>
/// <param name="packageUri">This is the uri that points to the entire package.
/// This parameter should be an absolute Uri. This parameter cannot be null or empty </param>
/// <param name="partUri">This is the uri that points to the part within the package
/// This parameter should be a relative Uri.
/// This parameter can be null in which case we will create a valid pack uri
/// that references the entire package</param>
/// <param name="fragment">Fragment for the resulting Pack URI. This parameter can be null
/// The fragment string must start with a "#"</param>
/// <returns>A Uri with the "pack://" scheme</returns>
/// <exception cref="ArgumentNullException">If packageUri parameter is null</exception>
/// <exception cref="ArgumentException">If packageUri parameter is not an absolute Uri</exception>
/// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
/// <exception cref="ArgumentException">If fragment parameter is empty or does not start with a "#"</exception>
public static Uri Create(Uri packageUri, Uri? partUri, string? fragment)
{
// Step 1 - Validate input parameters
packageUri = ValidatePackageUri(packageUri);
if (partUri != null)
partUri = ValidatePartUri(partUri);
if (fragment != null)
{
if (fragment.Length == 0 || fragment[0] != '#')
throw new ArgumentException(SR.Format(SR.FragmentMustStartWithHash, nameof(fragment)));
}
// Step 2 - Remove fragment identifier from the package URI, if it is present
// Since '#" is an excluded character in Uri syntax, it can only occur as the
// fragment identifier, in all other places it should be escaped.
// Hence we can safely use IndexOf to find the beginning of the fragment.
string absolutePackageUri = packageUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped);
if (!string.IsNullOrEmpty(packageUri.Fragment))
{
absolutePackageUri = absolutePackageUri.Substring(0, absolutePackageUri.IndexOf('#'));
}
// Step 3 - Escape: "%", "?", "@", "#" and "," in the package URI
absolutePackageUri = EscapeSpecialCharacters(absolutePackageUri);
// Step 4 - Replace all '/' with ',' in the resulting string
absolutePackageUri = absolutePackageUri.Replace('/', ',');
// Step 5 - Append pack:// at the beginning and a '/' at the end of the pack uri obtained so far
absolutePackageUri = string.Concat(PackUriHelper.UriSchemePack, Uri.SchemeDelimiter, absolutePackageUri);
Uri packUri = new Uri(absolutePackageUri);
// Step 6 - Append the part Uri if present.
if (partUri != null)
packUri = new Uri(packUri, partUri);
// Step 7 - Append fragment if present
if (fragment != null)
packUri = new Uri(string.Concat(packUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped), fragment));
// We want to ensure that internal content of resulting Uri has canonical form
// i.e. result.OriginalString would appear as perfectly formatted Uri string
// so we roundtrip the result.
return new Uri(packUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped));
}
/// <summary>
/// This method parses the pack uri and returns the inner
/// Uri that points to the package as a whole.
/// </summary>
/// <param name="packUri">Uri which has pack:// scheme</param>
/// <returns>Returns the inner uri that points to the entire package</returns>
/// <exception cref="ArgumentNullException">If packUri parameter is null</exception>
/// <exception cref="ArgumentException">If packUri parameter is not an absolute Uri</exception>
/// <exception cref="ArgumentException">If packUri parameter does not have "pack://" scheme</exception>
/// <exception cref="ArgumentException">If inner packageUri extracted from the packUri has a fragment component</exception>
public static Uri GetPackageUri(Uri packUri)
{
//Parameter Validation is done in the following method
ValidateAndGetPackUriComponents(packUri, out Uri packageUri, out _);
return packageUri;
}
/// <summary>
/// This method parses the pack uri and returns the absolute
/// path of the URI. This corresponds to the part within the
/// package. This corresponds to the absolute path component in
/// the Uri. If there is no part component present, this method
/// returns a null
/// </summary>
/// <param name="packUri">Returns a relative Uri that represents the
/// part within the package. If the pack Uri points to the entire
/// package then we return a null</param>
/// <returns>Returns a relative URI with an absolute path that points to a part within a package</returns>
/// <exception cref="ArgumentNullException">If packUri parameter is null</exception>
/// <exception cref="ArgumentException">If packUri parameter is not an absolute Uri</exception>
/// <exception cref="ArgumentException">If packUri parameter does not have "pack://" scheme</exception>
/// <exception cref="ArgumentException">If partUri extracted from packUri does not conform to the valid partUri syntax</exception>
public static Uri? GetPartUri(Uri packUri)
{
//Parameter Validation is done in the following method
ValidateAndGetPackUriComponents(packUri, out _, out Uri? partUri);
return partUri;
}
/// <summary>
/// This method compares two pack uris and returns an int to indicate the equivalence.
/// </summary>
/// <param name="firstPackUri">First Uri of pack:// scheme to be compared</param>
/// <param name="secondPackUri">Second Uri of pack:// scheme to be compared</param>
/// <returns>A 32-bit signed integer indicating the lexical relationship between the compared Uri components.
/// Value - Less than zero means firstUri is less than secondUri
/// Value - Equal to zero means both the Uris are equal
/// Value - Greater than zero means firstUri is greater than secondUri </returns>
/// <exception cref="ArgumentException">If either of the Uris are not absolute or if either of the Uris are not with pack:// scheme</exception>
/// <exception cref="ArgumentException">If firstPackUri or secondPackUri parameter is not an absolute Uri</exception>
/// <exception cref="ArgumentException">If firstPackUri or secondPackUri parameter does not have "pack://" scheme</exception>
public static int ComparePackUri(Uri? firstPackUri, Uri? secondPackUri)
{
//If any of the operands are null then we simply call System.Uri compare to return the correct value
if (firstPackUri == null || secondPackUri == null)
{
return CompareUsingSystemUri(firstPackUri, secondPackUri);
}
else
{
int compareResult;
ValidateAndGetPackUriComponents(firstPackUri, out Uri firstPackageUri, out Uri? firstPartUri);
ValidateAndGetPackUriComponents(secondPackUri, out Uri secondPackageUri, out Uri? secondPartUri);
if (firstPackageUri.Scheme == PackUriHelper.UriSchemePack && secondPackageUri.Scheme == PackUriHelper.UriSchemePack)
{
compareResult = ComparePackUri(firstPackageUri, secondPackageUri);
}
else
{
compareResult = CompareUsingSystemUri(firstPackageUri, secondPackageUri);
}
//Iff the PackageUri match do we compare the part uris.
if (compareResult == 0)
{
compareResult = System.IO.Packaging.PackUriHelper.ComparePartUri(firstPartUri, secondPartUri);
}
return compareResult;
}
}
#endregion Public Methods
#region Internal Methods
//This method validates the packUri and returns its two components if they are valid-
//1. Package Uri
//2. Part Uri
internal static void ValidateAndGetPackUriComponents(Uri packUri, out Uri packageUri, out Uri? partUri)
{
//Validate if its not null and is an absolute Uri, has pack:// Scheme.
packUri = ValidatePackUri(packUri);
packageUri = GetPackageUriComponent(packUri);
partUri = GetPartUriComponent(packUri);
}
#endregion Internal Methods
#region Private Constructor
static PackUriHelper()
{
EnsurePackSchemeRegistered();
}
#endregion Private Constructor
#region Private Methods
private static void EnsurePackSchemeRegistered()
{
if (!UriParser.IsKnownScheme(UriSchemePack))
{
// Indicate that we want a default hierarchical parser with a registry based authority
UriParser.Register(new GenericUriParser(GenericUriParserOptions.GenericAuthority), UriSchemePack, -1);
}
}
/// <summary>
/// This method is used to validate the package uri
/// </summary>
/// <param name="packageUri"></param>
/// <returns></returns>
private static Uri ValidatePackageUri(Uri packageUri)
{
if (packageUri == null)
throw new ArgumentNullException(nameof(packageUri));
if (!packageUri.IsAbsoluteUri)
throw new ArgumentException(SR.UriShouldBeAbsolute, nameof(packageUri));
return packageUri;
}
//validates is a given uri has pack:// scheme
private static Uri ValidatePackUri(Uri packUri)
{
if (packUri == null)
throw new ArgumentNullException(nameof(packUri));
if (!packUri.IsAbsoluteUri)
throw new ArgumentException(SR.UriShouldBeAbsolute, nameof(packUri));
if (packUri.Scheme != PackUriHelper.UriSchemePack)
throw new ArgumentException(SR.UriShouldBePackScheme, nameof(packUri));
return packUri;
}
/// <summary>
/// Escapes - %', '@', ',', '?' in the package URI
/// This method modifies the string in a culture safe and case safe manner.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private static string EscapeSpecialCharacters(string path)
{
// Escaping for the following - '%'; '@'; ',' and '?'
// !!Important!! - The order is important - The '%' sign should be escaped first.
// This is currently enforced by the order of characters in the s_specialCharacterChars array
foreach (char c in s_specialCharacterChars)
{
#if NET
if (path.Contains(c))
#else
if (path.IndexOf(c) != -1)
#endif
{
path = path.Replace(c.ToString(), Uri.HexEscape(c));
}
}
return path;
}
//This method validates and returns the PackageUri component
private static Uri GetPackageUriComponent(Uri packUri)
{
Debug.Assert(packUri != null, "packUri parameter cannot be null");
//Step 1 - Get the authority part of the URI. This section represents that package URI
string hostAndPort = packUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
//Step 2 - Replace the ',' with '/' to reconstruct the package URI
hostAndPort = hostAndPort.Replace(',', '/');
//Step 3 - Unescape the special characters that we had escaped to construct the packUri
Uri packageUri = new Uri(Uri.UnescapeDataString(hostAndPort));
if (packageUri.Fragment != string.Empty)
throw new ArgumentException(SR.InnerPackageUriHasFragment);
return packageUri;
}
//This method validates and returns the PartUri component.
private static PackUriHelper.ValidatedPartUri? GetPartUriComponent(Uri packUri)
{
Debug.Assert(packUri != null, "packUri parameter cannot be null");
string partName = GetStringForPartUriFromAnyUri(packUri);
if (partName.Length == 0)
return null;
else
return ValidatePartUri(new Uri(partName, UriKind.Relative));
}
#endregion Private Methods
}
}
|