using System; using System.Linq; namespace Microsoft.AspNet.Abstractions { /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// public struct PathString : IEquatable { /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initalize the path string with a given value. This value must be in unescaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(""/*Resources.Exception_PathMustStartWithSlash*/, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Purpose of the method is to return a string")] public string ToUriComponent() { // TODO: Measure the cost of this escaping and consider optimizing. return HasValue ? String.Join("/", _value.Split('/').Select(Uri.EscapeDataString)) : String.Empty; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Requirements not compatible with Uri processing")] public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Secondary information needed after boolean result obtained")] public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } }