Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordotnet-bot <dotnet-bot@microsoft.com>2015-04-24 04:20:55 +0300
committerdotnet-bot <dotnet-bot@microsoft.com>2015-04-24 04:20:55 +0300
commit57df1c06dc025aa6bd031603a8497b9c44137d85 (patch)
treee63229e54a7bd83247b8decfc2542812ef502940 /src
parentcd5f422458d7b86f98e3cf67ee6ce2804de7e680 (diff)
Move System.Runtime and System.Private.Uri to FxCore\Open. Checked in by erme
[tfs-changeset: 1459363]
Diffstat (limited to 'src')
-rw-r--r--src/.nuget/packages.config1
-rw-r--r--src/Common/src/Interop/Windows/mincore/Interop.GetLastError.cs13
-rw-r--r--src/Common/src/System/Collections/Generic/LowLevelDictionary.cs349
-rw-r--r--src/Common/src/System/Collections/Generic/LowLevelList.cs597
-rw-r--r--src/Common/src/System/Diagnostics/Debug.Unix.cs (renamed from src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.Unix.cs)0
-rw-r--r--src/Common/src/System/Diagnostics/Debug.Windows.cs (renamed from src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.Windows.cs)74
-rw-r--r--src/Common/src/System/Diagnostics/Debug.cs (renamed from src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.cs)6
-rw-r--r--src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj28
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/Debugger.cs28
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableAttribute.cs27
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableState.cs24
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerDisplayAttribute.cs71
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerHiddenAttribute.cs11
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerNonUserCodeAttribute.cs11
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerStepThroughAttribute.cs12
-rw-r--r--src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerTypeProxyAttribute.cs58
-rw-r--r--src/System.Diagnostics.Debug/src/project.json16
-rw-r--r--src/System.Diagnostics.Debug/tests/DebuggerTests.cs3
-rw-r--r--src/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj5
-rw-r--r--src/System.Diagnostics.Debug/tests/project.json17
-rw-r--r--src/System.Private.Uri/src/Resources/Strings.resx264
-rw-r--r--src/System.Private.Uri/src/System.Private.Uri.CoreCLR.csproj168
-rw-r--r--src/System.Private.Uri/src/System/DomainNameHelper.cs539
-rw-r--r--src/System.Private.Uri/src/System/IPv4AddressHelper.cs369
-rw-r--r--src/System.Private.Uri/src/System/IPv6AddressHelper.cs594
-rw-r--r--src/System.Private.Uri/src/System/IriHelper.cs364
-rw-r--r--src/System.Private.Uri/src/System/UncNameHelper.cs130
-rw-r--r--src/System.Private.Uri/src/System/Uri.cs5181
-rw-r--r--src/System.Private.Uri/src/System/UriBuilder.cs441
-rw-r--r--src/System.Private.Uri/src/System/UriEnumTypes.cs105
-rw-r--r--src/System.Private.Uri/src/System/UriExt.cs934
-rw-r--r--src/System.Private.Uri/src/System/UriFormatException.cs23
-rw-r--r--src/System.Private.Uri/src/System/UriHelper.cs712
-rw-r--r--src/System.Private.Uri/src/System/UriHostNameType.cs14
-rw-r--r--src/System.Private.Uri/src/System/UriPartial.cs13
-rw-r--r--src/System.Private.Uri/src/System/UriScheme.cs153
-rw-r--r--src/System.Private.Uri/src/System/UriSyntax.cs590
-rw-r--r--src/System.Private.Uri/src/project.json5
-rw-r--r--src/System.Runtime/System.Runtime.sln270
-rw-r--r--src/System.Runtime/src/System.Runtime.CoreCLR.csproj40
-rw-r--r--src/System.Runtime/src/System/Action.cs29
-rw-r--r--src/System.Runtime/src/System/Collections/Generic/ISet.cs48
-rw-r--r--src/System.Runtime/src/System/ComponentModel/DefaultValueAttribute.cs183
-rw-r--r--src/System.Runtime/src/System/ComponentModel/EditorBrowsableAttribute.cs67
-rw-r--r--src/System.Runtime/src/System/Function.cs29
-rw-r--r--src/System.Runtime/src/System/LazyOfTTMetadata.cs59
-rw-r--r--src/System.Runtime/src/System/Runtime/CompilerServices/StrongBox.cs58
-rw-r--r--src/System.Runtime/src/project.json5
-rw-r--r--src/System.Runtime/tests/System.Runtime.Tests.csproj6
49 files changed, 12391 insertions, 353 deletions
diff --git a/src/.nuget/packages.config b/src/.nuget/packages.config
index 4e77d0c2c4..e01426d7cd 100644
--- a/src/.nuget/packages.config
+++ b/src/.nuget/packages.config
@@ -2,4 +2,5 @@
<packages>
<package id="Microsoft.DotNet.BuildTools" version="1.0.25-prerelease-00040" />
<package id="dnx-coreclr-win-x86" version="1.0.0-beta5-11580" />
+ <package id="Microsoft.DotNet.BuildTools.ApiTools" version="1.0.0-prerelease" />
</packages>
diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetLastError.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetLastError.cs
new file mode 100644
index 0000000000..3bd3b063e6
--- /dev/null
+++ b/src/Common/src/Interop/Windows/mincore/Interop.GetLastError.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class mincore
+ {
+ [DllImport(Libraries.ErrorHandling)]
+ internal extern static int GetLastError();
+ }
+}
diff --git a/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs b/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs
new file mode 100644
index 0000000000..eacf44ea6f
--- /dev/null
+++ b/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs
@@ -0,0 +1,349 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace System.Collections.Generic
+{
+ /*============================================================
+ **
+ ** Class: LowLevelDictionary<TKey, TValue>
+ **
+ ** Private version of Dictionary<> for internal System.Private.CoreLib use. This
+ ** permits sharing more source between BCL and System.Private.CoreLib (as well as the
+ ** fact that Dictionary<> is just a useful class in general.)
+ **
+ ** This does not strive to implement the full api surface area
+ ** (but any portion it does implement should match the real Dictionary<>'s
+ ** behavior.)
+ **
+ ===========================================================*/
+#if TYPE_LOADER_IMPLEMENTATION
+ [System.Runtime.CompilerServices.ForceDictionaryLookups]
+#endif
+ internal class LowLevelDictionary<TKey, TValue>
+ {
+ private const int DefaultSize = 17;
+
+ public LowLevelDictionary()
+ : this(DefaultSize, new DefaultComparer<TKey>())
+ {
+ }
+
+ public LowLevelDictionary(int capacity)
+ : this(capacity, new DefaultComparer<TKey>())
+ {
+ }
+
+ public LowLevelDictionary(IEqualityComparer<TKey> comparer) : this(DefaultSize, comparer)
+ {
+ }
+
+ public LowLevelDictionary(int capacity, IEqualityComparer<TKey> comparer)
+ {
+ _comparer = comparer;
+ Clear(capacity);
+ }
+
+ public int Count
+ {
+ get
+ {
+ return _numEntries;
+ }
+ }
+
+ public TValue this[TKey key]
+ {
+ get
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ Entry entry = Find(key);
+ if (entry == null)
+ throw new KeyNotFoundException();
+ return entry.m_value;
+ }
+ set
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ _version++;
+ Entry entry = Find(key);
+ if (entry != null)
+ entry.m_value = value;
+ else
+ UncheckedAdd(key, value);
+ }
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ value = default(TValue);
+ if (key == null)
+ throw new ArgumentNullException("key");
+ Entry entry = Find(key);
+ if (entry != null)
+ {
+ value = entry.m_value;
+ return true;
+ }
+ return false;
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+ Entry entry = Find(key);
+ if (entry != null)
+ throw new ArgumentException(SR.Argument_AddingDuplicate);
+ _version++;
+ UncheckedAdd(key, value);
+ }
+
+ public void Clear(int capacity = DefaultSize)
+ {
+ _version++;
+ _buckets = new Entry[capacity];
+ _numEntries = 0;
+ }
+
+ public bool Remove(TKey key)
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+ int bucket = GetBucket(key);
+ Entry prev = null;
+ Entry entry = _buckets[bucket];
+ while (entry != null)
+ {
+ if (_comparer.Equals(key, entry.m_key))
+ {
+ if (prev == null)
+ {
+ _buckets[bucket] = entry.m_next;
+ }
+ else
+ {
+ prev.m_next = entry.m_next;
+ }
+ _version++;
+ _numEntries--;
+ return true;
+ }
+
+ prev = entry;
+ entry = entry.m_next;
+ }
+ return false;
+ }
+
+ internal TValue LookupOrAdd(TKey key, TValue value)
+ {
+ Entry entry = Find(key);
+ if (entry != null)
+ return entry.m_value;
+ UncheckedAdd(key, value);
+ return value;
+ }
+
+ private Entry Find(TKey key)
+ {
+ int bucket = GetBucket(key);
+ Entry entry = _buckets[bucket];
+ while (entry != null)
+ {
+ if (_comparer.Equals(key, entry.m_key))
+ return entry;
+
+ entry = entry.m_next;
+ }
+ return null;
+ }
+
+ private Entry UncheckedAdd(TKey key, TValue value)
+ {
+ Entry entry = new Entry();
+ entry.m_key = key;
+ entry.m_value = value;
+
+ int bucket = GetBucket(key);
+ entry.m_next = _buckets[bucket];
+ _buckets[bucket] = entry;
+
+ _numEntries++;
+ if (_numEntries > (_buckets.Length * 2))
+ ExpandBuckets();
+
+ return entry;
+ }
+
+
+ private void ExpandBuckets()
+ {
+ try
+ {
+ int newNumBuckets = _buckets.Length * 2 + 1;
+ Entry[] newBuckets = new Entry[newNumBuckets];
+ for (int i = 0; i < _buckets.Length; i++)
+ {
+ Entry entry = _buckets[i];
+ while (entry != null)
+ {
+ Entry nextEntry = entry.m_next;
+
+ int bucket = GetBucket(entry.m_key, newNumBuckets);
+ entry.m_next = newBuckets[bucket];
+ newBuckets[bucket] = entry;
+
+ entry = nextEntry;
+ }
+ }
+ _buckets = newBuckets;
+ }
+ catch (OutOfMemoryException)
+ {
+ }
+ }
+
+ private int GetBucket(TKey key, int numBuckets = 0)
+ {
+ int h = _comparer.GetHashCode(key);
+ h &= 0x7fffffff;
+ return (h % (numBuckets == 0 ? _buckets.Length : numBuckets));
+ }
+
+
+#if TYPE_LOADER_IMPLEMENTATION
+ [System.Runtime.CompilerServices.ForceDictionaryLookups]
+#endif
+ private sealed class Entry
+ {
+ public TKey m_key;
+ public TValue m_value;
+ public Entry m_next;
+ }
+
+ private Entry[] _buckets;
+ private int _numEntries;
+ private int _version;
+ private IEqualityComparer<TKey> _comparer;
+
+ // This comparator is used if no comparator is supplied. It emulates the behavior of EqualityComparer<T>.Default.
+#if TYPE_LOADER_IMPLEMENTATION
+ [System.Runtime.CompilerServices.ForceDictionaryLookups]
+#endif
+ private sealed class DefaultComparer<T> : IEqualityComparer<T>
+ {
+ public bool Equals(T x, T y)
+ {
+ if (x == null)
+ return y == null;
+ IEquatable<T> iequatable = x as IEquatable<T>;
+ if (iequatable != null)
+ return iequatable.Equals(y);
+ return ((object)x).Equals(y);
+ }
+
+ public int GetHashCode(T obj)
+ {
+ return ((object)obj).GetHashCode();
+ }
+ }
+
+#if TYPE_LOADER_IMPLEMENTATION
+ [System.Runtime.CompilerServices.ForceDictionaryLookups]
+#endif
+ protected sealed class LowLevelDictEnumerator : IEnumerator<KeyValuePair<TKey, TValue>>
+ {
+ public LowLevelDictEnumerator(LowLevelDictionary<TKey, TValue> dict)
+ {
+ _dict = dict;
+ _version = _dict._version;
+ Entry[] entries = new Entry[_dict._numEntries];
+ int dst = 0;
+ for (int bucket = 0; bucket < _dict._buckets.Length; bucket++)
+ {
+ Entry entry = _dict._buckets[bucket];
+ while (entry != null)
+ {
+ entries[dst++] = entry;
+ entry = entry.m_next;
+ }
+ }
+ _entries = entries;
+ Reset();
+ }
+
+ public KeyValuePair<TKey, TValue> Current
+ {
+ get
+ {
+ if (_version != _dict._version)
+ throw new InvalidOperationException("InvalidOperation_EnumFailedVersion");
+ if (_curPosition == -1 || _curPosition == _entries.Length)
+ throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen");
+ Entry entry = _entries[_curPosition];
+ return new KeyValuePair<TKey, TValue>(entry.m_key, entry.m_value);
+ }
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dict._version)
+ throw new InvalidOperationException("InvalidOperation_EnumFailedVersion");
+ if (_curPosition != _entries.Length)
+ _curPosition++;
+ bool anyMore = (_curPosition != _entries.Length);
+ return anyMore;
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ KeyValuePair<TKey, TValue> kv = Current;
+ return kv;
+ }
+ }
+
+ public void Reset()
+ {
+ if (_version != _dict._version)
+ throw new InvalidOperationException("InvalidOperation_EnumFailedVersion");
+ _curPosition = -1;
+ }
+
+ private LowLevelDictionary<TKey, TValue> _dict;
+ private Entry[] _entries;
+ private int _curPosition;
+ private int _version;
+ }
+ }
+
+ /// <summary>
+ /// LowLevelDictionary when enumeration is needed
+ /// </summary>
+ internal sealed class LowLevelDictionaryWithIEnumerable<TKey, TValue> : LowLevelDictionary<TKey, TValue>, IEnumerable<KeyValuePair<TKey, TValue>>
+ {
+ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+ {
+ return new LowLevelDictEnumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ IEnumerator<KeyValuePair<TKey, TValue>> ie = GetEnumerator();
+ return ie;
+ }
+ }
+}
diff --git a/src/Common/src/System/Collections/Generic/LowLevelList.cs b/src/Common/src/System/Collections/Generic/LowLevelList.cs
new file mode 100644
index 0000000000..8f70807e42
--- /dev/null
+++ b/src/Common/src/System/Collections/Generic/LowLevelList.cs
@@ -0,0 +1,597 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/*============================================================
+**
+** Class: LowLevelList<T>
+**
+** Private version of List<T> for internal System.Private.CoreLib use. This
+** permits sharing more source between BCL and System.Private.CoreLib (as well as the
+** fact that List<T> is just a useful class in general.)
+**
+** This does not strive to implement the full api surface area
+** (but any portion it does implement should match the real List<T>'s
+** behavior.)
+**
+** This file is a subset of System.Collections\System\Collections\Generics\List.cs
+** and should be kept in sync with that file.
+**
+===========================================================*/
+
+using System;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+
+namespace System.Collections.Generic
+{
+ // Implements a variable-size List that uses an array of objects to store the
+ // elements. A List has a capacity, which is the allocated length
+ // of the internal array. As elements are added to a List, the capacity
+ // of the List is automatically increased as required by reallocating the
+ // internal array.
+ //
+ /// <summary>
+ /// LowLevelList with no interface implementation to minimize both code and data size
+ /// Data size is smaller because there will be minimal virtual function table.
+ /// Code size is smaller because only functions called will be in the binary.
+ /// Use LowLevelListWithIList<T> for IList support
+ /// </summary>
+ [DebuggerDisplay("Count = {Count}")]
+#if TYPE_LOADER_IMPLEMENTATION
+ [System.Runtime.CompilerServices.ForceDictionaryLookups]
+#endif
+ internal class LowLevelList<T>
+ {
+ private const int _defaultCapacity = 4;
+
+ protected T[] _items;
+ [ContractPublicPropertyName("Count")]
+ protected int _size;
+ protected int _version;
+
+ private static readonly T[] s_emptyArray = new T[0];
+
+ // Constructs a List. The list is initially empty and has a capacity
+ // of zero. Upon adding the first element to the list the capacity is
+ // increased to 16, and then increased in multiples of two as required.
+ public LowLevelList()
+ {
+ _items = s_emptyArray;
+ }
+
+ // Constructs a List with a given initial capacity. The list is
+ // initially empty, but will have room for the given number of elements
+ // before any reallocations are required.
+ //
+ public LowLevelList(int capacity)
+ {
+ if (capacity < 0) throw new ArgumentOutOfRangeException("capacity");
+ Contract.EndContractBlock();
+
+ if (capacity == 0)
+ _items = s_emptyArray;
+ else
+ _items = new T[capacity];
+ }
+
+ // Constructs a List, copying the contents of the given collection. The
+ // size and capacity of the new list will both be equal to the size of the
+ // given collection.
+ //
+ public LowLevelList(IEnumerable<T> collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException("collection");
+ Contract.EndContractBlock();
+
+ ICollection<T> c = collection as ICollection<T>;
+ if (c != null)
+ {
+ int count = c.Count;
+ if (count == 0)
+ {
+ _items = s_emptyArray;
+ }
+ else
+ {
+ _items = new T[count];
+ c.CopyTo(_items, 0);
+ _size = count;
+ }
+ }
+ else
+ {
+ _size = 0;
+ _items = s_emptyArray;
+ // This enumerable could be empty. Let Add allocate a new array, if needed.
+ // Note it will also go to _defaultCapacity first, not 1, then 2, etc.
+
+ using (IEnumerator<T> en = collection.GetEnumerator())
+ {
+ while (en.MoveNext())
+ {
+ Add(en.Current);
+ }
+ }
+ }
+ }
+
+ // Gets and sets the capacity of this list. The capacity is the size of
+ // the internal array used to hold items. When set, the internal
+ // array of the list is reallocated to the given capacity.
+ //
+ public int Capacity
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<int>() >= 0);
+ return _items.Length;
+ }
+ set
+ {
+ if (value < _size)
+ {
+ throw new ArgumentOutOfRangeException("value");
+ }
+ Contract.EndContractBlock();
+
+ if (value != _items.Length)
+ {
+ if (value > 0)
+ {
+#if TYPE_LOADER_IMPLEMENTATION
+ T[] newItems = new T[value];
+ for (int i = 0; i < _size; i++)
+ newItems[i] = _items[i];
+ _items = newItems;
+#else
+ _items = ArrayT<T>.Resize(_items, value, _size);
+#endif
+ }
+ else
+ {
+ _items = s_emptyArray;
+ }
+ }
+ }
+ }
+
+ // Read-only property describing how many elements are in the List.
+ public int Count
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<int>() >= 0);
+ return _size;
+ }
+ }
+
+ // Sets or Gets the element at the given index.
+ //
+ public T this[int index]
+ {
+ get
+ {
+ // Following trick can reduce the range check by one
+ if ((uint)index >= (uint)_size)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ Contract.EndContractBlock();
+ return _items[index];
+ }
+
+ set
+ {
+ if ((uint)index >= (uint)_size)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ Contract.EndContractBlock();
+ _items[index] = value;
+ _version++;
+ }
+ }
+
+
+ // Adds the given object to the end of this list. The size of the list is
+ // increased by one. If required, the capacity of the list is doubled
+ // before adding the new element.
+ //
+ public void Add(T item)
+ {
+ if (_size == _items.Length) EnsureCapacity(_size + 1);
+ _items[_size++] = item;
+ _version++;
+ }
+
+ // Ensures that the capacity of this list is at least the given minimum
+ // value. If the currect capacity of the list is less than min, the
+ // capacity is increased to twice the current capacity or to min,
+ // whichever is larger.
+ private void EnsureCapacity(int min)
+ {
+ if (_items.Length < min)
+ {
+ int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;
+ // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
+ // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
+ //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
+ if (newCapacity < min) newCapacity = min;
+ Capacity = newCapacity;
+ }
+ }
+
+#if !TYPE_LOADER_IMPLEMENTATION
+ // Adds the elements of the given collection to the end of this list. If
+ // required, the capacity of the list is increased to twice the previous
+ // capacity or the new size, whichever is larger.
+ //
+ public void AddRange(IEnumerable<T> collection)
+ {
+ Contract.Ensures(Count >= Contract.OldValue(Count));
+
+ InsertRange(_size, collection);
+ }
+
+ // Clears the contents of List.
+ public void Clear()
+ {
+ if (_size > 0)
+ {
+ ArrayT<T>.Clear(_items, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references.
+ _size = 0;
+ }
+ _version++;
+ }
+
+ // Contains returns true if the specified element is in the List.
+ // It does a linear, O(n) search. Equality is determined by calling
+ // item.Equals().
+ //
+ public bool Contains(T item)
+ {
+ if ((object)item == null)
+ {
+ for (int i = 0; i < _size; i++)
+ if ((object)_items[i] == null)
+ return true;
+ return false;
+ }
+ else
+ {
+ int index = IndexOf(item);
+ if (index >= 0)
+ return true;
+ return false;
+ }
+ }
+
+
+ // Copies a section of this list to the given array at the given index.
+ //
+ // The method uses the Array.Copy method to copy the elements.
+ //
+ public void CopyTo(int index, T[] array, int arrayIndex, int count)
+ {
+ if (_size - index < count)
+ {
+ throw new ArgumentException();
+ }
+ Contract.EndContractBlock();
+
+ // Delegate rest of error checking to Array.Copy.
+ ArrayT<T>.Copy(_items, index, array, arrayIndex, count);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ // Delegate rest of error checking to Array.Copy.
+ ArrayT<T>.Copy(_items, 0, array, arrayIndex, _size);
+ }
+
+ // Returns the index of the first occurrence of a given value in a range of
+ // this list. The list is searched forwards from beginning to end.
+ // The elements of the list are compared to the given value using the
+ // Object.Equals method.
+ //
+ // This method uses the Array.IndexOf method to perform the
+ // search.
+ //
+ public int IndexOf(T item)
+ {
+ Contract.Ensures(Contract.Result<int>() >= -1);
+ Contract.Ensures(Contract.Result<int>() < Count);
+ return Array.IndexOf(_items, item, 0, _size);
+ }
+
+
+ // Returns the index of the first occurrence of a given value in a range of
+ // this list. The list is searched forwards, starting at index
+ // index and ending at count number of elements. The
+ // elements of the list are compared to the given value using the
+ // Object.Equals method.
+ //
+ // This method uses the Array.IndexOf method to perform the
+ // search.
+ //
+ public int IndexOf(T item, int index)
+ {
+ if (index > _size)
+ throw new ArgumentOutOfRangeException("index");
+ Contract.Ensures(Contract.Result<int>() >= -1);
+ Contract.Ensures(Contract.Result<int>() < Count);
+ Contract.EndContractBlock();
+ return Array.IndexOf(_items, item, index, _size - index);
+ }
+
+ // Inserts an element into this list at a given index. The size of the list
+ // is increased by one. If required, the capacity of the list is doubled
+ // before inserting the new element.
+ //
+ public void Insert(int index, T item)
+ {
+ // Note that insertions at the end are legal.
+ if ((uint)index > (uint)_size)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ Contract.EndContractBlock();
+ if (_size == _items.Length) EnsureCapacity(_size + 1);
+ if (index < _size)
+ {
+ ArrayT<T>.Copy(_items, index, _items, index + 1, _size - index);
+ }
+ _items[index] = item;
+ _size++;
+ _version++;
+ }
+
+ // Inserts the elements of the given collection at a given index. If
+ // required, the capacity of the list is increased to twice the previous
+ // capacity or the new size, whichever is larger. Ranges may be added
+ // to the end of the list by setting index to the List's size.
+ //
+ public void InsertRange(int index, IEnumerable<T> collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+
+ if ((uint)index > (uint)_size)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ Contract.EndContractBlock();
+
+ ICollection<T> c = collection as ICollection<T>;
+ if (c != null)
+ { // if collection is ICollection<T>
+ int count = c.Count;
+ if (count > 0)
+ {
+ EnsureCapacity(_size + count);
+ if (index < _size)
+ {
+ ArrayT<T>.Copy(_items, index, _items, index + count, _size - index);
+ }
+
+ // If we're inserting a List into itself, we want to be able to deal with that.
+ if (this == c)
+ {
+ // Copy first part of _items to insert location
+ ArrayT<T>.Copy(_items, 0, _items, index, index);
+ // Copy last part of _items back to inserted location
+ ArrayT<T>.Copy(_items, index + count, _items, index * 2, _size - index);
+ }
+ else
+ {
+ T[] itemsToInsert = new T[count];
+ c.CopyTo(itemsToInsert, 0);
+ ArrayT<T>.Copy(itemsToInsert, 0, _items, index, count);
+ }
+ _size += count;
+ }
+ }
+ else
+ {
+ using (IEnumerator<T> en = collection.GetEnumerator())
+ {
+ while (en.MoveNext())
+ {
+ Insert(index++, en.Current);
+ }
+ }
+ }
+ _version++;
+ }
+
+ // Removes the element at the given index. The size of the list is
+ // decreased by one.
+ //
+ public bool Remove(T item)
+ {
+ int index = IndexOf(item);
+ if (index >= 0)
+ {
+ RemoveAt(index);
+ return true;
+ }
+
+ return false;
+ }
+
+ // This method removes all items which matches the predicate.
+ // The complexity is O(n).
+ public int RemoveAll(Predicate<T> match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+ Contract.Ensures(Contract.Result<int>() >= 0);
+ Contract.Ensures(Contract.Result<int>() <= Contract.OldValue(Count));
+ Contract.EndContractBlock();
+
+ int freeIndex = 0; // the first free slot in items array
+
+ // Find the first item which needs to be removed.
+ while (freeIndex < _size && !match(_items[freeIndex])) freeIndex++;
+ if (freeIndex >= _size) return 0;
+
+ int current = freeIndex + 1;
+ while (current < _size)
+ {
+ // Find the first item which needs to be kept.
+ while (current < _size && match(_items[current])) current++;
+
+ if (current < _size)
+ {
+ // copy item to the free slot.
+ _items[freeIndex++] = _items[current++];
+ }
+ }
+
+ ArrayT<T>.Clear(_items, freeIndex, _size - freeIndex);
+ int result = _size - freeIndex;
+ _size = freeIndex;
+ _version++;
+ return result;
+ }
+
+ // Removes the element at the given index. The size of the list is
+ // decreased by one.
+ //
+ public void RemoveAt(int index)
+ {
+ if ((uint)index >= (uint)_size)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ Contract.EndContractBlock();
+ _size--;
+ if (index < _size)
+ {
+ ArrayT<T>.Copy(_items, index + 1, _items, index, _size - index);
+ }
+ _items[_size] = default(T);
+ _version++;
+ }
+
+ // ToArray returns a new Object array containing the contents of the List.
+ // This requires copying the List, which is an O(n) operation.
+ public T[] ToArray()
+ {
+ Contract.Ensures(Contract.Result<T[]>() != null);
+ Contract.Ensures(Contract.Result<T[]>().Length == Count);
+
+ T[] array = new T[_size];
+ ArrayT<T>.Copy(_items, 0, array, 0, _size);
+ return array;
+ }
+#endif
+ }
+
+#if !TYPE_LOADER_IMPLEMENTATION
+ /// <summary>
+ /// LowLevelList<T> with full IList<T> implementation
+ /// </summary>
+ internal sealed class LowLevelListWithIList<T> : LowLevelList<T>, IList<T>
+ {
+ // Is this List read-only?
+ bool ICollection<T>.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ /// <internalonly/>
+ IEnumerator<T> IEnumerable<T>.GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ private struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
+ {
+ private LowLevelListWithIList<T> _list;
+ private int _index;
+ private int _version;
+ private T _current;
+
+ internal Enumerator(LowLevelListWithIList<T> list)
+ {
+ _list = list;
+ _index = 0;
+ _version = list._version;
+ _current = default(T);
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ LowLevelListWithIList<T> localList = _list;
+
+ if (_version == localList._version && ((uint)_index < (uint)localList._size))
+ {
+ _current = localList._items[_index];
+ _index++;
+ return true;
+ }
+ return MoveNextRare();
+ }
+
+ private bool MoveNextRare()
+ {
+ if (_version != _list._version)
+ {
+ throw new InvalidOperationException();
+ }
+
+ _index = _list._size + 1;
+ _current = default(T);
+ return false;
+ }
+
+ public T Current
+ {
+ get
+ {
+ return _current;
+ }
+ }
+
+ object System.Collections.IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || _index == _list._size + 1)
+ {
+ throw new InvalidOperationException();
+ }
+ return Current;
+ }
+ }
+
+ void System.Collections.IEnumerator.Reset()
+ {
+ if (_version != _list._version)
+ {
+ throw new InvalidOperationException();
+ }
+
+ _index = 0;
+ _current = default(T);
+ }
+ }
+ }
+#endif // !TYPE_LOADER_IMPLEMENTATION
+}
+
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.Unix.cs b/src/Common/src/System/Diagnostics/Debug.Unix.cs
index 0089f2aacb..0089f2aacb 100644
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.Unix.cs
+++ b/src/Common/src/System/Diagnostics/Debug.Unix.cs
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.Windows.cs b/src/Common/src/System/Diagnostics/Debug.Windows.cs
index 3e14cce00f..9620632f14 100644
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.Windows.cs
+++ b/src/Common/src/System/Diagnostics/Debug.Windows.cs
@@ -6,7 +6,11 @@ using System.Threading;
namespace System.Diagnostics
{
+#if PUBLIC_DEBUG
public static partial class Debug
+#else
+ internal static partial class Debug
+#endif
{
// internal and not read only so that the tests can swap this out.
internal static IDebugLogger s_logger = new WindowsDebugLogger();
@@ -22,37 +26,19 @@ namespace System.Diagnostics
{
string fullMessage = message + Environment.NewLine + detailMessage + Environment.NewLine + stackTrace;
- int flags = Interop.User32.MB_OKCANCEL | Interop.User32.MB_ICONHAND | Interop.User32.MB_TOPMOST;
-
- if (IsRTLResources)
+ Debug.WriteLine(fullMessage);
+ if (Debugger.IsAttached)
{
- flags = flags | Interop.User32.MB_RIGHT | Interop.User32.MB_RTLREADING;
+ Debugger.Break();
}
-
- int rval = 0;
-
- // Run the message box on its own thread.
- rval = new MessageBoxPopup(fullMessage, SR.DebugAssertTitle, flags).ShowMessageBox();
-
- switch (rval)
+ else
{
- case Interop.User32.IDCANCEL:
- if (!System.Diagnostics.Debugger.IsAttached)
- {
- System.Diagnostics.Debugger.Launch();
- }
- System.Diagnostics.Debugger.Break();
- break;
- default:
- Debug.Assert(rval == Interop.User32.IDOK);
- break;
+ Environment.FailFast(fullMessage);
}
}
public void WriteCore(string message)
{
- Assert(message != null);
-
// really huge messages mess up both VS and dbmon, so we chop it up into
// reasonable chunks if it's too big. This is the number of characters
// that OutputDebugstring chunks at.
@@ -61,7 +47,7 @@ namespace System.Diagnostics
// We don't want output from multiple threads to be interleaved.
lock (s_ForLock)
{
- if (message.Length <= WriteChunkLength)
+ if (message == null || message.Length <= WriteChunkLength)
{
WriteToDebugger(message);
}
@@ -80,45 +66,13 @@ namespace System.Diagnostics
[System.Security.SecuritySafeCritical]
private static void WriteToDebugger(string message)
{
- Interop.mincore.OutputDebugString(message);
- }
-
- private static bool IsRTLResources
- {
- get
+ if (Debugger.IsLogging())
{
- return SR.RTL != "RTL_False";
+ Debugger.Log(0, null, message);
}
- }
-
- internal class MessageBoxPopup
- {
- private readonly string _body;
- private readonly string _title;
- private readonly int _flags;
- private int _returnValue;
-
-
- [SecurityCritical]
- public MessageBoxPopup(string body, string title, int flags)
- {
- _body = body;
- _title = title;
- _flags = flags;
- }
-
- public int ShowMessageBox()
- {
- Thread t = new Thread(DoPopup);
- t.Start();
- t.Join();
- return _returnValue;
- }
-
- [SecuritySafeCritical]
- public void DoPopup()
+ else
{
- _returnValue = Interop.User32.MessageBox(IntPtr.Zero, _body, _title, _flags);
+ Interop.mincore.OutputDebugString(message ?? string.Empty);
}
}
}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.cs b/src/Common/src/System/Diagnostics/Debug.cs
index 6dc6c72d68..b70ebfe563 100644
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debug.cs
+++ b/src/Common/src/System/Diagnostics/Debug.cs
@@ -1,12 +1,14 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#define DEBUG // Do not remove this, it is needed to retain calls to these conditional methods in release builds
+
namespace System.Diagnostics
{
/// <summary>
/// Provides a set of properties and methods for debugging code.
/// </summary>
- public static partial class Debug
+ static partial class Debug
{
private static readonly object s_ForLock = new Object();
diff --git a/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj b/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj
index 7b10eeea3a..88dc331030 100644
--- a/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj
+++ b/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj
@@ -7,7 +7,10 @@
<ProjectGuid>{E7E8DE8A-9EC1-46A8-A6EE-727DB32DBEB8}</ProjectGuid>
<OutputType>Library</OutputType>
<AssemblyName>System.Diagnostics.Debug</AssemblyName>
- <RootNamespace>System.Diagnostics.Debug</RootNamespace>
+ <AssemblyVersion>4.0.10.0</AssemblyVersion>
+ <DefineConstants>$(DefineConstants);PUBLIC_DEBUG</DefineConstants>
+ <IsPartialFacadeAssembly>true</IsPartialFacadeAssembly>
+ <NuGetTargetFrameworkMoniker>ASP.NetCore, version=v5.0</NuGetTargetFrameworkMoniker>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetsUnix)' == 'true' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -21,30 +24,25 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Release|AnyCPU' " />
<ItemGroup>
<Compile Include="Properties\InternalsVisibleTo.cs" />
- <Compile Include="System\Diagnostics\Debug.cs" />
- <Compile Include="System\Diagnostics\Debugger.cs" />
- <Compile Include="System\Diagnostics\DebuggerBrowsableAttribute.cs" />
- <Compile Include="System\Diagnostics\DebuggerBrowsableState.cs" />
- <Compile Include="System\Diagnostics\DebuggerDisplayAttribute.cs" />
- <Compile Include="System\Diagnostics\DebuggerHiddenAttribute.cs" />
- <Compile Include="System\Diagnostics\DebuggerNonUserCodeAttribute.cs" />
- <Compile Include="System\Diagnostics\DebuggerStepThroughAttribute.cs" />
- <Compile Include="System\Diagnostics\DebuggerTypeProxyAttribute.cs" />
+ <Compile Include="$(CommonPath)\System\Diagnostics\Debug.cs">
+ <Link>System\Diagnostics\Debug.cs</Link>
+ </Compile>
</ItemGroup>
<ItemGroup Condition=" '$(TargetsWindows)' == 'true' ">
- <Compile Include="System\Diagnostics\Debug.Windows.cs" />
+ <Compile Include="$(CommonPath)\System\Diagnostics\Debug.Windows.cs">
+ <Link>System\Diagnostics\Debug.Windows.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
<Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.OutputDebugString.cs">
<Link>Common\Interop\Windows\mincore\Interop.OutputDebugString.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\Interop\Windows\user32\Interop.MessageBox.cs">
- <Link>Common\Interop\Windows\user32\Interop.MessageBox.cs</Link>
- </Compile>
</ItemGroup>
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
- <Compile Include="System\Diagnostics\Debug.Unix.cs" />
+ <Compile Include="$(CommonPath)\System\Diagnostics\Debug.Unix.cs">
+ <Link>System\Diagnostics\Debug.Unix.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Unix\Interop.Devices.cs">
<Link>Common\Interop\Unix\Interop.Devices.cs</Link>
</Compile>
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debugger.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/Debugger.cs
deleted file mode 100644
index fc37dc5031..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/Debugger.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace System.Diagnostics
-{
- // This is a stub implementation of the Debugger class. The implementation of this class is not portable
- // since each runtime will have a different way of interacting with the debugger. The long term plan is
- // to make System.Diagnostics.Debug a partial facade over mscorlib. Once that is done, this type will be
- // removed and instead we will have a type forward to the implementation in mscorlib.
- public static class Debugger
- {
- public static bool IsAttached
- {
- get { return false; }
-
- }
-
- public static void Break()
- {
- throw new NotImplementedException();
- }
-
- public static bool Launch()
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableAttribute.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableAttribute.cs
deleted file mode 100644
index 96a0fe6623..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableAttribute.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System.Diagnostics.Contracts;
-
-namespace System.Diagnostics
-{
- // the one currently supported with the csee.dat
- // (mcee.dat, autoexp.dat) file.
- [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
- public sealed class DebuggerBrowsableAttribute : Attribute
- {
- private readonly DebuggerBrowsableState _state;
- public DebuggerBrowsableAttribute(DebuggerBrowsableState state)
- {
- if (state < DebuggerBrowsableState.Never || state > DebuggerBrowsableState.RootHidden)
- throw new ArgumentOutOfRangeException("state");
- Contract.EndContractBlock();
-
- this._state = state;
- }
- public DebuggerBrowsableState State
- {
- get { return _state; }
- }
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableState.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableState.cs
deleted file mode 100644
index 4e63697e19..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerBrowsableState.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace System.Diagnostics
-{
- // DebuggerBrowsableState states are defined as follows:
- // Never never show this element
- // Expanded expansion of the class is done, so that all visible internal members are shown
- // Collapsed expansion of the class is not performed. Internal visible members are hidden
- // RootHidden The target element itself should not be shown, but should instead be
- // automatically expanded to have its members displayed.
- // Default value is collapsed
-
- // Please also change the code which validates DebuggerBrowsableState variable (in DebuggerBrowseableState)
- // if you change this enum.
- public enum DebuggerBrowsableState
- {
- Never = 0,
- //Expanded is not supported in this release
- //Expanded = 1,
- Collapsed = 2,
- RootHidden = 3
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerDisplayAttribute.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerDisplayAttribute.cs
deleted file mode 100644
index 4c88fefdeb..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerDisplayAttribute.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System.Diagnostics.Contracts;
-
-namespace System.Diagnostics
-{
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Assembly, AllowMultiple = true)]
- public sealed class DebuggerDisplayAttribute : Attribute
- {
- private readonly string _value;
-
- private string _name;
- private string _type;
- private string _targetName;
- private Type _target;
-
- public DebuggerDisplayAttribute(string value)
- {
- if (value == null)
- {
- this._value = "";
- }
- else
- {
- this._value = value;
- }
- _name = "";
- _type = "";
- }
-
- public string Value
- {
- get { return this._value; }
- }
-
- public string Name
- {
- get { return _name; }
- set { _name = value; }
- }
-
- public string Type
- {
- get { return _type; }
- set { _type = value; }
- }
-
- public Type Target
- {
- get { return _target; }
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException("value");
- }
- Contract.EndContractBlock();
-
- _targetName = value.AssemblyQualifiedName;
- _target = value;
- }
- }
-
- public string TargetTypeName
- {
- get { return _targetName; }
- set { _targetName = value; }
- }
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerHiddenAttribute.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerHiddenAttribute.cs
deleted file mode 100644
index 17f8593cb1..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerHiddenAttribute.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace System.Diagnostics
-{
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, Inherited = false)]
- public sealed class DebuggerHiddenAttribute : Attribute
- {
- public DebuggerHiddenAttribute() { }
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerNonUserCodeAttribute.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerNonUserCodeAttribute.cs
deleted file mode 100644
index 84c03af609..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerNonUserCodeAttribute.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace System.Diagnostics
-{
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)]
- public sealed class DebuggerNonUserCodeAttribute : Attribute
- {
- public DebuggerNonUserCodeAttribute() { }
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerStepThroughAttribute.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerStepThroughAttribute.cs
deleted file mode 100644
index cb46204d26..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerStepThroughAttribute.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace System.Diagnostics
-{
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)]
- public sealed class DebuggerStepThroughAttribute : Attribute
- {
- public DebuggerStepThroughAttribute() { }
- }
-
-}
diff --git a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerTypeProxyAttribute.cs b/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerTypeProxyAttribute.cs
deleted file mode 100644
index 8af6f62d24..0000000000
--- a/src/System.Diagnostics.Debug/src/System/Diagnostics/DebuggerTypeProxyAttribute.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System.Diagnostics.Contracts;
-
-namespace System.Diagnostics
-{
- [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
- public sealed class DebuggerTypeProxyAttribute : Attribute
- {
- private readonly string _typeName;
- private string _targetName;
- private Type _target;
-
- public DebuggerTypeProxyAttribute(Type type)
- {
- if (type == null)
- {
- throw new ArgumentNullException("type");
- }
- Contract.EndContractBlock();
-
- this._typeName = type.AssemblyQualifiedName;
- }
-
- public DebuggerTypeProxyAttribute(string typeName)
- {
- this._typeName = typeName;
- }
- public string ProxyTypeName
- {
- get { return _typeName; }
- }
-
- public Type Target
- {
- get { return _target; }
-
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException("value");
- }
- Contract.EndContractBlock();
-
- _targetName = value.AssemblyQualifiedName;
- _target = value;
- }
- }
-
- public string TargetTypeName
- {
- get { return _targetName; }
- set { _targetName = value; }
- }
- }
-}
diff --git a/src/System.Diagnostics.Debug/src/project.json b/src/System.Diagnostics.Debug/src/project.json
index db682a6896..54912e4a28 100644
--- a/src/System.Diagnostics.Debug/src/project.json
+++ b/src/System.Diagnostics.Debug/src/project.json
@@ -1,19 +1,5 @@
{
"dependencies": {
- "System.Diagnostics.Contracts": "4.0.0-beta-22809",
- "System.Diagnostics.Tools": "4.0.0-beta-22809",
- "System.Globalization": "4.0.10-beta-22809",
- "System.IO": "4.0.10-beta-22809",
- "System.Reflection": "4.0.10-beta-22809",
- "System.Reflection.Primitives": "4.0.0-beta-22809",
- "System.Resources.ResourceManager": "4.0.0-beta-22809",
- "System.Runtime": "4.0.20-beta-22809",
- "System.Runtime.Extensions": "4.0.10-beta-22809",
- "System.Runtime.Handles": "4.0.0-beta-22809",
- "System.Runtime.InteropServices": "4.0.20-beta-22809",
- "System.Text.Encoding": "4.0.10-beta-22809",
- "System.Threading": "4.0.10-beta-22809",
- "System.Threading.Tasks": "4.0.10-beta-22809",
- "System.Threading.Thread": "4.0.0-beta-22809"
+ "Microsoft.DotNet.CoreCLR": "1.0.0-prerelease"
}
} \ No newline at end of file
diff --git a/src/System.Diagnostics.Debug/tests/DebuggerTests.cs b/src/System.Diagnostics.Debug/tests/DebuggerTests.cs
index 631d77f9b2..2af68c7264 100644
--- a/src/System.Diagnostics.Debug/tests/DebuggerTests.cs
+++ b/src/System.Diagnostics.Debug/tests/DebuggerTests.cs
@@ -11,21 +11,18 @@ namespace System.Diagnostics.Tests
public void IsAttached()
{
// TODO: Implement this when Debugger is properly implemented
- Assert.False(Debugger.IsAttached);
}
[Fact]
public void Launch()
{
// TODO: Implement this when Debugger is properly implemented
- Assert.Throws<NotImplementedException>(() => Debugger.Launch());
}
[Fact]
public void Break()
{
// TODO: Implement this when Debugger is properly implemented
- Assert.Throws<NotImplementedException>(() => Debugger.Break());
}
}
}
diff --git a/src/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj b/src/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj
index c054762990..1cbd4fdb2a 100644
--- a/src/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj
+++ b/src/System.Diagnostics.Debug/tests/System.Diagnostics.Debug.Tests.csproj
@@ -8,6 +8,10 @@
<OutputType>Library</OutputType>
<AssemblyName>System.Diagnostics.Debug.Tests</AssemblyName>
<RootNamespace>System.Diagnostics.Tests</RootNamespace>
+ <NuGetTargetFrameworkMoniker>ASP.NetCore, version=v5.0</NuGetTargetFrameworkMoniker>
+ <IgnoreArchitectureMismatches>true</IgnoreArchitectureMismatches>
+ <PostFilterNugetReferences>true</PostFilterNugetReferences>
+ <NoWarn>1685</NoWarn>
</PropertyGroup>
<!-- Default configurations to help VS understand the configurations -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@@ -23,6 +27,7 @@
<Project>{E7E8DE8A-9EC1-46A8-A6EE-727DB32DBEB8}</Project>
<Name>System.Diagnostics.Debug</Name>
</ProjectReference>
+ <ProjectReference Include="..\..\System.Runtime\src\System.Runtime.CoreCLR.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
diff --git a/src/System.Diagnostics.Debug/tests/project.json b/src/System.Diagnostics.Debug/tests/project.json
index db682a6896..7e514bcf34 100644
--- a/src/System.Diagnostics.Debug/tests/project.json
+++ b/src/System.Diagnostics.Debug/tests/project.json
@@ -1,19 +1,6 @@
{
"dependencies": {
- "System.Diagnostics.Contracts": "4.0.0-beta-22809",
- "System.Diagnostics.Tools": "4.0.0-beta-22809",
- "System.Globalization": "4.0.10-beta-22809",
- "System.IO": "4.0.10-beta-22809",
- "System.Reflection": "4.0.10-beta-22809",
- "System.Reflection.Primitives": "4.0.0-beta-22809",
- "System.Resources.ResourceManager": "4.0.0-beta-22809",
- "System.Runtime": "4.0.20-beta-22809",
- "System.Runtime.Extensions": "4.0.10-beta-22809",
- "System.Runtime.Handles": "4.0.0-beta-22809",
- "System.Runtime.InteropServices": "4.0.20-beta-22809",
- "System.Text.Encoding": "4.0.10-beta-22809",
- "System.Threading": "4.0.10-beta-22809",
- "System.Threading.Tasks": "4.0.10-beta-22809",
- "System.Threading.Thread": "4.0.0-beta-22809"
+ "Microsoft.DotNet.CoreCLR": "1.0.0-prerelease",
+ "System.Threading.Tasks": "4.0.10-beta-22809"
}
} \ No newline at end of file
diff --git a/src/System.Private.Uri/src/Resources/Strings.resx b/src/System.Private.Uri/src/Resources/Strings.resx
new file mode 100644
index 0000000000..961b75c1cf
--- /dev/null
+++ b/src/System.Private.Uri/src/Resources/Strings.resx
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
+ <value>Non-negative number required.</value>
+ </data>
+ <data name="ArgumentOutOfRange_Index" xml:space="preserve">
+ <value>Index was out of range. Must be non-negative and less than the size of the collection.</value>
+ </data>
+ <data name="ArgumentOutOfRange_IndexCountBuffer" xml:space="preserve">
+ <value>Index and count must refer to a location within the buffer.</value>
+ </data>
+ <data name="Argument_AddingDuplicate" xml:space="preserve">
+ <value>An item with the same key has already been added.</value>
+ </data>
+ <data name="Argument_IdnBadLabelSize" xml:space="preserve">
+ <value>IDN labels must be between 1 and 63 characters long.</value>
+ </data>
+ <data name="Argument_IdnBadPunycode" xml:space="preserve">
+ <value>Invalid IDN encoded string.</value>
+ </data>
+ <data name="Argument_IdnIllegalName" xml:space="preserve">
+ <value>Decoded string is not a valid IDN name.</value>
+ </data>
+ <data name="Argument_InvalidCharSequence" xml:space="preserve">
+ <value>Invalid Unicode code point found at index {0}.</value>
+ </data>
+ <data name="Argument_InvalidCharSequenceNoIndex" xml:space="preserve">
+ <value>String contains invalid Unicode code points.</value>
+ </data>
+ <data name="Arg_OutOfMemoryException" xml:space="preserve">
+ <value>Insufficient memory to continue the execution of the program.</value>
+ </data>
+ <data name="UnknownError_Num" xml:space="preserve">
+ <value>Unknown error '{0}'.</value>
+ </data>
+ <data name="net_uri_AlreadyRegistered" xml:space="preserve">
+ <value>A URI scheme name '{0}' already has a registered custom parser.</value>
+ </data>
+ <data name="net_uri_BadAuthority" xml:space="preserve">
+ <value>Invalid URI: The Authority/Host could not be parsed.</value>
+ </data>
+ <data name="net_uri_BadAuthorityTerminator" xml:space="preserve">
+ <value>Invalid URI: The Authority/Host cannot end with a backslash character ('\\').</value>
+ </data>
+ <data name="net_uri_BadFormat" xml:space="preserve">
+ <value>Invalid URI: The format of the URI could not be determined.</value>
+ </data>
+ <data name="net_uri_BadHostName" xml:space="preserve">
+ <value>Invalid URI: The hostname could not be parsed.</value>
+ </data>
+ <data name="net_uri_BadPort" xml:space="preserve">
+ <value>Invalid URI: Invalid port specified.</value>
+ </data>
+ <data name="net_uri_BadScheme" xml:space="preserve">
+ <value>Invalid URI: The URI scheme is not valid.</value>
+ </data>
+ <data name="net_uri_BadString" xml:space="preserve">
+ <value>Invalid URI: There is an invalid sequence in the string.</value>
+ </data>
+ <data name="net_uri_BadUserPassword" xml:space="preserve">
+ <value>Invalid URI: The username:password construct is badly formed.</value>
+ </data>
+ <data name="net_uri_CannotCreateRelative" xml:space="preserve">
+ <value>A relative URI cannot be created because the 'uriString' parameter represents an absolute URI.</value>
+ </data>
+ <data name="net_uri_SchemeLimit" xml:space="preserve">
+ <value>Invalid URI: The Uri scheme is too long.</value>
+ </data>
+ <data name="net_uri_EmptyUri" xml:space="preserve">
+ <value>Invalid URI: The URI is empty.</value>
+ </data>
+ <data name="net_uri_InvalidUriKind" xml:space="preserve">
+ <value>The value '{0}' passed for the UriKind parameter is invalid.</value>
+ </data>
+ <data name="net_uri_MustRootedPath" xml:space="preserve">
+ <value>Invalid URI: A Dos path must be rooted, for example, 'c:\\'.</value>
+ </data>
+ <data name="net_uri_NeedFreshParser" xml:space="preserve">
+ <value>The URI parser instance passed into 'uriParser' parameter is already registered with the scheme name '{0}'.</value>
+ </data>
+ <data name="net_uri_NotAbsolute" xml:space="preserve">
+ <value>This operation is not supported for a relative URI.</value>
+ </data>
+ <data name="net_uri_PortOutOfRange" xml:space="preserve">
+ <value>A derived type '{0}' has reported an invalid value for the Uri port '{1}'.</value>
+ </data>
+ <data name="net_uri_SizeLimit" xml:space="preserve">
+ <value>Invalid URI: The Uri string is too long.</value>
+ </data>
+ <data name="net_uri_UserDrivenParsing" xml:space="preserve">
+ <value>A derived type '{0}' is responsible for parsing this Uri instance. The base implementation must not be used.</value>
+ </data>
+ <data name="net_uri_NotJustSerialization" xml:space="preserve">
+ <value>UriComponents.SerializationInfoString must not be combined with other UriComponents.</value>
+ </data>
+ <data name="net_uri_BadUnicodeHostForIdn" xml:space="preserve">
+ <value>An invalid Unicode character by IDN standards was specified in the host.</value>
+ </data>
+ <data name="AssertionFailed" xml:space="preserve">
+ <value>Assertion failed.</value>
+ </data>
+ <data name="Debug_Fail" xml:space="preserve">
+ <value>Fail: {0}</value>
+ </data>
+ <data name="DebugAssertBanner" xml:space="preserve">
+ <value>---- DEBUG ASSERTION FAILED ----</value>
+ </data>
+ <data name="DebugAssertLongMessage" xml:space="preserve">
+ <value>---- Assert Long Message ----</value>
+ </data>
+ <data name="DebugAssertShortMessage" xml:space="preserve">
+ <value>---- Assert Short Message ----</value>
+ </data>
+ <data name="IO_FileNotFound" xml:space="preserve">
+ <value>Unable to find the specified file.</value>
+ </data>
+ <data name="IO_FileNotFound_FileName" xml:space="preserve">
+ <value>Could not find file '{0}'.</value>
+ </data>
+ <data name="IO_PathNotFound_NoPathName" xml:space="preserve">
+ <value>Could not find a part of the path.</value>
+ </data>
+ <data name="IO_PathNotFound_Path" xml:space="preserve">
+ <value>Could not find a part of the path '{0}'.</value>
+ </data>
+ <data name="IO_SharingViolation_File" xml:space="preserve">
+ <value>The process cannot access the file '{0}' because it is being used by another process.</value>
+ </data>
+ <data name="IO_SharingViolation_NoFileName" xml:space="preserve">
+ <value>The process cannot access the file because it is being used by another process.</value>
+ </data>
+ <data name="UnauthorizedAccess_IODenied_NoPathName" xml:space="preserve">
+ <value>Access to the path is denied.</value>
+ </data>
+ <data name="UnauthorizedAccess_IODenied_Path" xml:space="preserve">
+ <value>Access to the path '{0}' is denied.</value>
+ </data>
+ <data name="ArgumentOutOfRange_FileLengthTooBig" xml:space="preserve">
+ <value>Specified file length was too large for the file system.</value>
+ </data>
+ <data name="IO_FileExists_Name" xml:space="preserve">
+ <value>The file '{0}' already exists.</value>
+ </data>
+ <data name="IO_PathTooLong" xml:space="preserve">
+ <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/System.Private.Uri/src/System.Private.Uri.CoreCLR.csproj b/src/System.Private.Uri/src/System.Private.Uri.CoreCLR.csproj
new file mode 100644
index 0000000000..b9c38dc8ce
--- /dev/null
+++ b/src/System.Private.Uri/src/System.Private.Uri.CoreCLR.csproj
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+
+ <PropertyGroup>
+ <AssemblyName>System.Private.Uri</AssemblyName>
+ <AssemblyVersion>4.0.0.0</AssemblyVersion>
+ <OutputType>Library</OutputType>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <DefineConstants>$(DefineConstants);INTERNAL_GLOBALIZATION_EXTENSIONS</DefineConstants>
+ <NugetTargetFrameworkMoniker>ASP.NetCore, version=v5.0</NugetTargetFrameworkMoniker>
+ <IgnoreArchitectureMismatches>true</IgnoreArchitectureMismatches>
+ <PostFilterNugetReferences>true</PostFilterNugetReferences>
+ </PropertyGroup>
+
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Debug|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Release|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Debug|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Release|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Debug|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Release|AnyCPU' " />
+
+ <ItemGroup>
+ <Compile Include="$(CommonPath)\System\Diagnostics\Debug.cs">
+ <Link>Common\System\Diagnostics\Debug.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\ArrayT.cs">
+ <Link>Common\System\ArrayT.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Collections\Generic\LowLevelDictionary.cs">
+ <Link>Common\System\Collections\Generic\LowLevelDictionary.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Collections\Generic\LowLevelList.cs">
+ <Link>Common\System\Collections\Generic\LowLevelList.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\StringNormalizationExtensions.cs">
+ <Link>Common\System\StringNormalizationExtensions.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Globalization\IdnMapping.cs">
+ <Link>Common\System\Globalization\IdnMapping.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Text\NormalizationForm.cs">
+ <Link>Common\System\Text\NormalizationForm.cs</Link>
+ </Compile>
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Include="System\DomainNameHelper.cs" />
+ <Compile Include="System\IPv4AddressHelper.cs" />
+ <Compile Include="System\IPv6AddressHelper.cs" />
+ <Compile Include="System\IriHelper.cs" />
+ <Compile Include="System\UncNameHelper.cs" />
+ <Compile Include="System\Uri.cs" />
+ <Compile Include="System\UriBuilder.cs" />
+ <Compile Include="System\UriEnumTypes.cs" />
+ <Compile Include="System\UriExt.cs" />
+ <Compile Include="System\URIFormatException.cs" />
+ <Compile Include="System\UriHelper.cs" />
+ <Compile Include="System\UriHostNameType.cs" />
+ <Compile Include="System\UriPartial.cs" />
+ <Compile Include="System\UriScheme.cs" />
+ <Compile Include="System\UriSyntax.cs" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
+ <Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
+ <Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.OutputDebugString.cs">
+ <Link>Common\Interop\Windows\mincore\Interop.OutputDebugString.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\StringNormalizationExtensions.Windows.cs">
+ <Link>Common\System\StringNormalizationExtensions.Windows.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Globalization\IdnMapping.Windows.cs">
+ <Link>Common\System\Globalization\IdnMapping.Windows.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Diagnostics\Debug.Windows.cs">
+ <Link>Common\System\Diagnostics\Debug.Windows.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.GetLastError.cs">
+ <Link>Common\Interop\Windows\mincore\Interop.GetLastError.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.SetLastError.cs">
+ <Link>Common\Interop\Windows\mincore\Interop.SetLastError.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Interop.Globalization.Extensions.manual.cs">
+ <Link>Common\Interop\Interop.Globalization.Extensions.manual.cs</Link>
+ </Compile>
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
+ <Compile Include="$(CommonPath)\System\StringNormalizationExtensions.Unix.cs">
+ <Link>Common\System\StringNormalizationExtensions.Unix.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Globalization\IdnMapping.Unix.cs">
+ <Link>Common\System\Globalization\IdnMapping.Unix.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Diagnostics\Debug.Unix.cs">
+ <Link>Common\System\Diagnostics\Debug.Unix.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\NotImplemented.cs">
+ <Link>Common\System\NotImplemented.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\Interop.Devices.cs">
+ <Link>Common\Interop\Unix\Interop.Devices.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs">
+ <Link>Common\Interop\Unix\Interop.Libraries.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\Interop.Errors.cs">
+ <Link>Common\Interop\Unix\Interop.Errors.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\Interop.IOErrors.cs">
+ <Link>Common\Interop\Unix\Interop.IOErrors.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.close.cs">
+ <Link>Common\Interop\Unix\libc\Interop.close.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.gnu_get_libc_version.cs">
+ <Link>Common\Interop\Unix\Interop.gnu_get_libc_version.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.open.cs">
+ <Link>Common\Interop\Unix\libc\Interop.open.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.strerror.cs">
+ <Link>Common\Interop\Unix\libc\Interop.strerror.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.syslog.cs">
+ <Link>Common\Interop\Unix\libc\Interop.syslog.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.write.cs">
+ <Link>Common\Interop\Unix\libc\Interop.write.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\libcoreclrpal\Interop.GetFileInformation.cs">
+ <Link>Common\Interop\Unix\Interop.GetFileInformation.cs"</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs">
+ <Link>Common\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs</Link>
+ </Compile>
+ </ItemGroup>
+
+ <!-- Linux -->
+ <ItemGroup Condition="'$(TargetsLinux)' == 'true'">
+ <Compile Include="$(CommonPath)\Interop\Linux\Interop.Errors.cs">
+ <Link>Common\Interop\Linux\Interop.Errors.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\Linux\libc\Interop.OpenFlags.cs">
+ <Link>Common\Interop\Linux\Interop.OpenFlags.cs</Link>
+ </Compile>
+ </ItemGroup>
+ <!-- OSX -->
+ <ItemGroup Condition="'$(TargetsOSX)' == 'true'">
+ <Compile Include="$(CommonPath)\Interop\OSX\Interop.Errors.cs">
+ <Link>Common\Interop\OSX\Interop.Errors.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\Interop\OSX\libc\Interop.OpenFlags.cs">
+ <Link>Common\Interop\OSX\Interop.OpenFlags.cs</Link>
+ </Compile>
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Include="project.json" />
+ </ItemGroup>
+
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project> \ No newline at end of file
diff --git a/src/System.Private.Uri/src/System/DomainNameHelper.cs b/src/System.Private.Uri/src/System/DomainNameHelper.cs
new file mode 100644
index 0000000000..a3f40e3eff
--- /dev/null
+++ b/src/System.Private.Uri/src/System/DomainNameHelper.cs
@@ -0,0 +1,539 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Globalization;
+
+namespace System
+{
+ // The class designed as to keep working set of Uri class as minimal.
+ // The idea is to stay with static helper methods and strings
+ internal class DomainNameHelper
+ {
+ private const char c_DummyChar = (char)0xFFFF; //An Invalid Unicode character used as a dummy char passed into the parameter
+
+ private DomainNameHelper()
+ {
+ }
+
+ internal const string Localhost = "localhost";
+ internal const string Loopback = "loopback";
+
+ internal static string ParseCanonicalName(string str, int start, int end, ref bool loopback)
+ {
+ string res = null;
+
+ for (int i = end - 1; i >= start; --i)
+ {
+ if (str[i] >= 'A' && str[i] <= 'Z')
+ {
+ res = str.Substring(start, end - start).ToLowerInvariant();
+ break;
+ }
+ if (str[i] == ':')
+ end = i;
+ }
+
+ if (res == null)
+ {
+ res = str.Substring(start, end - start);
+ }
+
+ if (res == Localhost || res == Loopback)
+ {
+ loopback = true;
+ return Localhost;
+ }
+ return res;
+ }
+
+ //
+ // IsValid
+ //
+ // Determines whether a string is a valid domain name
+ //
+ // subdomain -> <label> | <label> "." <subdomain>
+ //
+ // Inputs:
+ // - name as Name to test
+ // - starting position
+ // - ending position
+ //
+ // Outputs:
+ // The end position of a valid domain name string, the canonical flag if found so
+ //
+ // Returns:
+ // bool
+ //
+ // Remarks: Optimized for speed as a most comon case,
+ // MUST NOT be used unless all input indexes are are verified and trusted.
+ //
+
+ internal unsafe static bool IsValid(char* name, ushort pos, ref int returnedEnd, ref bool notCanonical, bool notImplicitFile)
+ {
+ char* curPos = name + pos;
+ char* newPos = curPos;
+ char* end = name + returnedEnd;
+ for (; newPos < end; ++newPos)
+ {
+ char ch = *newPos;
+ if (ch > 0x7f) return false; // not ascii
+ if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
+ {
+ end = newPos;
+ break;
+ }
+ }
+
+ if (end == curPos)
+ {
+ return false;
+ }
+
+ do
+ {
+ // Determines whether a string is a valid domain name label. In keeping
+ // with RFC 1123, section 2.1, the requirement that the first character
+ // of a label be alphabetic is dropped. Therefore, Domain names are
+ // formed as:
+ //
+ // <label> -> <alphanum> [<alphanum> | <hyphen> | <underscore>] * 62
+
+ //find the dot or hit the end
+ newPos = curPos;
+ while (newPos < end)
+ {
+ if (*newPos == '.') break;
+ ++newPos;
+ }
+
+ //check the label start/range
+ if (curPos == newPos || newPos - curPos > 63 || !IsASCIILetterOrDigit(*curPos++, ref notCanonical))
+ {
+ return false;
+ }
+ //check the label content
+ while (curPos < newPos)
+ {
+ if (!IsValidDomainLabelCharacter(*curPos++, ref notCanonical))
+ {
+ return false;
+ }
+ }
+ ++curPos;
+ } while (curPos < end);
+
+ returnedEnd = (ushort)(end - name);
+ return true;
+ }
+
+ //
+ // Checks if the domain name is valid according to iri
+ // There are pretty much no restrictions and we effectively return the end of the
+ // domain name.
+ //
+ internal unsafe static bool IsValidByIri(char* name, ushort pos, ref int returnedEnd, ref bool notCanonical, bool notImplicitFile)
+ {
+ char* curPos = name + pos;
+ char* newPos = curPos;
+ char* end = name + returnedEnd;
+ int count = 0; // count number of octets in a label;
+
+ for (; newPos < end; ++newPos)
+ {
+ char ch = *newPos;
+ if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
+ {
+ end = newPos;
+ break;
+ }
+ }
+
+ if (end == curPos)
+ {
+ return false;
+ }
+
+ do
+ {
+ // Determines whether a string is a valid domain name label. In keeping
+ // with RFC 1123, section 2.1, the requirement that the first character
+ // of a label be alphabetic is dropped. Therefore, Domain names are
+ // formed as:
+ //
+ // <label> -> <alphanum> [<alphanum> | <hyphen> | <underscore>] * 62
+
+ //find the dot or hit the end
+ newPos = curPos;
+ count = 0;
+ bool labelHasUnicode = false; // if label has unicode we need to add 4 to label count for xn--
+ while (newPos < end)
+ {
+ if ((*newPos == '.') ||
+ (*newPos == '\u3002') || //IDEOGRAPHIC FULL STOP
+ (*newPos == '\uFF0E') || //FULLWIDTH FULL STOP
+ (*newPos == '\uFF61')) //HALFWIDTH IDEOGRAPHIC FULL STOP
+ break;
+ count++;
+ if (*newPos > 0xFF)
+ count++; // counts for two octets
+ if (*newPos >= 0xA0)
+ labelHasUnicode = true;
+
+ ++newPos;
+ }
+
+ //check the label start/range
+ if (curPos == newPos || (labelHasUnicode ? count + 4 : count) > 63 || ((*curPos++ < 0xA0) && !IsASCIILetterOrDigit(*(curPos - 1), ref notCanonical)))
+ {
+ return false;
+ }
+ //check the label content
+ while (curPos < newPos)
+ {
+ if ((*curPos++ < 0xA0) && !IsValidDomainLabelCharacter(*(curPos - 1), ref notCanonical))
+ {
+ return false;
+ }
+ }
+ ++curPos;
+ } while (curPos < end);
+
+ returnedEnd = (ushort)(end - name);
+ return true;
+ }
+
+ internal static string IdnEquivalent(string hostname)
+ {
+ bool allAscii = true;
+ bool atLeastOneValidIdn = false;
+ unsafe
+ {
+ fixed (char* host = hostname)
+ {
+ return IdnEquivalent(host, 0, hostname.Length, ref allAscii, ref atLeastOneValidIdn);
+ }
+ }
+ }
+
+ //
+ // Will convert a host name into its idn equivalent + tell you if it had a valid idn label
+ //
+ internal unsafe static string IdnEquivalent(char* hostname, int start, int end, ref bool allAscii, ref bool atLeastOneValidIdn)
+ {
+ string bidiStrippedHost = null;
+ string idnEquivalent = IdnEquivalent(hostname, start, end, ref allAscii, ref bidiStrippedHost);
+
+ if (idnEquivalent != null)
+ {
+ string strippedHost = (allAscii ? idnEquivalent : bidiStrippedHost);
+
+ fixed (char* strippedHostPtr = strippedHost)
+ {
+ int length = strippedHost.Length;
+ int newPos = 0;
+ int curPos = 0;
+ bool foundAce = false;
+ bool checkedAce = false;
+ bool foundDot = false;
+
+ do
+ {
+ foundAce = false;
+ checkedAce = false;
+ foundDot = false;
+
+ //find the dot or hit the end
+ newPos = curPos;
+ while (newPos < length)
+ {
+ char c = strippedHostPtr[newPos];
+ if (!checkedAce)
+ {
+ checkedAce = true;
+ if ((newPos + 3 < length) && IsIdnAce(strippedHostPtr, newPos))
+ {
+ newPos += 4;
+ foundAce = true;
+ continue;
+ }
+ }
+
+ if ((c == '.') || (c == '\u3002') || //IDEOGRAPHIC FULL STOP
+ (c == '\uFF0E') || //FULLWIDTH FULL STOP
+ (c == '\uFF61')) //HALFWIDTH IDEOGRAPHIC FULL STOP
+ {
+ foundDot = true;
+ break;
+ }
+ ++newPos;
+ }
+
+ if (foundAce)
+ {
+ // check ace validity
+ try
+ {
+ IdnMapping map = new IdnMapping();
+ map.GetUnicode(new string(strippedHostPtr, curPos, newPos - curPos));
+ atLeastOneValidIdn = true;
+ break;
+ }
+ catch (ArgumentException)
+ {
+ // not valid ace so treat it as a normal ascii label
+ }
+ }
+
+ curPos = newPos + (foundDot ? 1 : 0);
+ } while (curPos < length);
+ }
+ }
+ else
+ {
+ atLeastOneValidIdn = false;
+ }
+ return idnEquivalent;
+ }
+
+ //
+ // Will convert a host name into its idn equivalent
+ //
+ internal unsafe static string IdnEquivalent(char* hostname, int start, int end, ref bool allAscii, ref string bidiStrippedHost)
+ {
+ string idn = null;
+ if (end <= start)
+ return idn;
+
+ // indexes are validated
+
+ int newPos = start;
+ allAscii = true;
+
+ while (newPos < end)
+ {
+ // check if only ascii chars
+ // special case since idnmapping will not lowercase if only ascii present
+ if (hostname[newPos] > '\x7F')
+ {
+ allAscii = false;
+ break;
+ }
+ ++newPos;
+ }
+
+ if (allAscii)
+ {
+ // just lowercase for ascii
+ string unescapedHostname = new string(hostname, start, end - start);
+ return ((unescapedHostname != null) ? unescapedHostname.ToLowerInvariant() : null);
+ }
+ else
+ {
+ IdnMapping map = new IdnMapping();
+ string asciiForm;
+ bidiStrippedHost = Uri.StripBidiControlCharacter(hostname, start, end - start);
+ try
+ {
+ asciiForm = map.GetAscii(bidiStrippedHost);
+ }
+ catch (ArgumentException)
+ {
+ throw new UriFormatException(SR.net_uri_BadUnicodeHostForIdn);
+ }
+ return asciiForm;
+ }
+ }
+
+ private unsafe static bool IsIdnAce(string input, int index)
+ {
+ if ((input[index] == 'x') &&
+ (input[index + 1] == 'n') &&
+ (input[index + 2] == '-') &&
+ (input[index + 3] == '-'))
+ return true;
+ else
+ return false;
+ }
+
+ private unsafe static bool IsIdnAce(char* input, int index)
+ {
+ if ((input[index] == 'x') &&
+ (input[index + 1] == 'n') &&
+ (input[index + 2] == '-') &&
+ (input[index + 3] == '-'))
+ return true;
+ else
+ return false;
+ }
+
+ //
+ // Will convert a host name into its unicode equivalent expanding any existing idn names present
+ //
+ internal unsafe static string UnicodeEquivalent(string idnHost, char* hostname, int start, int end)
+ {
+ IdnMapping map = new IdnMapping();
+
+ // Test comon scenario first for perf
+ // try to get unicode equivalent
+ try
+ {
+ return map.GetUnicode(idnHost);
+ }
+ catch (ArgumentException)
+ {
+ }
+ // Here because something threw in GetUnicode above
+ // Need to now check individual labels of they had an ace label that was not valid Idn name
+ // or if there is a label with invalid Idn char.
+ bool dummy = true;
+ return UnicodeEquivalent(hostname, start, end, ref dummy, ref dummy);
+ }
+
+ internal unsafe static string UnicodeEquivalent(char* hostname, int start, int end, ref bool allAscii, ref bool atLeastOneValidIdn)
+ {
+ IdnMapping map = new IdnMapping();
+
+ // hostname already validated
+ allAscii = true;
+ atLeastOneValidIdn = false;
+ string idn = null;
+ if (end <= start)
+ return idn;
+
+ string unescapedHostname = Uri.StripBidiControlCharacter(hostname, start, (end - start));
+
+ string unicodeEqvlHost = null;
+ int curPos = 0;
+ int newPos = 0;
+ int length = unescapedHostname.Length;
+ bool asciiLabel = true;
+ bool foundAce = false;
+ bool checkedAce = false;
+ bool foundDot = false;
+
+
+ // We run a loop where for every label
+ // a) if label is ascii and no ace then we lowercase it
+ // b) if label is ascii and ace and not valid idn then just lowercase it
+ // c) if label is ascii and ace and is valid idn then get its unicode eqvl
+ // d) if label is unicode then clean it by running it through idnmapping
+ do
+ {
+ asciiLabel = true;
+ foundAce = false;
+ checkedAce = false;
+ foundDot = false;
+
+ //find the dot or hit the end
+ newPos = curPos;
+ while (newPos < length)
+ {
+ char c = unescapedHostname[newPos];
+ if (!checkedAce)
+ {
+ checkedAce = true;
+ if ((newPos + 3 < length) && (c == 'x') && IsIdnAce(unescapedHostname, newPos))
+ foundAce = true;
+ }
+ if (asciiLabel && (c > '\x7F'))
+ {
+ asciiLabel = false;
+ allAscii = false;
+ }
+ if ((c == '.') || (c == '\u3002') || //IDEOGRAPHIC FULL STOP
+ (c == '\uFF0E') || //FULLWIDTH FULL STOP
+ (c == '\uFF61')) //HALFWIDTH IDEOGRAPHIC FULL STOP
+ {
+ foundDot = true;
+ break;
+ }
+ ++newPos;
+ }
+
+ if (!asciiLabel)
+ {
+ string asciiForm = unescapedHostname.Substring(curPos, newPos - curPos);
+ try
+ {
+ asciiForm = map.GetAscii(asciiForm);
+ }
+ catch (ArgumentException)
+ {
+ throw new UriFormatException(SR.net_uri_BadUnicodeHostForIdn);
+ }
+
+ unicodeEqvlHost += map.GetUnicode(asciiForm);
+ if (foundDot)
+ unicodeEqvlHost += ".";
+ }
+ else
+ {
+ bool aceValid = false;
+ if (foundAce)
+ {
+ // check ace validity
+ try
+ {
+ unicodeEqvlHost += map.GetUnicode(unescapedHostname.Substring(curPos, newPos - curPos));
+ if (foundDot)
+ unicodeEqvlHost += ".";
+ aceValid = true;
+ atLeastOneValidIdn = true;
+ }
+ catch (ArgumentException)
+ {
+ // not valid ace so treat it as a normal ascii label
+ }
+ }
+
+ if (!aceValid)
+ {
+ // for invalid aces we just lowercase the label
+ unicodeEqvlHost += unescapedHostname.Substring(curPos, newPos - curPos).ToLowerInvariant();
+ if (foundDot)
+ unicodeEqvlHost += ".";
+ }
+ }
+
+ curPos = newPos + (foundDot ? 1 : 0);
+ } while (curPos < length);
+
+ return unicodeEqvlHost;
+ }
+
+ //
+ // Determines whether a character is a letter or digit according to the
+ // DNS specification [RFC 1035]. We use our own variant of IsLetterOrDigit
+ // because the base version returns false positives for non-ANSI characters
+ //
+ private static bool IsASCIILetterOrDigit(char character, ref bool notCanonical)
+ {
+ if ((character >= 'a' && character <= 'z') || (character >= '0' && character <= '9'))
+ return true;
+
+ if (character >= 'A' && character <= 'Z')
+ {
+ notCanonical = true;
+ return true;
+ }
+ return false;
+ }
+
+ //
+ // Takes into account the additional legal domain name characters '-' and '_'
+ // Note that '_' char is formally invalid but is historically in use, especially on corpnets
+ //
+ private static bool IsValidDomainLabelCharacter(char character, ref bool notCanonical)
+ {
+ if ((character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || (character == '-') || (character == '_'))
+ return true;
+
+ if (character >= 'A' && character <= 'Z')
+ {
+ notCanonical = true;
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/IPv4AddressHelper.cs b/src/System.Private.Uri/src/System/IPv4AddressHelper.cs
new file mode 100644
index 0000000000..66a2c2af27
--- /dev/null
+++ b/src/System.Private.Uri/src/System/IPv4AddressHelper.cs
@@ -0,0 +1,369 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+
+namespace System
+{
+ // The class designed as to keep minimal the working set of Uri class.
+ // The idea is to stay with static helper methods and strings
+ internal static class IPv4AddressHelper
+ {
+ internal const long Invalid = -1;
+ // Note: the native parser cannot handle MaxIPv4Value, only MaxIPv4Value - 1
+ private const long MaxIPv4Value = UInt32.MaxValue;
+ private const int Octal = 8;
+ private const int Decimal = 10;
+ private const int Hex = 16;
+
+ private const int NumberOfLabels = 4;
+
+ // methods
+ // Parse and canonicalize
+ internal static string ParseCanonicalName(string str, int start, int end, ref bool isLoopback)
+ {
+ unsafe
+ {
+ byte* numbers = stackalloc byte[NumberOfLabels];
+ isLoopback = Parse(str, numbers, start, end);
+ return numbers[0] + "." + numbers[1] + "." + numbers[2] + "." + numbers[3];
+ }
+ }
+
+ // Only called from the IPv6Helper, only parse the canonical format
+ internal static int ParseHostNumber(string str, int start, int end)
+ {
+ unsafe
+ {
+ byte* numbers = stackalloc byte[NumberOfLabels];
+ ParseCanonical(str, numbers, start, end);
+ return (numbers[0] << 24) + (numbers[1] << 16) + (numbers[2] << 8) + numbers[3];
+ }
+ }
+
+ //
+ // IsValid
+ //
+ // Performs IsValid on a substring. Updates the index to where we
+ // believe the IPv4 address ends
+ //
+ // Inputs:
+ // <argument> name
+ // string containing possible IPv4 address
+ //
+ // <argument> start
+ // offset in <name> to start checking for IPv4 address
+ //
+ // <argument> end
+ // offset in <name> of the last character we can touch in the check
+ //
+ // Outputs:
+ // <argument> end
+ // index of last character in <name> we checked
+ //
+ // <argument> allowIPv6
+ // enables parsing IPv4 addresses embedded in IPv6 address literals
+ //
+ // <argument> notImplicitFile
+ // do not consider this URI holding an implicit filename
+ //
+ // <argument> unknownScheme
+ // the check is made on an unknown scheme (suppress IPv4 canonicalization)
+ //
+ // Assumes:
+ // The address string is terminated by either
+ // end of the string, characters ':' '/' '\' '?'
+ //
+ //
+ // Returns:
+ // bool
+ //
+ // Throws:
+ // Nothing
+ //
+
+ //Remark: MUST NOT be used unless all input indexes are are verified and trusted.
+ internal unsafe static bool IsValid(char* name, int start, ref int end, bool allowIPv6, bool notImplicitFile, bool unknownScheme)
+ {
+ // IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv4 addresses.
+ if (allowIPv6 || unknownScheme)
+ {
+ return IsValidCanonical(name, start, ref end, allowIPv6, notImplicitFile);
+ }
+ else
+ {
+ return ParseNonCanonical(name, start, ref end, notImplicitFile) != Invalid;
+ }
+ }
+
+ //
+ // IsValidCanonical
+ //
+ // Checks if the substring is a valid canonical IPv4 address or an IPv4 address embedded in an IPv6 literal
+ // This is an attempt to parse ABNF productions from RFC3986, Section 3.2.2:
+ // IP-literal = "[" ( IPv6address / IPvFuture ) "]"
+ // IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
+ // dec-octet = DIGIT ; 0-9
+ // / %x31-39 DIGIT ; 10-99
+ // / "1" 2DIGIT ; 100-199
+ // / "2" %x30-34 DIGIT ; 200-249
+ // / "25" %x30-35 ; 250-255
+ //
+ internal unsafe static bool IsValidCanonical(char* name, int start, ref int end, bool allowIPv6, bool notImplicitFile)
+ {
+ int dots = 0;
+ int number = 0;
+ bool haveNumber = false;
+ bool firstCharIsZero = false;
+
+ while (start < end)
+ {
+ char ch = name[start];
+ if (allowIPv6)
+ {
+ // for ipv4 inside ipv6 the terminator is either ScopeId, prefix or ipv6 terminator
+ if (ch == ']' || ch == '/' || ch == '%') break;
+ }
+ else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
+ {
+ break;
+ }
+
+ if (ch <= '9' && ch >= '0')
+ {
+ if (!haveNumber && (ch == '0'))
+ {
+ if ((start + 1 < end) && name[start + 1] == '0')
+ {
+ // 00 is not allowed as a prefix.
+ return false;
+ }
+
+ firstCharIsZero = true;
+ }
+
+ haveNumber = true;
+ number = number * 10 + (name[start] - '0');
+ if (number > 255)
+ {
+ return false;
+ }
+ }
+ else if (ch == '.')
+ {
+ if (!haveNumber || (number > 0 && firstCharIsZero))
+ {
+ // 0 is not allowed to prefix a number.
+ return false;
+ }
+ ++dots;
+ haveNumber = false;
+ number = 0;
+ firstCharIsZero = false;
+ }
+ else
+ {
+ return false;
+ }
+ ++start;
+ }
+ bool res = (dots == 3) && haveNumber;
+ if (res)
+ {
+ end = start;
+ }
+ return res;
+ }
+
+ // Parse any canonical or noncanonical IPv4 formats and return a long between 0 and MaxIPv4Value.
+ // Return Invalid (-1) for failures.
+ // If the address has less than three dots, only the rightmost section is assumed to contain the combined value for
+ // the missing sections: 0xFF00FFFF == 0xFF.0x00.0xFF.0xFF == 0xFF.0xFFFF
+ internal unsafe static long ParseNonCanonical(char* name, int start, ref int end, bool notImplicitFile)
+ {
+ int numberBase = Decimal;
+ char ch;
+ long[] parts = new long[4];
+ long currentValue = 0;
+ bool atLeastOneChar = false;
+
+ // Parse one dotted section at a time
+ int dotCount = 0; // Limit 3
+ int current = start;
+ for (; current < end; current++)
+ {
+ ch = name[current];
+ currentValue = 0;
+
+ // Figure out what base this section is in
+ numberBase = Decimal;
+ if (ch == '0')
+ {
+ numberBase = Octal;
+ current++;
+ atLeastOneChar = true;
+ if (current < end)
+ {
+ ch = name[current];
+ if (ch == 'x' || ch == 'X')
+ {
+ numberBase = Hex;
+ current++;
+ atLeastOneChar = false;
+ }
+ }
+ }
+
+ // Parse this section
+ for (; current < end; current++)
+ {
+ ch = name[current];
+ int digitValue;
+
+ if ((numberBase == Decimal || numberBase == Hex) && '0' <= ch && ch <= '9')
+ {
+ digitValue = ch - '0';
+ }
+ else if (numberBase == Octal && '0' <= ch && ch <= '7')
+ {
+ digitValue = ch - '0';
+ }
+ else if (numberBase == Hex && 'a' <= ch && ch <= 'f')
+ {
+ digitValue = ch + 10 - 'a';
+ }
+ else if (numberBase == Hex && 'A' <= ch && ch <= 'F')
+ {
+ digitValue = ch + 10 - 'A';
+ }
+ else
+ {
+ break; // Invalid/terminator
+ }
+
+ currentValue = (currentValue * numberBase) + digitValue;
+
+ if (currentValue > MaxIPv4Value) // Overflow
+ {
+ return Invalid;
+ }
+
+ atLeastOneChar = true;
+ }
+
+ if (current < end && name[current] == '.')
+ {
+ if (dotCount >= 3 // Max of 3 dots and 4 segments
+ || !atLeastOneChar // No empty segmets: 1...1
+ // Only the last segment can be more than 255 (if there are less than 3 dots)
+ || currentValue > 0xFF)
+ {
+ return Invalid;
+ }
+ parts[dotCount] = currentValue;
+ dotCount++;
+ atLeastOneChar = false;
+ continue;
+ }
+ // We don't get here unless We find an invalid character or a terminator
+ break;
+ }
+
+ // Terminators
+ if (!atLeastOneChar)
+ {
+ return Invalid; // Empty trailing segment: 1.1.1.
+ }
+ else if (current >= end)
+ {
+ // end of string, allowed
+ }
+ else if ((ch = name[current]) == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
+ {
+ end = current;
+ }
+ else
+ {
+ // not a valid terminating character
+ return Invalid;
+ }
+
+ parts[dotCount] = currentValue;
+
+ // Parsed, reassemble and check for overflows
+ switch (dotCount)
+ {
+ case 0: // 0xFFFFFFFF
+ if (parts[0] > MaxIPv4Value)
+ {
+ return Invalid;
+ }
+ return parts[0];
+ case 1: // 0xFF.0xFFFFFF
+ if (parts[1] > 0xffffff)
+ {
+ return Invalid;
+ }
+ return (parts[0] << 24) | (parts[1] & 0xffffff);
+ case 2: // 0xFF.0xFF.0xFFFF
+ if (parts[2] > 0xffff)
+ {
+ return Invalid;
+ }
+ return (parts[0] << 24) | ((parts[1] & 0xff) << 16) | (parts[2] & 0xffff);
+ case 3: // 0xFF.0xFF.0xFF.0xFF
+ if (parts[3] > 0xff)
+ {
+ return Invalid;
+ }
+ return (parts[0] << 24) | ((parts[1] & 0xff) << 16) | ((parts[2] & 0xff) << 8) | (parts[3] & 0xff);
+ default:
+ return Invalid;
+ }
+ }
+
+ //
+ // Parse
+ //
+ // Convert this IPv4 address into a sequence of 4 8-bit numbers
+ //
+ unsafe private static bool Parse(string name, byte* numbers, int start, int end)
+ {
+ fixed (char* ipString = name)
+ {
+ int changedEnd = end;
+ long result = IPv4AddressHelper.ParseNonCanonical(ipString, start, ref changedEnd, true);
+ // end includes ports, so changedEnd may be different from end
+ Debug.Assert(result != Invalid, "Failed to parse after already validated: " + name);
+
+ numbers[0] = (byte)(result >> 24);
+ numbers[1] = (byte)(result >> 16);
+ numbers[2] = (byte)(result >> 8);
+ numbers[3] = (byte)(result);
+ }
+
+ return numbers[0] == 127;
+ }
+
+ // Assumes:
+ // <Name> has been validated and contains only decimal digits in groups
+ // of 8-bit numbers and the characters '.'
+ // Address may terminate with ':' or with the end of the string
+ //
+ unsafe private static bool ParseCanonical(string name, byte* numbers, int start, int end)
+ {
+ for (int i = 0; i < NumberOfLabels; ++i)
+ {
+ byte b = 0;
+ char ch;
+ for (; (start < end) && (ch = name[start]) != '.' && ch != ':'; ++start)
+ {
+ b = (byte)(b * 10 + (byte)(ch - '0'));
+ }
+ numbers[i] = b;
+ ++start;
+ }
+ return numbers[0] == 127;
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/IPv6AddressHelper.cs b/src/System.Private.Uri/src/System/IPv6AddressHelper.cs
new file mode 100644
index 0000000000..fa165aa9bb
--- /dev/null
+++ b/src/System.Private.Uri/src/System/IPv6AddressHelper.cs
@@ -0,0 +1,594 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Text;
+using System.Globalization;
+
+namespace System
+{
+ // The class designed as to keep minimal the working set of Uri class.
+ // The idea is to stay with static helper methods and strings
+ internal static class IPv6AddressHelper
+ {
+ // fields
+
+ private const int NumberOfLabels = 8;
+ // Upper case hex, zero padded to 4 characters
+ private const string LegacyFormat = "{0:X4}:{1:X4}:{2:X4}:{3:X4}:{4:X4}:{5:X4}:{6:X4}:{7:X4}";
+ // Lower case hex, no leading zeros
+ private const string CanonicalNumberFormat = "{0:x}";
+ private const string EmbeddedIPv4Format = ":{0:d}.{1:d}.{2:d}.{3:d}";
+ private const char Separator = ':';
+
+ // methods
+
+ internal static string ParseCanonicalName(string str, int start, ref bool isLoopback, ref string scopeId)
+ {
+ unsafe
+ {
+ ushort* numbers = stackalloc ushort[NumberOfLabels];
+ // optimized zeroing of 8 shorts = 2 longs
+ ((long*)numbers)[0] = 0L;
+ ((long*)numbers)[1] = 0L;
+ isLoopback = Parse(str, numbers, start, ref scopeId);
+ return '[' + CreateCanonicalName(numbers) + ']';
+ }
+ }
+
+ internal unsafe static string CreateCanonicalName(ushort* numbers)
+ {
+ // RFC 5952 Sections 4 & 5 - Compressed, lower case, with possible embedded IPv4 addresses.
+
+ // Start to finish, inclusive. <-1, -1> for no compression
+ KeyValuePair<int, int> range = FindCompressionRange(numbers);
+ bool ipv4Embedded = ShouldHaveIpv4Embedded(numbers);
+
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < NumberOfLabels; i++)
+ {
+ if (ipv4Embedded && i == (NumberOfLabels - 2))
+ {
+ // Write the remaining digits as an IPv4 address
+ builder.AppendFormat(CultureInfo.InvariantCulture, EmbeddedIPv4Format,
+ numbers[i] >> 8, numbers[i] & 0xFF, numbers[i + 1] >> 8, numbers[i + 1] & 0xFF);
+ break;
+ }
+
+ // Compression; 1::1, ::1, 1::
+ if (range.Key == i)
+ { // Start compression, add :
+ builder.Append(Separator);
+ }
+ if (range.Key <= i && range.Value == (NumberOfLabels - 1))
+ { // Remainder compressed; 1::
+ builder.Append(Separator);
+ break;
+ }
+ if (range.Key <= i && i <= range.Value)
+ {
+ continue; // Compressed
+ }
+
+ if (i != 0)
+ {
+ builder.Append(Separator);
+ }
+ builder.AppendFormat(CultureInfo.InvariantCulture, CanonicalNumberFormat, numbers[i]);
+ }
+
+ return builder.ToString();
+ }
+
+ // RFC 5952 Section 4.2.3
+ // Longest consecutive sequence of zero segments, minimum 2.
+ // On equal, first sequence wins.
+ // <-1, -1> for no compression.
+ private unsafe static KeyValuePair<int, int> FindCompressionRange(ushort* numbers)
+ {
+ int longestSequenceLength = 0;
+ int longestSequenceStart = -1;
+
+ int currentSequenceLength = 0;
+ for (int i = 0; i < NumberOfLabels; i++)
+ {
+ if (numbers[i] == 0)
+ { // In a sequence
+ currentSequenceLength++;
+ if (currentSequenceLength > longestSequenceLength)
+ {
+ longestSequenceLength = currentSequenceLength;
+ longestSequenceStart = i - currentSequenceLength + 1;
+ }
+ }
+ else
+ {
+ currentSequenceLength = 0;
+ }
+ }
+
+ if (longestSequenceLength >= 2)
+ {
+ return new KeyValuePair<int, int>(longestSequenceStart,
+ longestSequenceStart + longestSequenceLength - 1);
+ }
+
+ return new KeyValuePair<int, int>(-1, -1); // No compression
+ }
+
+ // Returns true if the IPv6 address should be formated with an embedded IPv4 address:
+ // ::192.168.1.1
+ private unsafe static bool ShouldHaveIpv4Embedded(ushort* numbers)
+ {
+ // 0:0 : 0:0 : x:x : x.x.x.x
+ if (numbers[0] == 0 && numbers[1] == 0 && numbers[2] == 0 && numbers[3] == 0 && numbers[6] != 0)
+ {
+ // RFC 5952 Section 5 - 0:0 : 0:0 : 0:[0 | FFFF] : x.x.x.x
+ if (numbers[4] == 0 && (numbers[5] == 0 || numbers[5] == 0xFFFF))
+ {
+ return true;
+ }
+ // SIIT - 0:0 : 0:0 : FFFF:0 : x.x.x.x
+ else if (numbers[4] == 0xFFFF && numbers[5] == 0)
+ {
+ return true;
+ }
+ }
+ // ISATAP
+ if (numbers[4] == 0 && numbers[5] == 0x5EFE)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ //
+ // InternalIsValid
+ //
+ // Determine whether a name is a valid IPv6 address. Rules are:
+ //
+ // * 8 groups of 16-bit hex numbers, separated by ':'
+ // * a *single* run of zeros can be compressed using the symbol '::'
+ // * an optional string of a ScopeID delimited by '%'
+ // * an optional (last) 1 or 2 character prefix length field delimited by '/'
+ // * the last 32 bits in an address can be represented as an IPv4 address
+ //
+ // Inputs:
+ // <argument> name
+ // Domain name field of a URI to check for pattern match with
+ // IPv6 address
+ // validateStrictAddress: if set to true, it expects strict ipv6 address. Otherwise it expects
+ // part of the string in ipv6 format.
+ //
+ // Outputs:
+ // Nothing
+ //
+ // Assumes:
+ // the correct name is terminated by ']' character
+ //
+ // Returns:
+ // true if <name> has IPv6 format/ipv6 address based on validateStrictAddress, else false
+ //
+ // Throws:
+ // Nothing
+ //
+
+ // Remarks: MUST NOT be used unless all input indexes are verified and trusted.
+ // start must be next to '[' position, or error is reported
+ unsafe private static bool InternalIsValid(char* name, int start, ref int end, bool validateStrictAddress)
+ {
+ int sequenceCount = 0;
+ int sequenceLength = 0;
+ bool haveCompressor = false;
+ bool haveIPv4Address = false;
+ bool havePrefix = false;
+ bool expectingNumber = true;
+ int lastSequence = 1;
+
+ int i;
+ for (i = start; i < end; ++i)
+ {
+ if (havePrefix ? (name[i] >= '0' && name[i] <= '9') : Uri.IsHexDigit(name[i]))
+ {
+ ++sequenceLength;
+ expectingNumber = false;
+ }
+ else
+ {
+ if (sequenceLength > 4)
+ {
+ return false;
+ }
+ if (sequenceLength != 0)
+ {
+ ++sequenceCount;
+ lastSequence = i - sequenceLength;
+ }
+ switch (name[i])
+ {
+ case '%':
+ while (true)
+ {
+ //accept anything in scopeID
+ if (++i == end)
+ {
+ // no closing ']', fail
+ return false;
+ }
+ if (name[i] == ']')
+ {
+ goto case ']';
+ }
+ else if (name[i] == '/')
+ {
+ goto case '/';
+ }
+ }
+ case ']':
+ start = i;
+ i = end;
+ //this will make i = end+1
+ continue;
+ case ':':
+ if ((i > 0) && (name[i - 1] == ':'))
+ {
+ if (haveCompressor)
+ {
+ //
+ // can only have one per IPv6 address
+ //
+
+ return false;
+ }
+ haveCompressor = true;
+ expectingNumber = false;
+ }
+ else
+ {
+ expectingNumber = true;
+ }
+ break;
+
+ case '/':
+ if (validateStrictAddress)
+ {
+ return false;
+ }
+ if ((sequenceCount == 0) || havePrefix)
+ {
+ return false;
+ }
+ havePrefix = true;
+ expectingNumber = true;
+ break;
+
+ case '.':
+ if (haveIPv4Address)
+ {
+ return false;
+ }
+
+ i = end;
+ if (!IPv4AddressHelper.IsValid(name, lastSequence, ref i, true, false, false))
+ {
+ return false;
+ }
+ // ipv4 address takes 2 slots in ipv6 address, one was just counted meeting the '.'
+ ++sequenceCount;
+ haveIPv4Address = true;
+ --i; // it will be incremented back on the next loop
+ break;
+
+ default:
+ return false;
+ }
+ sequenceLength = 0;
+ }
+ }
+
+ //
+ // if the last token was a prefix, check number of digits
+ //
+
+ if (havePrefix && ((sequenceLength < 1) || (sequenceLength > 2)))
+ {
+ return false;
+ }
+
+ //
+ // these sequence counts are -1 because it is implied in end-of-sequence
+ //
+
+ int expectedSequenceCount = 8 + (havePrefix ? 1 : 0);
+
+ if (!expectingNumber && (sequenceLength <= 4) && (haveCompressor ? (sequenceCount < expectedSequenceCount) : (sequenceCount == expectedSequenceCount)))
+ {
+ if (i == end + 1)
+ {
+ // ']' was found
+ end = start + 1;
+ return true;
+ }
+ return false;
+ }
+ return false;
+ }
+
+ //
+ // IsValid
+ //
+ // Determine whether a name is a valid IPv6 address. Rules are:
+ //
+ // * 8 groups of 16-bit hex numbers, separated by ':'
+ // * a *single* run of zeros can be compressed using the symbol '::'
+ // * an optional string of a ScopeID delimited by '%'
+ // * an optional (last) 1 or 2 character prefix length field delimited by '/'
+ // * the last 32 bits in an address can be represented as an IPv4 address
+ //
+ // Inputs:
+ // <argument> name
+ // Domain name field of a URI to check for pattern match with
+ // IPv6 address
+ //
+ // Outputs:
+ // Nothing
+ //
+ // Assumes:
+ // the correct name is terminated by ']' character
+ //
+ // Returns:
+ // true if <name> has IPv6 format, else false
+ //
+ // Throws:
+ // Nothing
+ //
+
+ // Remarks: MUST NOT be used unless all input indexes are are verified and trusted.
+ // start must be next to '[' position, or error is reported
+
+ internal unsafe static bool IsValid(char* name, int start, ref int end)
+ {
+ return InternalIsValid(name, start, ref end, false);
+ }
+
+ //
+ // IsValidStrict
+ //
+ // Determine whether a name is a valid IPv6 address. Rules are:
+ //
+ // * 8 groups of 16-bit hex numbers, separated by ':'
+ // * a *single* run of zeros can be compressed using the symbol '::'
+ // * an optional string of a ScopeID delimited by '%'
+ // * the last 32 bits in an address can be represented as an IPv4 address
+ //
+ // Difference between IsValid() and IsValidStrict() is that IsValid() expects part of the string to
+ // be ipv6 address where as IsValidStrict() expects strict ipv6 address.
+ //
+ // Inputs:
+ // <argument> name
+ // IPv6 address in string format
+ //
+ // Outputs:
+ // Nothing
+ //
+ // Assumes:
+ // the correct name is terminated by ']' character
+ //
+ // Returns:
+ // true if <name> is IPv6 address, else false
+ //
+ // Throws:
+ // Nothing
+ //
+
+ // Remarks: MUST NOT be used unless all input indexes are verified and trusted.
+ // start must be next to '[' position, or error is reported
+ internal unsafe static bool IsValidStrict(char* name, int start, ref int end)
+ {
+ return InternalIsValid(name, start, ref end, true);
+ }
+
+ //
+ // Parse
+ //
+ // Convert this IPv6 address into a sequence of 8 16-bit numbers
+ //
+ // Inputs:
+ // <member> Name
+ // The validated IPv6 address
+ //
+ // Outputs:
+ // <member> numbers
+ // Array filled in with the numbers in the IPv6 groups
+ //
+ // <member> PrefixLength
+ // Set to the number after the prefix separator (/) if found
+ //
+ // Assumes:
+ // <Name> has been validated and contains only hex digits in groups of
+ // 16-bit numbers, the characters ':' and '/', and a possible IPv4
+ // address
+ //
+ // Returns:
+ // true if this is a loopback, false otherwise. There is no falure indication as the sting must be a valid one.
+ //
+ // Throws:
+ // Nothing
+ //
+
+ unsafe internal static bool Parse(string address, ushort* numbers, int start, ref string scopeId)
+ {
+ int number = 0;
+ int index = 0;
+ int compressorIndex = -1;
+ bool numberIsValid = true;
+
+ //This used to be a class instance member but have not been used so far
+ int PrefixLength = 0;
+ if (address[start] == '[')
+ {
+ ++start;
+ }
+
+ for (int i = start; i < address.Length && address[i] != ']';)
+ {
+ switch (address[i])
+ {
+ case '%':
+ if (numberIsValid)
+ {
+ numbers[index++] = (ushort)number;
+ numberIsValid = false;
+ }
+
+ start = i;
+ for (++i; address[i] != ']' && address[i] != '/'; ++i)
+ {
+ ;
+ }
+ scopeId = address.Substring(start, i - start);
+ // ignore prefix if any
+ for (; address[i] != ']'; ++i)
+ {
+ ;
+ }
+ break;
+
+ case ':':
+ numbers[index++] = (ushort)number;
+ number = 0;
+ ++i;
+ if (address[i] == ':')
+ {
+ compressorIndex = index;
+ ++i;
+ }
+ else if ((compressorIndex < 0) && (index < 6))
+ {
+ //
+ // no point checking for IPv4 address if we don't
+ // have a compressor or we haven't seen 6 16-bit
+ // numbers yet
+ //
+
+ break;
+ }
+
+ //
+ // check to see if the upcoming number is really an IPv4
+ // address. If it is, convert it to 2 ushort numbers
+ //
+
+ for (int j = i; (address[j] != ']') &&
+ (address[j] != ':') &&
+ (address[j] != '%') &&
+ (address[j] != '/') &&
+ (j < i + 4); ++j)
+ {
+ if (address[j] == '.')
+ {
+ //
+ // we have an IPv4 address. Find the end of it:
+ // we know that since we have a valid IPv6
+ // address, the only things that will terminate
+ // the IPv4 address are the prefix delimiter '/'
+ // or the end-of-string (which we conveniently
+ // delimited with ']')
+ //
+
+ while ((address[j] != ']') && (address[j] != '/') && (address[j] != '%'))
+ {
+ ++j;
+ }
+ number = IPv4AddressHelper.ParseHostNumber(address, i, j);
+ numbers[index++] = (ushort)(number >> 16);
+ numbers[index++] = (ushort)number;
+ i = j;
+
+ //
+ // set this to avoid adding another number to
+ // the array if there's a prefix
+ //
+
+ number = 0;
+ numberIsValid = false;
+ break;
+ }
+ }
+ break;
+
+ case '/':
+ if (numberIsValid)
+ {
+ numbers[index++] = (ushort)number;
+ numberIsValid = false;
+ }
+
+ //
+ // since we have a valid IPv6 address string, the prefix
+ // length is the last token in the string
+ //
+
+ for (++i; address[i] != ']'; ++i)
+ {
+ PrefixLength = PrefixLength * 10 + (address[i] - '0');
+ }
+ break;
+
+ default:
+ number = number * 16 + Uri.FromHex(address[i++]);
+ break;
+ }
+ }
+
+ //
+ // add number to the array if its not the prefix length or part of
+ // an IPv4 address that's already been handled
+ //
+
+ if (numberIsValid)
+ {
+ numbers[index++] = (ushort)number;
+ }
+
+ //
+ // if we had a compressor sequence ("::") then we need to expand the
+ // numbers array
+ //
+
+ if (compressorIndex > 0)
+ {
+ int toIndex = NumberOfLabels - 1;
+ int fromIndex = index - 1;
+
+ for (int i = index - compressorIndex; i > 0; --i)
+ {
+ numbers[toIndex--] = numbers[fromIndex];
+ numbers[fromIndex--] = 0;
+ }
+ }
+
+ //
+ // is the address loopback? Loopback is defined as one of:
+ //
+ // 0:0:0:0:0:0:0:1
+ // 0:0:0:0:0:0:127.0.0.1 == 0:0:0:0:0:0:7F00:0001
+ // 0:0:0:0:0:FFFF:127.0.0.1 == 0:0:0:0:0:FFFF:7F00:0001
+ //
+
+ return ((numbers[0] == 0)
+ && (numbers[1] == 0)
+ && (numbers[2] == 0)
+ && (numbers[3] == 0)
+ && (numbers[4] == 0))
+ && (((numbers[5] == 0)
+ && (numbers[6] == 0)
+ && (numbers[7] == 1))
+ || (((numbers[6] == 0x7F00)
+ && (numbers[7] == 0x0001))
+ && ((numbers[5] == 0)
+ || (numbers[5] == 0xFFFF))));
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/IriHelper.cs b/src/System.Private.Uri/src/System/IriHelper.cs
new file mode 100644
index 0000000000..da1ebc48e1
--- /dev/null
+++ b/src/System.Private.Uri/src/System/IriHelper.cs
@@ -0,0 +1,364 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System
+{
+ internal static class IriHelper
+ {
+ //
+ // Checks if provided non surrogate char lies in iri range
+ //
+ internal static bool CheckIriUnicodeRange(char unicode, bool isQuery)
+ {
+ return ((unicode >= '\u00A0' && unicode <= '\uD7FF') ||
+ (unicode >= '\uF900' && unicode <= '\uFDCF') ||
+ (unicode >= '\uFDF0' && unicode <= '\uFFEF') ||
+ (isQuery && unicode >= '\uE000' && unicode <= '\uF8FF'));
+ }
+
+ //
+ // Check if highSurr and lowSurr are a surrogate pair then
+ // it checks if the combined char is in the range
+ // Takes in isQuery because because iri restrictions for query are different
+ //
+ internal static bool CheckIriUnicodeRange(char highSurr, char lowSurr, ref bool surrogatePair, bool isQuery)
+ {
+ bool inRange = false;
+ surrogatePair = false;
+
+ Debug.Assert(char.IsHighSurrogate(highSurr));
+
+ if (char.IsSurrogatePair(highSurr, lowSurr))
+ {
+ surrogatePair = true;
+ char[] chars = new char[2] { highSurr, lowSurr };
+ string surrPair = new string(chars);
+ if (((string.CompareOrdinal(surrPair, "\U00010000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0001FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00020000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0002FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00030000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0003FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00040000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0004FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00050000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0005FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00060000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0006FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00070000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0007FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00080000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0008FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00090000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0009FFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U000A0000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U000AFFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U000B0000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U000BFFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U000C0000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U000CFFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U000D0000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U000DFFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U000E1000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U000EFFFD") <= 0)) ||
+ (isQuery &&
+ (((string.CompareOrdinal(surrPair, "\U000F0000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U000FFFFD") <= 0)) ||
+ ((string.CompareOrdinal(surrPair, "\U00100000") >= 0)
+ && (string.CompareOrdinal(surrPair, "\U0010FFFD") <= 0)))))
+ {
+ inRange = true;
+ }
+ }
+
+ return inRange;
+ }
+
+ //
+ // Check reserved chars according to rfc 3987 in a sepecific component
+ //
+ internal static bool CheckIsReserved(char ch, UriComponents component)
+ {
+ if ((component != UriComponents.Scheme) &&
+ (component != UriComponents.UserInfo) &&
+ (component != UriComponents.Host) &&
+ (component != UriComponents.Port) &&
+ (component != UriComponents.Path) &&
+ (component != UriComponents.Query) &&
+ (component != UriComponents.Fragment)
+ )
+ {
+ return (component == (UriComponents)0) ? Uri.IsGenDelim(ch) : false;
+ }
+ else
+ {
+ switch (component)
+ {
+ // Reserved chars according to rfc 3987
+ case UriComponents.UserInfo:
+ if (ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@')
+ return true;
+ break;
+ case UriComponents.Host:
+ if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@')
+ return true;
+ break;
+ case UriComponents.Path:
+ if (ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']')
+ return true;
+ break;
+ case UriComponents.Query:
+ if (ch == '#' || ch == '[' || ch == ']')
+ return true;
+ break;
+ case UriComponents.Fragment:
+ if (ch == '#' || ch == '[' || ch == ']')
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+ }
+
+ //
+ // IRI normalization for strings containing characters that are not allowed or
+ // escaped characters that should be unescaped in the context of the specified Uri component.
+ //
+ internal static unsafe string EscapeUnescapeIri(char* pInput, int start, int end, UriComponents component)
+ {
+ char[] dest = new char[end - start];
+ byte[] bytes = null;
+
+ // Pin the array to do pointer accesses
+ GCHandle destHandle = GCHandle.Alloc(dest, GCHandleType.Pinned);
+ char* pDest = (char*)destHandle.AddrOfPinnedObject();
+
+ const int percentEncodingLen = 3; // Escaped UTF-8 will take 3 chars: %AB.
+ const int bufferCapacityIncrease = 30 * percentEncodingLen;
+ int bufferRemaining = 0;
+
+ int next = start;
+ int destOffset = 0;
+ char ch;
+ bool escape = false;
+ bool surrogatePair = false;
+
+ for (; next < end; ++next)
+ {
+ escape = false;
+ surrogatePair = false;
+
+ if ((ch = pInput[next]) == '%')
+ {
+ if (next + 2 < end)
+ {
+ ch = UriHelper.EscapedAscii(pInput[next + 1], pInput[next + 2]);
+
+ // Do not unescape a reserved char
+ if (ch == Uri.c_DummyChar || ch == '%' || CheckIsReserved(ch, component) || UriHelper.IsNotSafeForUnescape(ch))
+ {
+ // keep as is
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next++];
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next++];
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next];
+ continue;
+ }
+ else if (ch <= '\x7F')
+ {
+ Debug.Assert(ch < 0xFF, "Expecting ASCII character.");
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ //ASCII
+ pDest[destOffset++] = ch;
+ next += 2;
+ continue;
+ }
+ else
+ {
+ // possibly utf8 encoded sequence of unicode
+
+ // check if safe to unescape according to Iri rules
+
+ Debug.Assert(ch < 0xFF, "Expecting ASCII character.");
+
+ int startSeq = next;
+ int byteCount = 1;
+ // lazy initialization of max size, will reuse the array for next sequences
+ if ((object)bytes == null)
+ bytes = new byte[end - next];
+
+ bytes[0] = (byte)ch;
+ next += 3;
+ while (next < end)
+ {
+ // Check on exit criterion
+ if ((ch = pInput[next]) != '%' || next + 2 >= end)
+ break;
+
+ // already made sure we have 3 characters in str
+ ch = UriHelper.EscapedAscii(pInput[next + 1], pInput[next + 2]);
+
+ //invalid hex sequence ?
+ if (ch == Uri.c_DummyChar)
+ break;
+ // character is not part of a UTF-8 sequence ?
+ else if (ch < '\x80')
+ break;
+ else
+ {
+ //a UTF-8 sequence
+ bytes[byteCount++] = (byte)ch;
+ next += 3;
+ }
+
+ Debug.Assert(ch < 0xFF, "Expecting ASCII character.");
+ }
+ next--; // for loop will increment
+
+
+ // Using encoder with no replacement fall-back will skip all invalid UTF-8 sequences.
+ Encoding noFallbackCharUTF8 = Encoding.GetEncoding(
+ Encoding.UTF8.CodePage,
+ new EncoderReplacementFallback(""),
+ new DecoderReplacementFallback(""));
+
+ char[] unescapedChars = new char[bytes.Length];
+ int charCount = noFallbackCharUTF8.GetChars(bytes, 0, byteCount, unescapedChars, 0);
+
+
+ if (charCount != 0)
+ {
+ // If invalid sequences were present in the original escaped string, we need to
+ // copy the escaped versions of those sequences.
+ // Decoded Unicode values will be kept only when they are allowed by the URI/IRI RFC
+ // rules.
+ UriHelper.MatchUTF8Sequence(pDest, dest, ref destOffset, unescapedChars, charCount, bytes,
+ byteCount, component == UriComponents.Query, true);
+ }
+ else
+ {
+ // copy escaped sequence as is
+ for (int i = startSeq; i <= next; ++i)
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[i];
+ }
+ }
+ }
+ }
+ else
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next];
+ }
+ }
+ else if (ch > '\x7f')
+ {
+ // unicode
+
+ char ch2;
+
+ if ((char.IsHighSurrogate(ch)) && (next + 1 < end))
+ {
+ ch2 = pInput[next + 1];
+ escape = !CheckIriUnicodeRange(ch, ch2, ref surrogatePair, component == UriComponents.Query);
+ if (!escape)
+ {
+ // copy the two chars
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next++];
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next];
+ }
+ }
+ else
+ {
+ if (CheckIriUnicodeRange(ch, component == UriComponents.Query))
+ {
+ if (!Uri.IsBidiControlCharacter(ch))
+ {
+ // copy it
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next];
+ }
+ }
+ else
+ {
+ // escape it
+ escape = true;
+ }
+ }
+ }
+ else
+ {
+ // just copy the character
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = pInput[next];
+ }
+
+ if (escape)
+ {
+ const int maxNumberOfBytesEncoded = 4;
+
+ if (bufferRemaining < maxNumberOfBytesEncoded * percentEncodingLen)
+ {
+ int newBufferLength = 0;
+
+ checked
+ {
+ // may need more memory since we didn't anticipate escaping
+ newBufferLength = dest.Length + bufferCapacityIncrease;
+ bufferRemaining += bufferCapacityIncrease;
+ }
+
+ char[] newDest = new char[newBufferLength];
+
+ fixed (char* pNewDest = newDest)
+ {
+ Buffer.MemoryCopy((byte*)pDest, (byte*)pNewDest, newBufferLength, destOffset * sizeof(char));
+ }
+
+ if (destHandle.IsAllocated)
+ {
+ destHandle.Free();
+ }
+
+ dest = newDest;
+
+ // re-pin new dest[] array
+ destHandle = GCHandle.Alloc(dest, GCHandleType.Pinned);
+ pDest = (char*)destHandle.AddrOfPinnedObject();
+ }
+
+ byte[] encodedBytes = new byte[maxNumberOfBytesEncoded];
+ fixed (byte* pEncodedBytes = encodedBytes)
+ {
+ int encodedBytesCount = Encoding.UTF8.GetBytes(pInput + next, surrogatePair ? 2 : 1, pEncodedBytes, maxNumberOfBytesEncoded);
+ Debug.Assert(encodedBytesCount <= maxNumberOfBytesEncoded, "UTF8 encoder should not exceed specified byteCount");
+
+ bufferRemaining -= encodedBytesCount * percentEncodingLen;
+
+ for (int count = 0; count < encodedBytesCount; ++count)
+ {
+ UriHelper.EscapeAsciiChar((char)encodedBytes[count], dest, ref destOffset);
+ }
+ }
+ }
+ }
+
+ if (destHandle.IsAllocated)
+ destHandle.Free();
+
+ Debug.Assert(destOffset <= dest.Length, "Destination length met or exceeded destination offset.");
+ return new string(dest, 0, destOffset);
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UncNameHelper.cs b/src/System.Private.Uri/src/System/UncNameHelper.cs
new file mode 100644
index 0000000000..16628058e8
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UncNameHelper.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Globalization;
+
+namespace System
+{
+ // The class designed as to keep minimal the working set of Uri class.
+ // The idea is to stay with static helper methods and strings
+ internal class UncNameHelper
+ {
+ // fields
+
+ internal const int MaximumInternetNameLength = 256;
+
+ private UncNameHelper()
+ {
+ }
+
+
+ // properties
+
+ // methods
+ internal static string ParseCanonicalName(string str, int start, int end, ref bool loopback)
+ {
+ return DomainNameHelper.ParseCanonicalName(str, start, end, ref loopback);
+ }
+
+ //
+ // IsValid
+ //
+ //
+ // ATTN: This class has been re-designed as to conform to XP+ UNC hostname format
+ // It is now similar to DNS name but can contain Unicode characters as well
+ // This class will be removed and replaced by IDN specification later,
+ // but for now we violate URI RFC cause we never escape Unicode characters on the wire
+ // For the same reason we never unescape UNC host names since we never accept
+ // them in escaped format.
+ //
+ //
+ // Valid UNC server name chars:
+ // a Unicode Letter (not allowed as the only in a segment)
+ // a Latin-1 digit
+ // '-' 45 0x2D
+ // '.' 46 0x2E (only as a host domain delimiter)
+ // '_' 95 0x5F
+ //
+ //
+ // Assumption is the caller will check on the resulting name length
+ // Remarks: MUST NOT be used unless all input indexes are are verified and trusted.
+ internal unsafe static bool IsValid(char* name, ushort start, ref int returnedEnd, bool notImplicitFile)
+ {
+ ushort end = (ushort)returnedEnd;
+
+ if (start == end)
+ return false;
+ //
+ // First segment could consist of only '_' or '-' but it cannot be all digits or empty
+ //
+ bool validShortName = false;
+ ushort i = start;
+ for (; i < end; ++i)
+ {
+ if (name[i] == '/' || name[i] == '\\' || (notImplicitFile && (name[i] == ':' || name[i] == '?' || name[i] == '#')))
+ {
+ end = i;
+ break;
+ }
+ else if (name[i] == '.')
+ {
+ ++i;
+ break;
+ }
+ if (char.IsLetter(name[i]) || name[i] == '-' || name[i] == '_')
+ {
+ validShortName = true;
+ }
+ else if (name[i] < '0' || name[i] > '9')
+ return false;
+ }
+
+ if (!validShortName)
+ return false;
+
+ //
+ // Subsequent segments must start with a letter or a digit
+ //
+
+ for (; i < end; ++i)
+ {
+ if (name[i] == '/' || name[i] == '\\' || (notImplicitFile && (name[i] == ':' || name[i] == '?' || name[i] == '#')))
+ {
+ end = i;
+ break;
+ }
+ else if (name[i] == '.')
+ {
+ if (!validShortName || ((i - 1) >= start && name[i - 1] == '.'))
+ return false;
+
+ validShortName = false;
+ }
+ else if (name[i] == '-' || name[i] == '_')
+ {
+ if (!validShortName)
+ return false;
+ }
+ else if (char.IsLetter(name[i]) || (name[i] >= '0' && name[i] <= '9'))
+ {
+ if (!validShortName)
+ validShortName = true;
+ }
+ else
+ return false;
+ }
+
+ // last segment can end with the dot
+ if (((i - 1) >= start && name[i - 1] == '.'))
+ validShortName = true;
+
+ if (!validShortName)
+ return false;
+
+ // caller must check for (end - start <= MaximumInternetNameLength)
+
+ returnedEnd = end;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/Uri.cs b/src/System.Private.Uri/src/System/Uri.cs
new file mode 100644
index 0000000000..e29e369429
--- /dev/null
+++ b/src/System.Private.Uri/src/System/Uri.cs
@@ -0,0 +1,5181 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Globalization;
+using System.Collections.Generic;
+using System.Security;
+using System.Threading;
+
+namespace System
+{
+ public partial class Uri
+ {
+ internal static readonly string UriSchemeFile = UriParser.FileUri.SchemeName;
+ internal static readonly string UriSchemeFtp = UriParser.FtpUri.SchemeName;
+ internal static readonly string UriSchemeGopher = UriParser.GopherUri.SchemeName;
+ internal static readonly string UriSchemeHttp = UriParser.HttpUri.SchemeName;
+ internal static readonly string UriSchemeHttps = UriParser.HttpsUri.SchemeName;
+ internal static readonly string UriSchemeWs = UriParser.WsUri.SchemeName;
+ internal static readonly string UriSchemeWss = UriParser.WssUri.SchemeName;
+ internal static readonly string UriSchemeMailto = UriParser.MailToUri.SchemeName;
+ internal static readonly string UriSchemeNews = UriParser.NewsUri.SchemeName;
+ internal static readonly string UriSchemeNntp = UriParser.NntpUri.SchemeName;
+ internal static readonly string UriSchemeNetTcp = UriParser.NetTcpUri.SchemeName;
+ internal static readonly string UriSchemeNetPipe = UriParser.NetPipeUri.SchemeName;
+ internal static readonly string SchemeDelimiter = "://";
+
+
+ private const int c_Max16BitUtf8SequenceLength = 3 + 3 + 3 + 3; //each unicode byte takes 3 escaped chars
+ internal const int c_MaxUriBufferSize = 0xFFF0;
+ private const int c_MaxUriSchemeName = 1024;
+
+ // untouched user string unless string has unicode chars and iriparsing is enabled
+ // or idn is on and we have unicode host or idn host
+ // In that case, this string is normalized, stripped of bidi chars, and validated
+ // with char limits
+ private string _string;
+
+ // untouched user string if string has unicode with iri on or unicode/idn host with idn on
+ private string _originalUnicodeString;
+
+ private UriParser _syntax; // This is a whole Uri syntax, not only the scheme name
+ // temporarily stores dnssafe host when we have unicode/idn host and idn is on
+ private string _dnsSafeHost = null;
+
+ [Flags]
+ private enum Flags : ulong
+ {
+ Zero = 0x00000000,
+
+ SchemeNotCanonical = 0x1,
+ UserNotCanonical = 0x2,
+ HostNotCanonical = 0x4,
+ PortNotCanonical = 0x8,
+ PathNotCanonical = 0x10,
+ QueryNotCanonical = 0x20,
+ FragmentNotCanonical = 0x40,
+ CannotDisplayCanonical = 0x7F,
+
+ E_UserNotCanonical = 0x80,
+ E_HostNotCanonical = 0x100,
+ E_PortNotCanonical = 0x200,
+ E_PathNotCanonical = 0x400,
+ E_QueryNotCanonical = 0x800,
+ E_FragmentNotCanonical = 0x1000,
+ E_CannotDisplayCanonical = 0x1F80,
+
+
+ ShouldBeCompressed = 0x2000,
+ FirstSlashAbsent = 0x4000,
+ BackslashInPath = 0x8000,
+
+ IndexMask = 0x0000FFFF,
+ HostTypeMask = 0x00070000,
+ HostNotParsed = 0x00000000,
+ IPv6HostType = 0x00010000,
+ IPv4HostType = 0x00020000,
+ DnsHostType = 0x00030000,
+ UncHostType = 0x00040000,
+ BasicHostType = 0x00050000,
+ UnusedHostType = 0x00060000,
+ UnknownHostType = 0x00070000,
+
+ UserEscaped = 0x00080000,
+ AuthorityFound = 0x00100000,
+ HasUserInfo = 0x00200000,
+ LoopbackHost = 0x00400000,
+ NotDefaultPort = 0x00800000,
+
+ UserDrivenParsing = 0x01000000,
+ CanonicalDnsHost = 0x02000000,
+ ErrorOrParsingRecursion = 0x04000000, // Used to signal a default parser error and alsoe to confirm Port
+ // and Host values in case of a custom user Parser
+ DosPath = 0x08000000,
+ UncPath = 0x10000000,
+ ImplicitFile = 0x20000000,
+ MinimalUriInfoSet = 0x40000000,
+ AllUriInfoSet = unchecked(0x80000000),
+ IdnHost = 0x100000000,
+ HasUnicode = 0x200000000,
+ HostUnicodeNormalized = 0x400000000,
+ RestUnicodeNormalized = 0x800000000,
+ UnicodeHost = 0x1000000000,
+ IntranetUri = 0x2000000000,
+ UseOrigUncdStrOffset= 0x4000000000,
+ // Is this component Iri canonical
+ UserIriCanonical = 0x8000000000,
+ PathIriCanonical = 0x10000000000,
+ QueryIriCanonical = 0x20000000000,
+ FragmentIriCanonical = 0x40000000000,
+ IriCanonical = 0x78000000000,
+ }
+
+ private Flags _flags;
+ private UriInfo _info;
+
+ private class UriInfo
+ {
+ public string Host;
+ public string ScopeId; //only IP v6 may need this
+ public string String;
+ public Offset Offset;
+ public string DnsSafeHost; // stores dns safe host when idn is on and we have unicode or idn host
+ public MoreInfo MoreInfo; // Multi-threading: This field must be always accessed through a _local_
+ // stack copy of m_Info.
+ };
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct Offset
+ {
+ public ushort Scheme;
+ public ushort User;
+ public ushort Host;
+ public ushort PortValue;
+ public ushort Path;
+ public ushort Query;
+ public ushort Fragment;
+ public ushort End;
+ };
+
+ private class MoreInfo
+ {
+ public string Path;
+ public string Query;
+ public string Fragment;
+ public string AbsoluteUri;
+ public int Hash;
+ public string RemoteUrl;
+ };
+
+ private bool IsImplicitFile
+ {
+ get { return (_flags & Flags.ImplicitFile) != 0; }
+ }
+
+ private bool IsUncOrDosPath
+ {
+ get { return (_flags & (Flags.UncPath | Flags.DosPath)) != 0; }
+ }
+
+ private bool IsDosPath
+ {
+ get { return (_flags & Flags.DosPath) != 0; }
+ }
+
+ private bool IsUncPath
+ {
+ get { return (_flags & Flags.UncPath) != 0; }
+ }
+
+ private Flags HostType
+ {
+ get { return _flags & Flags.HostTypeMask; }
+ }
+
+ private UriParser Syntax
+ {
+ get
+ {
+ return _syntax;
+ }
+ }
+
+ private bool IsNotAbsoluteUri
+ {
+ get { return (object)_syntax == null; }
+ }
+
+ //
+ // Checks if Iri parsing is allowed by the syntax & by config
+ //
+ private bool _iriParsing;
+
+ //
+ // Statically checks if Iri parsing is allowed by the syntax & by config
+ //
+ internal static bool IriParsingStatic(UriParser syntax)
+ {
+ return (s_IriParsing && (((syntax != null) && syntax.InFact(UriSyntaxFlags.AllowIriParsing)) ||
+ (syntax == null)));
+ }
+
+ //
+ // Checks if Idn is allowed by the syntax & by config
+ //
+ private bool AllowIdn
+ {
+ get
+ {
+ return ((_syntax != null) && ((_syntax.Flags & UriSyntaxFlags.AllowIdn) != 0) &&
+ ((s_IdnScope == UriIdnScope.All) || ((s_IdnScope == UriIdnScope.AllExceptIntranet)
+ && NotAny(Flags.IntranetUri))));
+ }
+ }
+
+ //
+ // Checks statically if Idn is allowed by the syntax & by config
+ //
+ private bool AllowIdnStatic(UriParser syntax, Flags flags)
+ {
+ return ((syntax != null) && ((syntax.Flags & UriSyntaxFlags.AllowIdn) != 0) &&
+ ((s_IdnScope == UriIdnScope.All) || ((s_IdnScope == UriIdnScope.AllExceptIntranet)
+ && StaticNotAny(flags, Flags.IntranetUri))));
+ }
+
+ private bool IsIntranet(string schemeHost)
+ {
+ // .NET Native/CoreCLR behavior difference: all URI/IRIs will be treated as Internet.
+ return false;
+ }
+
+ internal bool UserDrivenParsing
+ {
+ get
+ {
+ return (_flags & Flags.UserDrivenParsing) != 0;
+ }
+ }
+ private void SetUserDrivenParsing()
+ {
+ // we use = here to clear all parsing flags for a uri that we think is invalid.
+ _flags = Flags.UserDrivenParsing | (_flags & Flags.UserEscaped);
+ }
+
+ private ushort SecuredPathIndex
+ {
+ get
+ {
+ // This is one more trouble with a Dos Path.
+ // This property gets "safe" first path slash that is not the first if path = c:\
+ if (IsDosPath)
+ {
+ char ch = _string[_info.Offset.Path];
+ return (ushort)((ch == '/' || ch == '\\') ? 3 : 2);
+ }
+ return (ushort)0;
+ }
+ }
+
+ private bool NotAny(Flags flags)
+ {
+ return (_flags & flags) == 0;
+ }
+
+ private bool InFact(Flags flags)
+ {
+ return (_flags & flags) != 0;
+ }
+
+ private static bool StaticNotAny(Flags allFlags, Flags checkFlags)
+ {
+ return (allFlags & checkFlags) == 0;
+ }
+
+ private static bool StaticInFact(Flags allFlags, Flags checkFlags)
+ {
+ return (allFlags & checkFlags) != 0;
+ }
+
+ private UriInfo EnsureUriInfo()
+ {
+ Flags cF = _flags;
+ if ((_flags & Flags.MinimalUriInfoSet) == 0)
+ {
+ CreateUriInfo(cF);
+ }
+ return _info;
+ }
+
+ private void EnsureParseRemaining()
+ {
+ if ((_flags & Flags.AllUriInfoSet) == 0)
+ {
+ ParseRemaining();
+ }
+ }
+
+ private void EnsureHostString(bool allowDnsOptimization)
+ {
+ EnsureUriInfo();
+ if ((object)_info.Host == null)
+ {
+ if (allowDnsOptimization && InFact(Flags.CanonicalDnsHost))
+ {
+ /* Optimization for a canonical DNS name
+ * ATTN: the host string won't be created,
+ * Hence ALL m_Info.Host callers first call EnsureHostString(false)
+ * For example IsLoopBack property is one of such callers.
+ */
+ return;
+ }
+ CreateHostString();
+ }
+ }
+
+ //
+ // Uri(string)
+ //
+ // We expect to create a Uri from a display name - e.g. that was typed by
+ // a user, or that was copied & pasted from a document. That is, we do not
+ // expect already encoded URI to be supplied.
+ //
+ public Uri(string uriString)
+ {
+ if ((object)uriString == null)
+ throw new ArgumentNullException("uriString");
+
+ CreateThis(uriString, false, UriKind.Absolute);
+ }
+
+ //
+ // Uri(string, UriKind);
+ //
+ public Uri(string uriString, UriKind uriKind)
+ {
+ if ((object)uriString == null)
+ throw new ArgumentNullException("uriString");
+
+ CreateThis(uriString, false, uriKind);
+ }
+
+ //
+ // Uri(Uri, string)
+ //
+ // Construct a new Uri from a base and relative URI. The relative URI may
+ // also be an absolute URI, in which case the resultant URI is constructed
+ // entirely from it
+ //
+ public Uri(Uri baseUri, string relativeUri)
+ {
+ if ((object)baseUri == null)
+ throw new ArgumentNullException("baseUri");
+
+ if (!baseUri.IsAbsoluteUri)
+ throw new ArgumentOutOfRangeException("baseUri");
+
+ CreateUri(baseUri, relativeUri, false);
+ }
+
+ private void CreateUri(Uri baseUri, string relativeUri, bool dontEscape)
+ {
+ // Parse relativeUri and populate Uri internal data.
+ CreateThis(relativeUri, dontEscape, UriKind.RelativeOrAbsolute);
+
+ UriFormatException e;
+ if (baseUri.Syntax.IsSimple)
+ {
+ // Resolve Uris if possible OR get merged Uri String to re-parse below
+ Uri uriResult = ResolveHelper(baseUri, this, ref relativeUri, ref dontEscape, out e);
+
+ if (e != null)
+ throw e;
+
+ // If resolved into a Uri then we build from that Uri
+ if (uriResult != null)
+ {
+ if ((object)uriResult != (object)this)
+ CreateThisFromUri(uriResult);
+
+ return;
+ }
+ }
+ else
+ {
+ dontEscape = false;
+ relativeUri = baseUri.Syntax.InternalResolve(baseUri, this, out e);
+ if (e != null)
+ throw e;
+ }
+
+ _flags = Flags.Zero;
+ _info = null;
+ _syntax = null;
+ // If not resolved, we reparse modified Uri string and populate Uri internal data.
+ CreateThis(relativeUri, dontEscape, UriKind.Absolute);
+ }
+
+ //
+ // Uri(Uri , Uri )
+ // Note: a static Create() method should be used by users, not this .ctor
+ //
+ public Uri(Uri baseUri, Uri relativeUri)
+ {
+ if ((object)baseUri == null)
+ throw new ArgumentNullException("baseUri");
+
+ if (!baseUri.IsAbsoluteUri)
+ throw new ArgumentOutOfRangeException("baseUri");
+
+ CreateThisFromUri(relativeUri);
+
+ string newUriString = null;
+ UriFormatException e;
+ bool dontEscape;
+
+ if (baseUri.Syntax.IsSimple)
+ {
+ dontEscape = InFact(Flags.UserEscaped);
+ relativeUri = ResolveHelper(baseUri, this, ref newUriString, ref dontEscape, out e);
+
+ if (e != null)
+ throw e;
+
+ if (relativeUri != null)
+ {
+ if ((object)relativeUri != (object)this)
+ CreateThisFromUri(relativeUri);
+
+ return;
+ }
+ }
+ else
+ {
+ dontEscape = false;
+ newUriString = baseUri.Syntax.InternalResolve(baseUri, this, out e);
+ if (e != null)
+ throw e;
+ }
+
+ _flags = Flags.Zero;
+ _info = null;
+ _syntax = null;
+ CreateThis(newUriString, dontEscape, UriKind.Absolute);
+ }
+
+ //
+ // This method is shared by base+relative Uris constructors and is only called from them.
+ // The assumptions:
+ // - baseUri is a valid absolute Uri
+ // - relative part is not null and not empty
+ private unsafe static ParsingError GetCombinedString(Uri baseUri, string relativeStr,
+ bool dontEscape, ref string result)
+ {
+ // NB: This is not RFC2396 compliant although it is inline with w3c.org recommendations
+ // This parser will allow the relativeStr to be an absolute Uri with the different scheme
+ // In fact this is strict violation of RFC2396
+ //
+ for (int i = 0; i < relativeStr.Length; ++i)
+ {
+ if (relativeStr[i] == '/' || relativeStr[i] == '\\' || relativeStr[i] == '?' || relativeStr[i] == '#')
+ {
+ break;
+ }
+ else if (relativeStr[i] == ':')
+ {
+ if (i < 2)
+ {
+ // Note we don't support one-letter Uri schemes.
+ // Hence anything like x:sdsd is a relative path and be added to the baseUri Path
+ break;
+ }
+ string scheme = relativeStr.Substring(0, i);
+ fixed (char* sptr = scheme)
+ {
+ UriParser syntax = null;
+ if (CheckSchemeSyntax(sptr, (ushort)scheme.Length, ref syntax) == ParsingError.None)
+ {
+ if (baseUri.Syntax == syntax)
+ {
+ //Remove the scheme for backward Uri parsers compatibility
+ if (i + 1 < relativeStr.Length)
+ {
+ relativeStr = relativeStr.Substring(i + 1);
+ }
+ else
+ {
+ relativeStr = string.Empty;
+ }
+ }
+ else
+ {
+ // This is the place where we switch the scheme.
+ // Return relative part as the result Uri.
+ result = relativeStr;
+ return ParsingError.None;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if (relativeStr.Length == 0)
+ {
+ result = baseUri.OriginalString;
+ return ParsingError.None;
+ }
+
+ result = CombineUri(baseUri, relativeStr, dontEscape ? UriFormat.UriEscaped : UriFormat.SafeUnescaped);
+ return ParsingError.None;
+ }
+
+ private static UriFormatException GetException(ParsingError err)
+ {
+ switch (err)
+ {
+ case ParsingError.None:
+ return null;
+ // Could be OK for Relative Uri
+ case ParsingError.BadFormat:
+ return new UriFormatException(SR.net_uri_BadFormat);
+ case ParsingError.BadScheme:
+ return new UriFormatException(SR.net_uri_BadScheme);
+ case ParsingError.BadAuthority:
+ return new UriFormatException(SR.net_uri_BadAuthority);
+ case ParsingError.EmptyUriString:
+ return new UriFormatException(SR.net_uri_EmptyUri);
+ // Fatal
+ case ParsingError.SchemeLimit:
+ return new UriFormatException(SR.net_uri_SchemeLimit);
+ case ParsingError.SizeLimit:
+ return new UriFormatException(SR.net_uri_SizeLimit);
+ case ParsingError.MustRootedPath:
+ return new UriFormatException(SR.net_uri_MustRootedPath);
+ // Derived class controllable
+ case ParsingError.BadHostName:
+ return new UriFormatException(SR.net_uri_BadHostName);
+ case ParsingError.NonEmptyHost: //unix-only
+ return new UriFormatException(SR.net_uri_BadFormat);
+ case ParsingError.BadPort:
+ return new UriFormatException(SR.net_uri_BadPort);
+ case ParsingError.BadAuthorityTerminator:
+ return new UriFormatException(SR.net_uri_BadAuthorityTerminator);
+ case ParsingError.CannotCreateRelative:
+ return new UriFormatException(SR.net_uri_CannotCreateRelative);
+ default:
+ break;
+ }
+ return new UriFormatException(SR.net_uri_BadFormat);
+ }
+
+ public string AbsolutePath
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ string path = PrivateAbsolutePath;
+ //
+ // For Compat:
+ // Remove the first slash from a Dos Path if it's present
+ //
+ if (IsDosPath && path[0] == '/')
+ {
+ path = path.Substring(1);
+ }
+ return path;
+ }
+ }
+
+ private string PrivateAbsolutePath
+ {
+ get
+ {
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null)
+ {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.Path;
+ if ((object)result == null)
+ {
+ result = GetParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+ info.MoreInfo.Path = result;
+ }
+ return result;
+ }
+ }
+
+ public string AbsoluteUri
+ {
+ get
+ {
+ if (_syntax == null)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null)
+ {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.AbsoluteUri;
+ if ((object)result == null)
+ {
+ result = GetParts(UriComponents.AbsoluteUri, UriFormat.UriEscaped);
+ info.MoreInfo.AbsoluteUri = result;
+ }
+ return result;
+ }
+ }
+
+ //
+ // LocalPath
+ //
+ // Returns a 'local' version of the path. This is mainly for file: URI
+ // such that DOS and UNC paths are returned with '/' converted back to
+ // '\', and any escape sequences converted
+ //
+ // The form of the returned path is in NOT Escaped
+ //
+ public string LocalPath
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+ return GetLocalPath();
+ }
+ }
+
+ //
+ // The result is of the form "hostname[:port]" Port is omitted if default
+ //
+ public string Authority
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ // Note: Compatibilty with V1 that does not report user info
+ return GetParts(UriComponents.Host | UriComponents.Port, UriFormat.UriEscaped);
+ }
+ }
+
+
+ public UriHostNameType HostNameType
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ if (_syntax.IsSimple)
+ EnsureUriInfo();
+ else
+ {
+ // For a custom parser we request HostString creation to confirm HostType
+ EnsureHostString(false);
+ }
+
+ switch (HostType)
+ {
+ case Flags.DnsHostType: return UriHostNameType.Dns;
+ case Flags.IPv4HostType: return UriHostNameType.IPv4;
+ case Flags.IPv6HostType: return UriHostNameType.IPv6;
+ case Flags.BasicHostType: return UriHostNameType.Basic;
+ case Flags.UncHostType: return UriHostNameType.Basic;
+ case Flags.UnknownHostType: return UriHostNameType.Unknown;
+ default:
+ break;
+ }
+ return UriHostNameType.Unknown;
+ }
+ }
+
+ public bool IsDefaultPort
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+ if (_syntax.IsSimple)
+ EnsureUriInfo();
+ else
+ {
+ // For a custom parser we request HostString creation that will aso set the port
+ EnsureHostString(false);
+ }
+
+ return NotAny(Flags.NotDefaultPort);
+ }
+ }
+
+ public bool IsFile
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ return (object)_syntax.SchemeName == (object)UriSchemeFile;
+ }
+ }
+
+ public bool IsLoopback
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ EnsureHostString(false);
+
+ return InFact(Flags.LoopbackHost);
+ }
+ }
+
+ //
+ // Gets the escaped Uri.AbsolutePath and Uri.Query
+ // properties separated by a "?" character.
+ public string PathAndQuery
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ string result = GetParts(UriComponents.PathAndQuery, UriFormat.UriEscaped);
+ //
+ // Compatibility:
+ // Remove the first slash from a Dos Path if it's present
+ //
+ if (IsDosPath && result[0] == '/')
+ {
+ result = result.Substring(1);
+ }
+ return result;
+ }
+ }
+
+ //
+ // Gets an array of the segments that make up a URI.
+ public string[] Segments
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ string[] segments = null; // used to be a class cached result
+ if (segments == null)
+ {
+ string path = PrivateAbsolutePath;
+
+ if (path.Length == 0)
+ {
+ segments = Array.Empty<string>();
+ }
+ else
+ {
+ LowLevelList<string> pathSegments = new LowLevelList<string>();
+ int current = 0;
+ while (current < path.Length)
+ {
+ int next = path.IndexOf('/', current);
+ if (next == -1)
+ {
+ next = path.Length - 1;
+ }
+ pathSegments.Add(path.Substring(current, (next - current) + 1));
+ current = next + 1;
+ }
+ segments = pathSegments.ToArray();
+ }
+ }
+ return segments;
+ }
+ }
+
+ public bool IsUnc
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+ return IsUncPath;
+ }
+ }
+
+ //
+ // Gets a hostname part (special formatting for IPv6 form)
+ public string Host
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ return GetParts(UriComponents.Host, UriFormat.UriEscaped);
+ }
+ }
+
+ private static bool StaticIsFile(UriParser syntax)
+ {
+ return syntax.InFact(UriSyntaxFlags.FileLikeUri);
+ }
+
+ // Value from config Uri section
+ // The use of this IDN mechanic is discouraged on Win8+ due to native platform improvements.
+ private static volatile UriIdnScope s_IdnScope = UriIdnScope.None; // IDN is disabled in .NET Native and CoreCLR.
+
+ // Value from config Uri section
+ // On by default in .NET 4.5+ and cannot be disabled by config.
+ private static volatile bool s_IriParsing = true; // IRI Parsing is always enabled in .NET Native and CoreCLR
+
+ private static object s_initLock;
+
+ private static object InitializeLock
+ {
+ get
+ {
+ if (s_initLock == null)
+ {
+ object o = new object();
+ Interlocked.CompareExchange(ref s_initLock, o, null);
+ }
+ return s_initLock;
+ }
+ }
+
+ private string GetLocalPath()
+ {
+ EnsureParseRemaining();
+
+ //Other cases will get a Unix-style path
+ if (IsUncOrDosPath)
+ {
+ EnsureHostString(false);
+ int start;
+
+ // Do we have a valid local path right in m_string?
+ if (NotAny(Flags.HostNotCanonical | Flags.PathNotCanonical | Flags.ShouldBeCompressed))
+ {
+ start = IsUncPath ? _info.Offset.Host - 2 : _info.Offset.Path;
+
+ string str = (IsImplicitFile && _info.Offset.Host == (IsDosPath ? 0 : 2) &&
+ _info.Offset.Query == _info.Offset.End)
+ ? _string
+ : (IsDosPath && (_string[start] == '/' || _string[start] == '\\'))
+ ? _string.Substring(start + 1, _info.Offset.Query - start - 1)
+ : _string.Substring(start, _info.Offset.Query - start);
+
+ // Should be a rare case, convert c|\ into c:\
+ if (IsDosPath && str[1] == '|')
+ {
+ // Sadly, today there is no method for replacing just one occurrence
+ str = str.Remove(1, 1);
+ str = str.Insert(1, ":");
+ }
+
+ // check for all back slashes
+ for (int i = 0; i < str.Length; ++i)
+ {
+ if (str[i] == '/')
+ {
+ str = str.Replace('/', '\\');
+ break;
+ }
+ }
+
+ return str;
+ }
+
+ char[] result;
+ int count = 0;
+ start = _info.Offset.Path;
+
+ string host = _info.Host;
+ result = new char[host.Length + 3 + _info.Offset.Fragment - _info.Offset.Path];
+
+ if (IsUncPath)
+ {
+ result[0] = '\\';
+ result[1] = '\\';
+ count = 2;
+
+ UriHelper.UnescapeString(host, 0, host.Length, result, ref count, c_DummyChar, c_DummyChar,
+ c_DummyChar, UnescapeMode.CopyOnly, _syntax, false);
+ }
+ else
+ {
+ // Dos path
+ if (_string[start] == '/' || _string[start] == '\\')
+ {
+ // Skip leading slash for a DOS path
+ ++start;
+ }
+ }
+
+
+ ushort pathStart = (ushort)count; //save for optional Compress() call
+
+ UnescapeMode mode = (InFact(Flags.PathNotCanonical) && !IsImplicitFile)
+ ? (UnescapeMode.Unescape | UnescapeMode.UnescapeAll) : UnescapeMode.CopyOnly;
+ UriHelper.UnescapeString(_string, start, _info.Offset.Query, result, ref count, c_DummyChar,
+ c_DummyChar, c_DummyChar, mode, _syntax, true);
+
+ // Possibly convert c|\ into c:\
+ if (result[1] == '|')
+ result[1] = ':';
+
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ // suspecting not compressed path
+ // For a dos path we won't compress the "x:" part if found /../ sequences
+ result = Compress(result, (ushort)(IsDosPath ? pathStart + 2 : pathStart), ref count, _syntax);
+ }
+
+ // We don't know whether all slashes were the back ones
+ // Plus going through Compress will turn them into / anyway
+ // Converting / back into \
+ for (ushort i = 0; i < (ushort)count; ++i)
+ {
+ if (result[i] == '/')
+ {
+ result[i] = '\\';
+ }
+ }
+
+ return new string(result, 0, count);
+ }
+ else
+ {
+ // Return unescaped canonical path
+ // Note we cannot call GetParts here because it has circular dependancy on GelLocalPath method
+ return GetUnescapedParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.Unescaped);
+ }
+ }
+
+ public int Port
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ if (_syntax.IsSimple)
+ EnsureUriInfo();
+ else
+ {
+ // For a custom parser we request HostString creation that will aso set the port
+ EnsureHostString(false);
+ }
+
+ if (InFact(Flags.NotDefaultPort))
+ {
+ return (int)_info.Offset.PortValue;
+ }
+ return _syntax.DefaultPort;
+ }
+ }
+
+ //
+ // Gets the escaped query.
+ public string Query
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null)
+ {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.Query;
+ if ((object)result == null)
+ {
+ result = GetParts(UriComponents.Query | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+ info.MoreInfo.Query = result;
+ }
+ return result;
+ }
+ }
+
+ //
+ // Gets the escaped fragment.
+ public string Fragment
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null)
+ {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.Fragment;
+ if ((object)result == null)
+ {
+ result = GetParts(UriComponents.Fragment | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+ info.MoreInfo.Fragment = result;
+ }
+ return result;
+ }
+ }
+
+ //
+ // Gets the Scheme string of this Uri
+ //
+ public string Scheme
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ return _syntax.SchemeName;
+ }
+ }
+
+ //
+ // Was the original string switched from m_String to m_OriginalUnicodeString
+ // Will happen when Iri is turned on and we have unicode chars or of idn is
+ // is on and we have an idn or unicode host.
+ //
+ private bool OriginalStringSwitched
+ {
+ get
+ {
+ return ((_iriParsing && InFact(Flags.HasUnicode)) ||
+ (AllowIdn && (InFact(Flags.IdnHost) || InFact(Flags.UnicodeHost))));
+ }
+ }
+
+ //
+ // Gets the exact string passed by a user.
+ public string OriginalString
+ {
+ get
+ {
+ return OriginalStringSwitched ? _originalUnicodeString : _string;
+ }
+ }
+
+ //
+ // Gets the host string that is unescaped and if it's Ipv6 host,
+ // then the returned string is suitable for DNS lookup.
+ //
+ // For Ipv6 this will strip [] and add ScopeId if was found in the original string
+ public string DnsSafeHost
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ if (AllowIdn && (((_flags & Flags.IdnHost) != 0) || ((_flags & Flags.UnicodeHost) != 0)))
+ {
+ // return pre generated idn
+ EnsureUriInfo();
+ return _info.DnsSafeHost;
+ }
+
+ EnsureHostString(false);
+
+ if (!string.IsNullOrEmpty(_info.DnsSafeHost))
+ {
+ // Cached
+ return _info.DnsSafeHost;
+ }
+ else if (_info.Host.Length == 0)
+ {
+ // Empty host, no possible processing
+ return string.Empty;
+ }
+
+ // Special case, will include ScopeID and strip [] around IPv6
+ // This will also unescape the host string
+ string ret = _info.Host;
+
+ if (HostType == Flags.IPv6HostType)
+ {
+ ret = ret.Substring(1, ret.Length - 2);
+ if ((object)_info.ScopeId != null)
+ {
+ ret += _info.ScopeId;
+ }
+ }
+ // Validate that this basic host qualifies as Dns safe,
+ // It has looser parsing rules that might allow otherwise.
+ // It might be a registry-based host from RFC 2396 Section 3.2.1
+ else if (HostType == Flags.BasicHostType
+ && InFact(Flags.HostNotCanonical | Flags.E_HostNotCanonical))
+ {
+ // Unescape everything
+ char[] dest = new char[ret.Length];
+ int count = 0;
+ UriHelper.UnescapeString(ret, 0, ret.Length, dest, ref count, c_DummyChar, c_DummyChar,
+ c_DummyChar, UnescapeMode.Unescape | UnescapeMode.UnescapeAll, _syntax, false);
+ ret = new string(dest, 0, count);
+ }
+
+ _info.DnsSafeHost = ret;
+
+ return ret;
+ }
+ }
+
+ // Returns the host name represented as IDN (using punycode encoding) regardless of app.config settings
+ public string IdnHost
+ {
+ get
+ {
+ string host = this.DnsSafeHost;
+
+ if (HostType == Flags.DnsHostType)
+ {
+ host = DomainNameHelper.IdnEquivalent(host);
+ }
+
+ return host;
+ }
+ }
+
+ //
+ // Returns false if the string passed in the constructor cannot be parsed as
+ // valid AbsoluteUri. This could be a relative Uri instead.
+ //
+ public bool IsAbsoluteUri
+ {
+ get
+ {
+ return _syntax != null;
+ }
+ }
+
+ //
+ // Returns 'true' if the 'dontEscape' parameter was set to 'true ' when the Uri instance was created.
+ public bool UserEscaped
+ {
+ get
+ {
+ return InFact(Flags.UserEscaped);
+ }
+ }
+
+ //
+ // Gets the user name, password, and other user specific information associated
+ // with the Uniform Resource Identifier (URI).
+ public string UserInfo
+ {
+ get
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ return GetParts(UriComponents.UserInfo, UriFormat.UriEscaped);
+ }
+ }
+
+ //
+ // CheckHostName
+ //
+ // Determines whether a host name authority is a valid Host name according
+ // to DNS naming rules and IPv4 canonicalization rules
+ //
+ // Returns:
+ // true if <name> is valid else false
+ //
+ // Throws:
+ // Nothing
+ //
+ public static UriHostNameType CheckHostName(string name)
+ {
+ if ((object)name == null || name.Length == 0 || name.Length > short.MaxValue)
+ {
+ return UriHostNameType.Unknown;
+ }
+ int end = name.Length;
+ unsafe
+ {
+ fixed (char* fixedName = name)
+ {
+ if (name[0] == '[' && name[name.Length - 1] == ']')
+ {
+ // we require that _entire_ name is recognized as ipv6 address
+ if (IPv6AddressHelper.IsValid(fixedName, 1, ref end) && end == name.Length)
+ {
+ return UriHostNameType.IPv6;
+ }
+ }
+ end = name.Length;
+ if (IPv4AddressHelper.IsValid(fixedName, 0, ref end, false, false, false) && end == name.Length)
+ {
+ return UriHostNameType.IPv4;
+ }
+ end = name.Length;
+ bool dummyBool = false;
+ if (DomainNameHelper.IsValid(fixedName, 0, ref end, ref dummyBool, false) && end == name.Length)
+ {
+ return UriHostNameType.Dns;
+ }
+
+ end = name.Length;
+ dummyBool = false;
+ if (DomainNameHelper.IsValidByIri(fixedName, 0, ref end, ref dummyBool, false)
+ && end == name.Length)
+ {
+ return UriHostNameType.Dns;
+ }
+ }
+
+ //This checks the form without []
+ end = name.Length + 2;
+ // we require that _entire_ name is recognized as ipv6 address
+ name = "[" + name + "]";
+ fixed (char* newFixedName = name)
+ {
+ if (IPv6AddressHelper.IsValid(newFixedName, 1, ref end) && end == name.Length)
+ {
+ return UriHostNameType.IPv6;
+ }
+ }
+ }
+ return UriHostNameType.Unknown;
+ }
+
+ //
+ // GetLeftPart
+ //
+ // Returns part of the URI based on the parameters:
+ //
+ // Inputs:
+ // <argument> part
+ // Which part of the URI to return
+ //
+ // Returns:
+ // The requested substring
+ //
+ // Throws:
+ // UriFormatException if URI type doesn't have host-port or authority parts
+ //
+ internal string GetLeftPart(UriPartial part)
+ {
+ if (IsNotAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ EnsureUriInfo();
+ const UriComponents NonPathPart = (UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port);
+
+ switch (part)
+ {
+ case UriPartial.Scheme:
+
+ return GetParts(UriComponents.Scheme | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+
+ case UriPartial.Authority:
+
+ if (NotAny(Flags.AuthorityFound) || IsDosPath)
+ {
+ // V1.0 compatibility.
+ // It not return an empty string but instead "scheme:" because it is a LEFT part.
+ // Also neither it should check for IsDosPath here
+
+ // From V1.0 comments:
+
+ // anything that didn't have "//" after the scheme name
+ // (mailto: and news: e.g.) doesn't have an authority
+ //
+
+ return string.Empty;
+ }
+ return GetParts(NonPathPart, UriFormat.UriEscaped);
+
+ case UriPartial.Path:
+ return GetParts(NonPathPart | UriComponents.Path, UriFormat.UriEscaped);
+
+ case UriPartial.Query:
+ return GetParts(NonPathPart | UriComponents.Path | UriComponents.Query, UriFormat.UriEscaped);
+ }
+ throw new ArgumentException("part");
+ }
+
+ //
+ // Is this a gen delim char from RFC 3986
+ //
+ internal static bool IsGenDelim(char ch)
+ {
+ return (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@');
+ }
+
+ //
+ // CheckSchemeName
+ //
+ // Determines whether a string is a valid scheme name according to RFC 2396.
+ // Syntax is:
+ // scheme = alpha *(alpha | digit | '+' | '-' | '.')
+ //
+ public static bool CheckSchemeName(string schemeName)
+ {
+ if (((object)schemeName == null)
+ || (schemeName.Length == 0)
+ || !IsAsciiLetter(schemeName[0]))
+ {
+ return false;
+ }
+ for (int i = schemeName.Length - 1; i > 0; --i)
+ {
+ if (!(IsAsciiLetterOrDigit(schemeName[i])
+ || (schemeName[i] == '+')
+ || (schemeName[i] == '-')
+ || (schemeName[i] == '.')))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //
+ // IsHexDigit
+ //
+ // Determines whether a character is a valid hexadecimal digit in the range
+ // [0..9] | [A..F] | [a..f]
+ //
+ // Inputs:
+ // <argument> character
+ // Character to test
+ //
+ // Returns:
+ // true if <character> is a hexadecimal digit character
+ //
+ // Throws:
+ // Nothing
+ //
+ internal static bool IsHexDigit(char character)
+ {
+ return ((character >= '0') && (character <= '9'))
+ || ((character >= 'A') && (character <= 'F'))
+ || ((character >= 'a') && (character <= 'f'));
+ }
+
+ //
+ // Returns:
+ // Number in the range 0..15
+ //
+ // Throws:
+ // ArgumentException
+ //
+ internal static int FromHex(char digit)
+ {
+ if (((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f')))
+ {
+ return (digit <= '9')
+ ? ((int)digit - (int)'0')
+ : (((digit <= 'F')
+ ? ((int)digit - (int)'A')
+ : ((int)digit - (int)'a'))
+ + 10);
+ }
+ throw new ArgumentException("digit");
+ }
+
+ //
+ // GetHashCode
+ //
+ // Overrides default function (in Object class)
+ //
+ //
+ public override int GetHashCode()
+ {
+ if (IsNotAbsoluteUri)
+ {
+ return CalculateCaseInsensitiveHashCode(OriginalString);
+ }
+
+ // Consider moving hash code storage from m_Info.MoreInfo to m_Info
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null)
+ {
+ info.MoreInfo = new MoreInfo();
+ }
+ int tempHash = info.MoreInfo.Hash;
+ if (tempHash == 0)
+ {
+ string chkString = info.MoreInfo.RemoteUrl;
+ if ((object)chkString == null)
+ chkString = GetParts(UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped);
+ tempHash = CalculateCaseInsensitiveHashCode(chkString);
+ if (tempHash == 0)
+ {
+ tempHash = 0x1000000; //making it not zero still large enough to be maped to zero by a hashtable
+ }
+ info.MoreInfo.Hash = tempHash;
+ }
+ return tempHash;
+ }
+
+ //
+ // ToString
+ //
+ // The better implementation would be just
+ //
+ private const UriFormat V1ToStringUnescape = (UriFormat)0x7FFF;
+
+ public override string ToString()
+ {
+ if (_syntax == null)
+ {
+ return (_iriParsing && InFact(Flags.HasUnicode)) ? _string : OriginalString;
+ }
+
+ EnsureUriInfo();
+ if ((object)_info.String == null)
+ {
+ if (Syntax.IsSimple)
+ _info.String = GetComponentsHelper(UriComponents.AbsoluteUri, V1ToStringUnescape);
+ else
+ _info.String = GetParts(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
+ }
+ return _info.String;
+ }
+
+ //
+ // A static shortcut to Uri.Equals
+ //
+ public static bool operator ==(Uri uri1, Uri uri2)
+ {
+ if ((object)uri1 == (object)uri2)
+ {
+ return true;
+ }
+ if ((object)uri1 == null || (object)uri2 == null)
+ {
+ return false;
+ }
+ return uri2.Equals(uri1);
+ }
+
+ //
+ // A static shortcut to !Uri.Equals
+ //
+ public static bool operator !=(Uri uri1, Uri uri2)
+ {
+ if ((object)uri1 == (object)uri2)
+ {
+ return false;
+ }
+
+ if ((object)uri1 == null || (object)uri2 == null)
+ {
+ return true;
+ }
+
+ return !uri2.Equals(uri1);
+ }
+
+ //
+ // Equals
+ //
+ // Overrides default function (in Object class)
+ //
+ // Assumes:
+ // <comparand> is an object of class Uri
+ //
+ // Returns:
+ // true if objects have the same value, else false
+ //
+ // Throws:
+ // Nothing
+ //
+ public override bool Equals(object comparand)
+ {
+ if ((object)comparand == null)
+ {
+ return false;
+ }
+
+ if ((object)this == (object)comparand)
+ {
+ return true;
+ }
+
+ Uri obj = comparand as Uri;
+
+ //
+ // we allow comparisons of Uri and String objects only. If a string
+ // is passed, convert to Uri. This is inefficient, but allows us to
+ // canonicalize the comparand, making comparison possible
+ //
+ if ((object)obj == null)
+ {
+ string s = comparand as string;
+
+ if ((object)s == null)
+ return false;
+
+ if (!TryCreate(s, UriKind.RelativeOrAbsolute, out obj))
+ return false;
+ }
+
+ // Since v1.0 two Uris are equal if everything but fragment and UserInfo does match
+
+ // This check is for a case where we already fixed up the equal references
+ if ((object)_string == (object)obj._string)
+ {
+ return true;
+ }
+
+ if (IsAbsoluteUri != obj.IsAbsoluteUri)
+ return false;
+
+ if (IsNotAbsoluteUri)
+ return OriginalString.Equals(obj.OriginalString);
+
+ if (NotAny(Flags.AllUriInfoSet) || obj.NotAny(Flags.AllUriInfoSet))
+ {
+ // Try raw compare for m_Strings as the last chance to keep the working set small
+ if (!IsUncOrDosPath)
+ {
+ if (_string.Length == obj._string.Length)
+ {
+ unsafe
+ {
+ // Try case sensitive compare on m_Strings
+ fixed (char* selfPtr = _string)
+ {
+ fixed (char* otherPtr = obj._string)
+ {
+ // This will never go negative since m_String is checked to be a valid URI
+ int i = (_string.Length - 1);
+ for (; i >= 0; --i)
+ {
+ if (*(selfPtr + i) != *(otherPtr + i))
+ {
+ break;
+ }
+ }
+ if (i == -1)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (string.Compare(_string, obj._string, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ return true;
+ }
+ }
+
+ // Note that equality test will bring the working set of both
+ // objects up to creation of m_Info.MoreInfo member
+ EnsureUriInfo();
+ obj.EnsureUriInfo();
+
+ if (!UserDrivenParsing && !obj.UserDrivenParsing && Syntax.IsSimple && obj.Syntax.IsSimple)
+ {
+ // Optimization of canonical DNS names by avoiding host string creation.
+ // Note there could be explicit ports specified that would invalidate path offsets
+ if (InFact(Flags.CanonicalDnsHost) && obj.InFact(Flags.CanonicalDnsHost))
+ {
+ ushort i1 = _info.Offset.Host;
+ ushort end1 = _info.Offset.Path;
+
+ ushort i2 = obj._info.Offset.Host;
+ ushort end2 = obj._info.Offset.Path;
+ string str = obj._string;
+ //Taking the shortest part
+ if (end1 - i1 > end2 - i2)
+ {
+ end1 = (ushort)(i1 + end2 - i2);
+ }
+ // compare and break on ':' if found
+ while (i1 < end1)
+ {
+ if (_string[i1] != str[i2])
+ {
+ return false;
+ }
+ if (str[i2] == ':')
+ {
+ // The other must have ':' too to have equal host
+ break;
+ }
+ ++i1; ++i2;
+ }
+
+ // The longest host must have ':' or be of the same size
+ if (i1 < _info.Offset.Path && _string[i1] != ':')
+ {
+ return false;
+ }
+ if (i2 < end2 && str[i2] != ':')
+ {
+ return false;
+ }
+ //hosts are equal!
+ }
+ else
+ {
+ EnsureHostString(false);
+ obj.EnsureHostString(false);
+ if (!_info.Host.Equals(obj._info.Host))
+ {
+ return false;
+ }
+ }
+
+ if (Port != obj.Port)
+ {
+ return false;
+ }
+ }
+
+ // We want to cache RemoteUrl to improve perf for Uri as a key.
+ // We should consider reducing the overall working set by not caching some other properties mentioned in MoreInfo
+
+ UriInfo selfInfo = _info;
+ UriInfo otherInfo = obj._info;
+ if ((object)selfInfo.MoreInfo == null)
+ {
+ selfInfo.MoreInfo = new MoreInfo();
+ }
+ if ((object)otherInfo.MoreInfo == null)
+ {
+ otherInfo.MoreInfo = new MoreInfo();
+ }
+
+ // NB: To avoid a race condition when creating MoreInfo field
+ // "selfInfo" and "otherInfo" shall remain as local copies.
+ string selfUrl = selfInfo.MoreInfo.RemoteUrl;
+ if ((object)selfUrl == null)
+ {
+ selfUrl = GetParts(UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped);
+ selfInfo.MoreInfo.RemoteUrl = selfUrl;
+ }
+ string otherUrl = otherInfo.MoreInfo.RemoteUrl;
+ if ((object)otherUrl == null)
+ {
+ otherUrl = obj.GetParts(UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped);
+ otherInfo.MoreInfo.RemoteUrl = otherUrl;
+ }
+
+ if (!IsUncOrDosPath)
+ {
+ if (selfUrl.Length != otherUrl.Length)
+ {
+ return false;
+ }
+ unsafe
+ {
+ // Try case sensitive compare on m_Strings
+ fixed (char* seltPtr = selfUrl)
+ {
+ fixed (char* otherPtr = otherUrl)
+ {
+ char* endSelf = seltPtr + selfUrl.Length;
+ char* endOther = otherPtr + selfUrl.Length;
+ while (endSelf != seltPtr)
+ {
+ if (*--endSelf != *--endOther)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ // if IsUncOrDosPath is true then we ignore case in the path comparison
+ // Get Unescaped form as most safe for the comparison
+ // Fragment AND UserInfo are ignored
+ //
+ return (string.Compare(selfInfo.MoreInfo.RemoteUrl,
+ otherInfo.MoreInfo.RemoteUrl,
+ IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0);
+ }
+
+ public Uri MakeRelativeUri(Uri uri)
+ {
+ if ((object)uri == null)
+ throw new ArgumentNullException("uri");
+
+ if (IsNotAbsoluteUri || uri.IsNotAbsoluteUri)
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+
+ // Note that the UserInfo part is ignored when computing a relative Uri.
+ if ((Scheme == uri.Scheme) && (Host == uri.Host) && (Port == uri.Port))
+ {
+ string otherPath = uri.AbsolutePath;
+
+ // Relative Path
+ string relativeUriString = PathDifference(AbsolutePath, otherPath, !IsUncOrDosPath);
+
+ // Relative Uri's cannot have a colon ':' in the first path segment (RFC 3986, Section 4.2)
+ if (CheckForColonInFirstPathSegment(relativeUriString)
+ // Except for full implicit dos file paths
+ && !(uri.IsDosPath && otherPath.Equals(relativeUriString, StringComparison.Ordinal)))
+ relativeUriString = "./" + relativeUriString;
+
+ // Query & Fragment
+ relativeUriString += uri.GetParts(UriComponents.Query | UriComponents.Fragment, UriFormat.UriEscaped);
+
+ return new Uri(relativeUriString, UriKind.Relative);
+ }
+ return uri;
+ }
+
+ //
+ // http://www.ietf.org/rfc/rfc3986.txt
+ //
+ // 3.3. Path
+ // In addition, a URI reference (Section 4.1) may be a relative-path reference, in which case the first
+ // path segment cannot contain a colon (":") character.
+ //
+ // 4.2. Relative Reference
+ // A path segment that contains a colon character (e.g., "this:that") cannot be used as the first segment
+ // of a relative-path reference, as it would be mistaken for a scheme name. Such a segment must be
+ // preceded by a dot-segment (e.g., "./this:that") to make a relative-path reference.
+ //
+ // 5.4.2. Abnormal Examples
+ // http:(relativeUri) may be considered a valid relative Uri.
+ //
+ // Returns true if a colon is found in the first path segment, false otherwise
+ //
+ private static bool CheckForColonInFirstPathSegment(string uriString)
+ {
+ // Check for anything that may terminate the first regular path segment
+ // or an illegal colon
+ char[] pathDelims = new char[] { ':', '\\', '/', '?', '#' };
+ int index = uriString.IndexOfAny(pathDelims);
+
+ return (index >= 0 && uriString[index] == ':');
+ }
+
+ internal unsafe static string InternalEscapeString(string rawString)
+ {
+ if ((object)rawString == null)
+ return string.Empty;
+
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(rawString, 0, rawString.Length, null, ref position, true, '?', '#', '%');
+ if ((object)dest == null)
+ return rawString;
+
+ return new string(dest, 0, position);
+ }
+
+ //
+ // This method is called first to figure out the scheme or a simple file path
+ // Is called only at the .ctor time
+ //
+ private static unsafe ParsingError ParseScheme(string uriString, ref Flags flags, ref UriParser syntax)
+ {
+ int length = uriString.Length;
+ if (length == 0)
+ return ParsingError.EmptyUriString;
+
+ if (length >= c_MaxUriBufferSize)
+ return ParsingError.SizeLimit;
+
+ //STEP1: parse scheme, lookup this Uri Syntax or create one using UnknownV1SyntaxFlags uri syntax template
+ fixed (char* pUriString = uriString)
+ {
+ ParsingError err = ParsingError.None;
+ ushort idx = ParseSchemeCheckImplicitFile(pUriString, (ushort)length, ref err, ref flags, ref syntax);
+
+ if (err != ParsingError.None)
+ return err;
+
+ flags |= (Flags)idx;
+ }
+ return ParsingError.None;
+ }
+
+ //
+ // A wrapper for ParseMinimal() called from a user parser
+ // It signals back that the call has been done
+ // plus it communicates back a flag for an error if any
+ //
+ internal UriFormatException ParseMinimal()
+ {
+ ParsingError result = PrivateParseMinimal();
+ if (result == ParsingError.None)
+ return null;
+
+ // Means the we think the Uri is invalid, bu that can be later overriden by a user parser
+ _flags |= Flags.ErrorOrParsingRecursion;
+
+ return GetException(result);
+ }
+
+ //
+ //
+ // This method tries to parse the minimal information needed to certify the valifity
+ // of a uri string
+ //
+ // scheme://userinfo@host:Port/Path?Query#Fragment
+ //
+ // The method must be called only at the .ctor time
+ //
+ // Returns ParsingError.None if the Uri syntax is valid, an error otheriwse
+ //
+ private unsafe ParsingError PrivateParseMinimal()
+ {
+ ushort idx = (ushort)(_flags & Flags.IndexMask);
+ ushort length = (ushort)_string.Length;
+ string newHost = null; // stores newly parsed host when original strings are being switched
+
+ // Means a custom UriParser did call "base" InitializeAndValidate()
+ _flags &= ~(Flags.IndexMask | Flags.UserDrivenParsing);
+
+ //STEP2: Parse up to the port
+
+ fixed (char* pUriString = ((_iriParsing &&
+ ((_flags & Flags.HasUnicode) != 0) &&
+ ((_flags & Flags.HostUnicodeNormalized) == 0)) ? _originalUnicodeString : _string))
+ {
+ // Cut trailing spaces in m_String
+ if (length > idx && IsLWS(pUriString[length - 1]))
+ {
+ --length;
+ while (length != idx && IsLWS(pUriString[--length]))
+ ;
+ ++length;
+ }
+
+ // Old Uri parser tries to figure out on a DosPath in all cases.
+ // Hence http://c:/ is treated as as DosPath without the host while it should be a host "c", port 80
+ //
+ // This block is compatible with Old Uri parser in terms it will look for the DosPath if the scheme
+ // syntax allows both empty hostnames and DosPath
+ //
+ if (_syntax.IsAllSet(UriSyntaxFlags.AllowEmptyHost | UriSyntaxFlags.AllowDOSPath)
+ && NotAny(Flags.ImplicitFile) && (idx + 1 < length))
+ {
+ char c;
+ ushort i = (ushort)idx;
+
+ // V1 Compat: Allow _compression_ of > 3 slashes only for File scheme.
+ // This will skip all slashes and if their number is 2+ it sets the AuthorityFound flag
+ for (; i < length; ++i)
+ {
+ if (!((c = pUriString[i]) == '\\' || c == '/'))
+ break;
+ }
+
+ if (_syntax.InFact(UriSyntaxFlags.FileLikeUri) || i - idx <= 3)
+ {
+ // if more than one slash after the scheme, the authority is present
+ if (i - idx >= 2)
+ {
+ _flags |= Flags.AuthorityFound;
+ }
+ // DOS-like path?
+ if (i + 1 < (ushort)length && ((c = pUriString[i + 1]) == ':' || c == '|') &&
+ IsAsciiLetter(pUriString[i]))
+ {
+ if (i + 2 >= (ushort)length || ((c = pUriString[i + 2]) != '\\' && c != '/'))
+ {
+ // report an error but only for a file: scheme
+ if (_syntax.InFact(UriSyntaxFlags.FileLikeUri))
+ return ParsingError.MustRootedPath;
+ }
+ else
+ {
+ // This will set IsDosPath
+ _flags |= Flags.DosPath;
+
+ if (_syntax.InFact(UriSyntaxFlags.MustHaveAuthority))
+ {
+ // when DosPath found and Authority is required, set this flag even if Authority is empty
+ _flags |= Flags.AuthorityFound;
+ }
+ if (i != idx && i - idx != 2)
+ {
+ //This will remember that DosPath is rooted
+ idx = (ushort)(i - 1);
+ }
+ else
+ {
+ idx = i;
+ }
+ }
+ }
+ else if (_syntax.InFact(UriSyntaxFlags.FileLikeUri) && (i - idx >= 2 && i - idx != 3 &&
+ i < length && pUriString[i] != '?' && pUriString[i] != '#'))
+ {
+ // V1.0 did not support file:///, fixing it with minimal behavior change impact
+ // Only FILE scheme may have UNC Path flag set
+ _flags |= Flags.UncPath;
+ idx = i;
+ }
+ }
+ }
+ //
+ //STEP 1.5 decide on the Authority component
+ //
+ if ((_flags & (Flags.UncPath | Flags.DosPath)) != 0)
+ {
+ }
+ else if ((idx + 2) <= length)
+ {
+ char first = pUriString[idx];
+ char second = pUriString[idx + 1];
+
+ if (_syntax.InFact(UriSyntaxFlags.MustHaveAuthority))
+ {
+ // (V1.0 compatiblity) This will allow http:\\ http:\/ http:/\
+ if ((first == '/' || first == '\\') && (second == '/' || second == '\\'))
+ {
+ _flags |= Flags.AuthorityFound;
+ idx += 2;
+ }
+ else
+ {
+ return ParsingError.BadAuthority;
+ }
+ }
+ else if (_syntax.InFact(UriSyntaxFlags.OptionalAuthority) && (InFact(Flags.AuthorityFound) ||
+ (first == '/' && second == '/')))
+ {
+ _flags |= Flags.AuthorityFound;
+ idx += 2;
+ }
+ else if (_syntax.NotAny(UriSyntaxFlags.MailToLikeUri))
+ {
+ // There is no Authority component, save the Path index
+ // Note: mailto is the only guy who is treated specially, should be not.
+ _flags |= ((Flags)idx | Flags.UnknownHostType);
+ return ParsingError.None;
+ }
+ }
+ else if (_syntax.InFact(UriSyntaxFlags.MustHaveAuthority))
+ {
+ return ParsingError.BadAuthority;
+ }
+ else if (_syntax.NotAny(UriSyntaxFlags.MailToLikeUri))
+ {
+ // There is no Authority component, save the Path index
+ // mailto is treated specially.
+ _flags |= ((Flags)idx | Flags.UnknownHostType);
+ return ParsingError.None;
+ }
+
+ // vsmacros://c:\path\file
+ // Note that two slashes say there must be an Authority but instead the path goes
+ // Fro V1 compat the next block allow this case but not for schemes like http
+ if (InFact(Flags.DosPath))
+ {
+ _flags |= (((_flags & Flags.AuthorityFound) != 0) ? Flags.BasicHostType : Flags.UnknownHostType);
+ _flags |= (Flags)idx;
+ return ParsingError.None;
+ }
+
+ //STEP 2: Check the syntax of authority expecting at least one character in it
+ //
+ // Note here we do know that there is an authority in the string OR it's a DOS path
+
+ // We may find a userInfo and the port when parsing an authority
+ // Also we may find a registry based authority.
+ // We must ensure that known schemes do use a server-based authority
+ {
+ ParsingError err = ParsingError.None;
+ idx = CheckAuthorityHelper(pUriString, idx, (ushort)length, ref err, ref _flags, _syntax, ref newHost);
+ if (err != ParsingError.None)
+ return err;
+
+ // This will disallow '\' as the host terminator for any scheme that is not implicitFile or cannot have a Dos Path
+ if ((idx < (ushort)length && pUriString[idx] == '\\') && NotAny(Flags.ImplicitFile) &&
+ _syntax.NotAny(UriSyntaxFlags.AllowDOSPath))
+ {
+ return ParsingError.BadAuthorityTerminator;
+ }
+ }
+
+ // The Path (or Port) parsing index is reloaded on demand in CreateUriInfo when accessing a Uri property
+ _flags |= (Flags)idx;
+
+ // The rest of the string will be parsed on demand
+ // The Host/Authorty is all checked, the type is known but the host value string
+ // is not created/canonicalized at this point.
+ }
+
+ if ((s_IdnScope != UriIdnScope.None) || _iriParsing)
+ PrivateParseMinimalIri(newHost, idx);
+
+ return ParsingError.None;
+ }
+
+ private void PrivateParseMinimalIri(string newHost, ushort idx)
+ {
+ // we have a new host!
+ if (newHost != null)
+ _string = newHost;
+
+ // conditions where we dont need to go to parseremaining, so we copy the rest of the
+ // original string.. and switch offsets
+ if ((!_iriParsing && AllowIdn && (((_flags & Flags.IdnHost) != 0) || ((_flags & Flags.UnicodeHost) != 0))) ||
+ (_iriParsing && ((_flags & Flags.HasUnicode) == 0) && AllowIdn && ((_flags & Flags.IdnHost) != 0)))
+ {
+ // update the start of path from the end of new string
+ _flags &= ~(Flags.IndexMask);
+ _flags |= (Flags)_string.Length;
+
+ _string += _originalUnicodeString.Substring(idx, _originalUnicodeString.Length - idx);
+ }
+
+ // Indicate to createuriinfo that offset is in m_originalUnicodeString
+ if (_iriParsing && ((_flags & Flags.HasUnicode) != 0))
+ {
+ // offset in Flags.IndexMask refers to m_originalUnicodeString
+ _flags |= Flags.UseOrigUncdStrOffset;
+ }
+ }
+
+ //
+ //
+ // The method is called when we have to access m_Info members
+ // This will create the m_Info based on the copied parser context
+ // Under milti-threading race this method may do duplicated yet harmless work
+ //
+ private unsafe void CreateUriInfo(Flags cF)
+ {
+ UriInfo info = new UriInfo();
+
+ // This will be revisited in ParseRemaining but for now just have it at least m_String.Length
+ info.Offset.End = (ushort)_string.Length;
+
+ if (UserDrivenParsing)
+ goto Done;
+
+ ushort idx;
+ bool notCanonicalScheme = false;
+
+ // The m_String may have leading spaces, figure that out
+ // plus it will set idx value for next steps
+ if ((cF & Flags.ImplicitFile) != 0)
+ {
+ idx = (ushort)0;
+ while (IsLWS(_string[idx]))
+ {
+ ++idx;
+ ++info.Offset.Scheme;
+ }
+
+ if (StaticInFact(cF, Flags.UncPath))
+ {
+ // For implicit file AND Unc only
+ idx += 2;
+ //skip any other slashes (compatibility with V1.0 parser)
+ while (idx < (ushort)(cF & Flags.IndexMask) && (_string[idx] == '/' || _string[idx] == '\\'))
+ {
+ ++idx;
+ }
+ }
+ }
+ else
+ {
+ // This is NOT an ImplicitFile uri
+ idx = (ushort)_syntax.SchemeName.Length;
+
+ while (_string[idx++] != ':')
+ {
+ ++info.Offset.Scheme;
+ }
+
+ if ((cF & Flags.AuthorityFound) != 0)
+ {
+ if (_string[idx] == '\\' || _string[idx + 1] == '\\')
+ notCanonicalScheme = true;
+
+ idx += 2;
+ if ((cF & (Flags.UncPath | Flags.DosPath)) != 0)
+ {
+ // Skip slashes if it was allowed during ctor time
+ // NB: Today this is only allowed if a Unc or DosPath was found after the scheme
+ while (idx < (ushort)(cF & Flags.IndexMask) && (_string[idx] == '/' || _string[idx] == '\\'))
+ {
+ notCanonicalScheme = true;
+ ++idx;
+ }
+ }
+ }
+ }
+
+ // Some schemes (mailto) do not have Authority-based syntax, still they do have a port
+ if (_syntax.DefaultPort != UriParser.NoDefaultPort)
+ info.Offset.PortValue = (ushort)_syntax.DefaultPort;
+
+ //Here we set the indexes for already parsed components
+ if ((cF & Flags.HostTypeMask) == Flags.UnknownHostType
+ || StaticInFact(cF, Flags.DosPath)
+ )
+ {
+ //there is no Authotity component defined
+ info.Offset.User = (ushort)(cF & Flags.IndexMask);
+ info.Offset.Host = info.Offset.User;
+ info.Offset.Path = info.Offset.User;
+ cF &= ~Flags.IndexMask;
+ if (notCanonicalScheme)
+ {
+ cF |= Flags.SchemeNotCanonical;
+ }
+ goto Done;
+ }
+
+ info.Offset.User = idx;
+
+ //Basic Host Type does not have userinfo and port
+ if (HostType == Flags.BasicHostType)
+ {
+ info.Offset.Host = idx;
+ info.Offset.Path = (ushort)(cF & Flags.IndexMask);
+ cF &= ~Flags.IndexMask;
+ goto Done;
+ }
+
+ if ((cF & Flags.HasUserInfo) != 0)
+ {
+ // we previously found a userinfo, get it again
+ while (_string[idx] != '@')
+ {
+ ++idx;
+ }
+ ++idx;
+ info.Offset.Host = idx;
+ }
+ else
+ {
+ info.Offset.Host = idx;
+ }
+
+ //Now reload the end of the parsed host
+
+ idx = (ushort)(cF & Flags.IndexMask);
+
+ //From now on we do not need IndexMask bits, and reuse the space for X_NotCanonical flags
+ //clear them now
+ cF &= ~Flags.IndexMask;
+
+ // If this is not canonical, don't count on user input to be good
+ if (notCanonicalScheme)
+ {
+ cF |= Flags.SchemeNotCanonical;
+ }
+
+ //Guessing this is a path start
+ info.Offset.Path = idx;
+
+ // parse Port if any. The new spec allows a port after ':' to be empty (assuming default?)
+ bool notEmpty = false;
+ // Note we already checked on general port syntax in ParseMinimal()
+
+ // If iri parsing is on with unicode chars then the end of parsed host
+ // points to m_orig string and not m_String
+
+ bool UseOrigUnicodeStrOffset = ((cF & Flags.UseOrigUncdStrOffset) != 0);
+ // This should happen only once. Reset it
+ cF &= ~Flags.UseOrigUncdStrOffset;
+
+ if (UseOrigUnicodeStrOffset)
+ info.Offset.End = (ushort)_originalUnicodeString.Length;
+
+ if (idx < info.Offset.End)
+ {
+ fixed (char* userString = UseOrigUnicodeStrOffset ? _originalUnicodeString : _string)
+ {
+ if (userString[idx] == ':')
+ {
+ int port = 0;
+
+ //Check on some noncanonical cases http://host:0324/, http://host:03, http://host:0, etc
+ if (++idx < info.Offset.End)
+ {
+ port = (ushort)(userString[idx] - '0');
+ if (!(port == unchecked((ushort)('/' - '0')) || port == (ushort)('?' - '0') ||
+ port == unchecked((ushort)('#' - '0'))))
+ {
+ notEmpty = true;
+ if (port == 0)
+ {
+ cF |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
+ }
+ for (++idx; idx < info.Offset.End; ++idx)
+ {
+ ushort val = (ushort)((ushort)userString[idx] - (ushort)'0');
+ if (val == unchecked((ushort)('/' - '0')) || val == (ushort)('?' - '0') ||
+ val == unchecked((ushort)('#' - '0')))
+ {
+ break;
+ }
+ port = (port * 10 + val);
+ }
+ }
+ }
+ if (notEmpty && info.Offset.PortValue != (ushort)port)
+ {
+ info.Offset.PortValue = (ushort)port;
+ cF |= Flags.NotDefaultPort;
+ }
+ else
+ {
+ //This will tell that we do have a ':' but the port value does
+ //not follow to canonical rules
+ cF |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
+ }
+ info.Offset.Path = (ushort)idx;
+ }
+ }
+ }
+
+ Done:
+ cF |= Flags.MinimalUriInfoSet;
+ info.DnsSafeHost = _dnsSafeHost;
+ lock (_string)
+ {
+ if ((_flags & Flags.MinimalUriInfoSet) == 0)
+ {
+ _info = info;
+ _flags = (_flags & ~Flags.IndexMask) | cF;
+ }
+ }
+ }
+
+ //
+ // This will create a Host string. The validity has been already checked
+ //
+ // Assuming: UriInfo memeber is already set at this point
+ private unsafe void CreateHostString()
+ {
+ if (!_syntax.IsSimple)
+ {
+ lock (_info)
+ {
+ // ATTN: Avoid possible recursion through
+ // CreateHostString->Syntax.GetComponents->Uri.GetComponentsHelper->CreateHostString
+ if (NotAny(Flags.ErrorOrParsingRecursion))
+ {
+ _flags |= Flags.ErrorOrParsingRecursion;
+ // Need to get host string through the derived type
+ GetHostViaCustomSyntax();
+ _flags &= ~Flags.ErrorOrParsingRecursion;
+ return;
+ }
+ }
+ }
+ Flags flags = _flags;
+ string host = CreateHostStringHelper(_string, _info.Offset.Host, _info.Offset.Path, ref flags, ref _info.ScopeId);
+
+ // now check on canonical host representation
+ if (host.Length != 0)
+ {
+ // An Authority may need escaping except when it's an inet server address
+ if (HostType == Flags.BasicHostType)
+ {
+ ushort idx = 0;
+ Check result;
+ fixed (char* pHost = host)
+ {
+ result = CheckCanonical(pHost, ref idx, (ushort)host.Length, c_DummyChar);
+ }
+
+ if ((result & Check.DisplayCanonical) == 0)
+ {
+ // For implicit file the user string must be in perfect display format,
+ // Hence, ignoring complains from CheckCanonical()
+ if (NotAny(Flags.ImplicitFile) || (result & Check.ReservedFound) != 0)
+ {
+ flags |= Flags.HostNotCanonical;
+ }
+ }
+
+ if (InFact(Flags.ImplicitFile) && (result & (Check.ReservedFound | Check.EscapedCanonical)) != 0)
+ {
+ // need to re-escape this host if any escaped sequence was found
+ result &= ~Check.EscapedCanonical;
+ }
+
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
+ {
+ // we will make a canonical host in m_Info.Host, but mark that m_String holds wrong data
+ flags |= Flags.E_HostNotCanonical;
+ if (NotAny(Flags.UserEscaped))
+ {
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(host, 0, host.Length, null, ref position, true, '?',
+ '#', IsImplicitFile ? c_DummyChar : '%');
+ if ((object)dest != null)
+ host = new string(dest, 0, position);
+ }
+ else
+ {
+ // We should throw here but currenty just accept user input known as invalid
+ }
+ }
+ }
+ else if (NotAny(Flags.CanonicalDnsHost))
+ {
+ // Check to see if we can take the canonical host string out of m_String
+ if ((object)_info.ScopeId != null)
+ {
+ // IPv6 ScopeId is included when serializing a Uri
+ flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
+ }
+ else
+ {
+ for (ushort i = 0; i < host.Length; ++i)
+ {
+ if ((_info.Offset.Host + i) >= _info.Offset.End ||
+ host[i] != _string[_info.Offset.Host + i])
+ {
+ flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ _info.Host = host;
+ lock (_info)
+ {
+ _flags |= flags;
+ }
+ }
+
+ private static string CreateHostStringHelper(string str, ushort idx, ushort end, ref Flags flags, ref string scopeId)
+ {
+ bool loopback = false;
+ string host;
+ switch (flags & Flags.HostTypeMask)
+ {
+ case Flags.DnsHostType:
+ host = DomainNameHelper.ParseCanonicalName(str, idx, end, ref loopback);
+ break;
+
+ case Flags.IPv6HostType:
+ // The helper will return [...] string that is not suited for Dns.Resolve()
+ host = IPv6AddressHelper.ParseCanonicalName(str, idx, ref loopback, ref scopeId);
+ break;
+
+ case Flags.IPv4HostType:
+ host = IPv4AddressHelper.ParseCanonicalName(str, idx, end, ref loopback);
+ break;
+
+ case Flags.UncHostType:
+ host = UncNameHelper.ParseCanonicalName(str, idx, end, ref loopback);
+ break;
+
+ case Flags.BasicHostType:
+ if (StaticInFact(flags, Flags.DosPath))
+ {
+ host = string.Empty;
+ }
+ else
+ {
+ // This is for a registry-based authority, not relevant for known schemes
+ host = str.Substring(idx, end - idx);
+ }
+ // A empty host would count for a loopback
+ if (host.Length == 0)
+ {
+ loopback = true;
+ }
+ //there will be no port
+ break;
+
+ case Flags.UnknownHostType:
+ //means the host is *not expected* for this uri type
+ host = string.Empty;
+ break;
+
+ default:
+ throw GetException(ParsingError.BadHostName);
+ }
+
+ if (loopback)
+ {
+ flags |= Flags.LoopbackHost;
+ }
+ return host;
+ }
+
+ //
+ // Called under lock()
+ //
+ private unsafe void GetHostViaCustomSyntax()
+ {
+ // A multithreading check
+ if (_info.Host != null)
+ return;
+
+ string host = _syntax.InternalGetComponents(this, UriComponents.Host, UriFormat.UriEscaped);
+
+ // ATTN: Check on whether recursion has not happened
+ if ((object)_info.Host == null)
+ {
+ if (host.Length >= c_MaxUriBufferSize)
+ throw GetException(ParsingError.SizeLimit);
+
+ ParsingError err = ParsingError.None;
+ Flags flags = _flags & ~Flags.HostTypeMask;
+
+ fixed (char* pHost = host)
+ {
+ string newHost = null;
+ if (CheckAuthorityHelper(pHost, 0, (ushort)host.Length, ref err, ref flags, _syntax, ref newHost) !=
+ (ushort)host.Length)
+ {
+ // We cannot parse the entire host string
+ flags &= ~Flags.HostTypeMask;
+ flags |= Flags.UnknownHostType;
+ }
+ }
+
+ if (err != ParsingError.None || (flags & Flags.HostTypeMask) == Flags.UnknownHostType)
+ {
+ // Well, custom parser has returned a not known host type, take it as Basic then.
+ _flags = (_flags & ~Flags.HostTypeMask) | Flags.BasicHostType;
+ }
+ else
+ {
+ host = CreateHostStringHelper(host, 0, (ushort)host.Length, ref flags, ref _info.ScopeId);
+ for (ushort i = 0; i < host.Length; ++i)
+ {
+ if ((_info.Offset.Host + i) >= _info.Offset.End || host[i] != _string[_info.Offset.Host + i])
+ {
+ _flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
+ break;
+ }
+ }
+ _flags = (_flags & ~Flags.HostTypeMask) | (flags & Flags.HostTypeMask);
+ }
+ }
+ //
+ // This is a chance for a custom parser to report a different port value
+ //
+ string portStr = _syntax.InternalGetComponents(this, UriComponents.StrongPort, UriFormat.UriEscaped);
+ int port = 0;
+ if ((object)portStr == null || portStr.Length == 0)
+ {
+ // It's like no port
+ _flags &= ~Flags.NotDefaultPort;
+ _flags |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
+ _info.Offset.PortValue = 0;
+ }
+ else
+ {
+ for (int idx = 0; idx < portStr.Length; ++idx)
+ {
+ int val = portStr[idx] - '0';
+ if (val < 0 || val > 9 || (port = (port * 10 + val)) > 0xFFFF)
+ throw new UriFormatException(SR.Format(SR.net_uri_PortOutOfRange, _syntax.GetType().ToString(), portStr));
+ }
+ if (port != _info.Offset.PortValue)
+ {
+ if (port == _syntax.DefaultPort)
+ _flags &= ~Flags.NotDefaultPort;
+ else
+ _flags |= Flags.NotDefaultPort;
+
+ _flags |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
+ _info.Offset.PortValue = (ushort)port;
+ }
+ }
+ // This must be done as the last thing in this method
+ _info.Host = host;
+ }
+
+ //
+ // An internal shortcut into Uri extenisiblity API
+ //
+ internal string GetParts(UriComponents uriParts, UriFormat formatAs)
+ {
+ return GetComponents(uriParts, formatAs);
+ }
+
+ private string GetEscapedParts(UriComponents uriParts)
+ {
+ // Which Uri parts are not escaped canonically ?
+ // Notice that public UriPart and private Flags must me in Sync so below code can work
+ //
+ ushort nonCanonical = (ushort)(((ushort)_flags & ((ushort)Flags.CannotDisplayCanonical << 7)) >> 6);
+ if (InFact(Flags.SchemeNotCanonical))
+ {
+ nonCanonical |= (ushort)Flags.SchemeNotCanonical;
+ }
+
+ // We keep separate flags for some of path canonicalization facts
+ if ((uriParts & UriComponents.Path) != 0)
+ {
+ if (InFact(Flags.ShouldBeCompressed | Flags.FirstSlashAbsent | Flags.BackslashInPath))
+ {
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ else if (IsDosPath && _string[_info.Offset.Path + SecuredPathIndex - 1] == '|')
+ {
+ // A rare case of c|\
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ }
+
+ if (((ushort)uriParts & nonCanonical) == 0)
+ {
+ string ret = GetUriPartsFromUserString(uriParts);
+ if ((object)ret != null)
+ {
+ return ret;
+ }
+ }
+
+ return ReCreateParts(uriParts, nonCanonical, UriFormat.UriEscaped);
+ }
+
+ private string GetUnescapedParts(UriComponents uriParts, UriFormat formatAs)
+ {
+ // Which Uri parts are not escaped canonically ?
+ // Notice that public UriComponents and private Uri.Flags must me in Sync so below code can work
+ //
+ ushort nonCanonical = (ushort)((ushort)_flags & (ushort)Flags.CannotDisplayCanonical);
+
+ // We keep separate flags for some of path canonicalization facts
+ if ((uriParts & UriComponents.Path) != 0)
+ {
+ if ((_flags & (Flags.ShouldBeCompressed | Flags.FirstSlashAbsent | Flags.BackslashInPath)) != 0)
+ {
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ else if (IsDosPath && _string[_info.Offset.Path + SecuredPathIndex - 1] == '|')
+ {
+ // A rare case of c|\
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ }
+
+ if (((ushort)uriParts & nonCanonical) == 0)
+ {
+ string ret = GetUriPartsFromUserString(uriParts);
+ if ((object)ret != null)
+ {
+ return ret;
+ }
+ }
+
+ return ReCreateParts(uriParts, nonCanonical, formatAs);
+ }
+
+ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat formatAs)
+ {
+ EnsureHostString(false);
+ string stemp = (parts & UriComponents.Host) == 0 ? string.Empty : _info.Host;
+ // we reserve more space than required because a canonical Ipv6 Host
+ // may take more characteres than in original m_String
+ // Also +3 is for :// and +1 is for absent first slash
+ // Also we may escape every character, hence multiplying by 12
+ // UTF-8 can use up to 4 bytes per char * 3 chars per byte (%A4) = 12 encoded chars
+ int count = (_info.Offset.End - _info.Offset.User) * (formatAs == UriFormat.UriEscaped ? 12 : 1);
+ char[] chars = new char[stemp.Length + count + _syntax.SchemeName.Length + 3 + 1];
+ count = 0;
+
+ //Scheme and slashes
+ if ((parts & UriComponents.Scheme) != 0)
+ {
+ _syntax.SchemeName.CopyTo(0, chars, count, _syntax.SchemeName.Length);
+ count += _syntax.SchemeName.Length;
+ if (parts != UriComponents.Scheme)
+ {
+ chars[count++] = ':';
+ if (InFact(Flags.AuthorityFound))
+ {
+ chars[count++] = '/';
+ chars[count++] = '/';
+ }
+ }
+ }
+
+ //UserInfo
+ if ((parts & UriComponents.UserInfo) != 0 && InFact(Flags.HasUserInfo))
+ {
+ if ((nonCanonical & (ushort)UriComponents.UserInfo) != 0)
+ {
+ switch (formatAs)
+ {
+ case UriFormat.UriEscaped:
+ if (NotAny(Flags.UserEscaped))
+ {
+ chars = UriHelper.EscapeString(_string, _info.Offset.User, _info.Offset.Host, chars,
+ ref count, true, '?', '#', '%');
+ }
+ else
+ {
+ if (InFact(Flags.E_UserNotCanonical))
+ {
+ // We should throw here but currenty just accept user input known as invalid
+ }
+ _string.CopyTo(_info.Offset.User, chars, count, _info.Offset.Host - _info.Offset.User);
+ count += (_info.Offset.Host - _info.Offset.User);
+ }
+ break;
+
+ case UriFormat.SafeUnescaped:
+ chars = UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host - 1,
+ chars, ref count, '@', '/', '\\', InFact(Flags.UserEscaped) ? UnescapeMode.Unescape :
+ UnescapeMode.EscapeUnescape, _syntax, false);
+ chars[count++] = '@';
+ break;
+
+ case UriFormat.Unescaped:
+ chars = UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar,
+ UnescapeMode.Unescape | UnescapeMode.UnescapeAll, _syntax, false);
+ break;
+
+ default: //V1ToStringUnescape
+ chars = UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax,
+ false);
+ break;
+ }
+ }
+ else
+ {
+ UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host, chars, ref count,
+ c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, false);
+ }
+ if (parts == UriComponents.UserInfo)
+ {
+ //strip '@' delimiter
+ --count;
+ }
+ }
+
+ // Host
+ if ((parts & UriComponents.Host) != 0 && stemp.Length != 0)
+ {
+ UnescapeMode mode;
+ if (formatAs != UriFormat.UriEscaped && HostType == Flags.BasicHostType
+ && (nonCanonical & (ushort)UriComponents.Host) != 0)
+ {
+ // only Basic host could be in the escaped form
+ mode = formatAs == UriFormat.Unescaped
+ ? (UnescapeMode.Unescape | UnescapeMode.UnescapeAll) :
+ (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape);
+ }
+ else
+ {
+ mode = UnescapeMode.CopyOnly;
+ }
+ // NormalizedHost
+ if ((parts & UriComponents.NormalizedHost) != 0)
+ {
+ unsafe
+ {
+ fixed (char* hostPtr = stemp)
+ {
+ bool allAscii = false;
+ bool atLeastOneValidIdn = false;
+ try
+ {
+ // Upconvert any punycode to unicode, xn--pck -> ?
+ stemp = DomainNameHelper.UnicodeEquivalent(
+ hostPtr, 0, stemp.Length, ref allAscii, ref atLeastOneValidIdn);
+ }
+ // The host may be invalid punycode (www.xn--?-pck.com),
+ // but we shouldn't throw after the constructor.
+ catch (UriFormatException) { }
+ }
+ }
+ }
+ chars = UriHelper.UnescapeString(stemp, 0, stemp.Length, chars, ref count, '/', '?', '#', mode,
+ _syntax, false);
+
+ // A fix up only for SerializationInfo and IpV6 host with a scopeID
+ if ((parts & UriComponents.SerializationInfoString) != 0 && HostType == Flags.IPv6HostType &&
+ (object)_info.ScopeId != null)
+ {
+ _info.ScopeId.CopyTo(0, chars, count - 1, _info.ScopeId.Length);
+ count += _info.ScopeId.Length;
+ chars[count - 1] = ']';
+ }
+ }
+
+ //Port (always wants a ':' delimiter if got to this method)
+ if ((parts & UriComponents.Port) != 0)
+ {
+ if ((nonCanonical & (ushort)UriComponents.Port) == 0)
+ {
+ //take it from m_String
+ if (InFact(Flags.NotDefaultPort))
+ {
+ ushort start = _info.Offset.Path;
+ while (_string[--start] != ':')
+ {
+ ;
+ }
+ _string.CopyTo(start, chars, count, _info.Offset.Path - start);
+ count += (_info.Offset.Path - start);
+ }
+ else if ((parts & UriComponents.StrongPort) != 0 && _syntax.DefaultPort != UriParser.NoDefaultPort)
+ {
+ chars[count++] = ':';
+ stemp = _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+ stemp.CopyTo(0, chars, count, stemp.Length);
+ count += stemp.Length;
+ }
+ }
+ else if (InFact(Flags.NotDefaultPort) || ((parts & UriComponents.StrongPort) != 0 &&
+ _syntax.DefaultPort != UriParser.NoDefaultPort))
+ {
+ // recreate string from port value
+ chars[count++] = ':';
+ stemp = _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+ stemp.CopyTo(0, chars, count, stemp.Length);
+ count += stemp.Length;
+ }
+ }
+
+ ushort delimiterAwareIndex;
+
+ //Path
+ if ((parts & UriComponents.Path) != 0)
+ {
+ chars = GetCanonicalPath(chars, ref count, formatAs);
+
+ // (possibly strip the leading '/' delimiter)
+ if (parts == UriComponents.Path)
+ {
+ if (InFact(Flags.AuthorityFound) && count != 0 && chars[0] == '/')
+ {
+ delimiterAwareIndex = 1; --count;
+ }
+ else
+ {
+ delimiterAwareIndex = 0;
+ }
+ return count == 0 ? string.Empty : new string(chars, delimiterAwareIndex, count);
+ }
+ }
+
+ //Query (possibly strip the '?' delimiter)
+ if ((parts & UriComponents.Query) != 0 && _info.Offset.Query < _info.Offset.Fragment)
+ {
+ delimiterAwareIndex = (ushort)(_info.Offset.Query + 1);
+ if (parts != UriComponents.Query)
+ chars[count++] = '?'; //see Fragment+1 below
+
+ if ((nonCanonical & (ushort)UriComponents.Query) != 0)
+ {
+ switch (formatAs)
+ {
+ case UriFormat.UriEscaped:
+ //Can Assert IsImplicitfile == false
+ if (NotAny(Flags.UserEscaped))
+ chars = UriHelper.EscapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars,
+ ref count, true, '#', c_DummyChar, '%');
+ else
+ {
+ UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax,
+ true);
+ }
+ break;
+
+ case V1ToStringUnescape:
+
+ chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag,
+ _syntax, true);
+ break;
+
+ case UriFormat.Unescaped:
+
+ chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars,
+ ref count, '#', c_DummyChar, c_DummyChar,
+ (UnescapeMode.Unescape | UnescapeMode.UnescapeAll), _syntax, true);
+ break;
+
+ default: // UriFormat.SafeUnescaped
+
+ chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape), _syntax, true);
+ break;
+ }
+ }
+ else
+ {
+ UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars, ref count,
+ c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, true);
+ }
+ }
+
+ //Fragment (possibly strip the '#' delimiter)
+ if ((parts & UriComponents.Fragment) != 0 && _info.Offset.Fragment < _info.Offset.End)
+ {
+ delimiterAwareIndex = (ushort)(_info.Offset.Fragment + 1);
+ if (parts != UriComponents.Fragment)
+ chars[count++] = '#'; //see Fragment+1 below
+
+ if ((nonCanonical & (ushort)UriComponents.Fragment) != 0)
+ {
+ switch (formatAs)
+ {
+ case UriFormat.UriEscaped:
+ if (NotAny(Flags.UserEscaped))
+ chars = UriHelper.EscapeString(_string, delimiterAwareIndex, _info.Offset.End, chars,
+ ref count, true, c_DummyChar, c_DummyChar, '%');
+ else
+ {
+ UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax,
+ false);
+ }
+ break;
+
+ case V1ToStringUnescape:
+
+ chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag,
+ _syntax, false);
+ break;
+ case UriFormat.Unescaped:
+
+ chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars,
+ ref count, '#', c_DummyChar, c_DummyChar,
+ UnescapeMode.Unescape | UnescapeMode.UnescapeAll, _syntax, false);
+ break;
+
+ default: // UriFormat.SafeUnescaped
+
+ chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape), _syntax, false);
+ break;
+ }
+ }
+ else
+ {
+ UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars, ref count,
+ c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, false);
+ }
+ }
+
+ return new string(chars, 0, count);
+ }
+
+ //
+ // This method is called only if the user string has a canonical representation
+ // of requested parts
+ //
+ private string GetUriPartsFromUserString(UriComponents uriParts)
+ {
+ ushort delimiterAwareIdx;
+
+ switch (uriParts & ~UriComponents.KeepDelimiter)
+ {
+ // For FindServicePoint perf
+ case UriComponents.Scheme | UriComponents.Host | UriComponents.Port:
+ if (!InFact(Flags.HasUserInfo))
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.Path - _info.Offset.Scheme);
+
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme)
+ + _string.Substring(_info.Offset.Host, _info.Offset.Path - _info.Offset.Host);
+
+ // For HttpWebRequest.ConnectHostAndPort perf
+ case UriComponents.HostAndPort: //Host|StrongPort
+
+ if (!InFact(Flags.HasUserInfo))
+ goto case UriComponents.StrongAuthority;
+
+ if (InFact(Flags.NotDefaultPort) || _syntax.DefaultPort == UriParser.NoDefaultPort)
+ return _string.Substring(_info.Offset.Host, _info.Offset.Path - _info.Offset.Host);
+
+ return _string.Substring(_info.Offset.Host, _info.Offset.Path - _info.Offset.Host)
+ + ':' + _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+
+ // For an obvious common case perf
+ case UriComponents.AbsoluteUri: //Scheme|UserInfo|Host|Port|Path|Query|Fragment,
+ if (_info.Offset.Scheme == 0 && _info.Offset.End == _string.Length)
+ return _string;
+
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.End - _info.Offset.Scheme);
+
+ // For Uri.Equals() and HttpWebRequest through a proxy perf
+ case UriComponents.HttpRequestUrl: //Scheme|Host|Port|Path|Query,
+ if (InFact(Flags.HasUserInfo))
+ {
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme)
+ + _string.Substring(_info.Offset.Host, _info.Offset.Fragment - _info.Offset.Host);
+ }
+ if (_info.Offset.Scheme == 0 && _info.Offset.Fragment == _string.Length)
+ return _string;
+
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.Fragment - _info.Offset.Scheme);
+
+ // For CombineUri() perf
+ case UriComponents.SchemeAndServer | UriComponents.UserInfo:
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.Path - _info.Offset.Scheme);
+
+ // For Cache perf
+ case (UriComponents.AbsoluteUri & ~UriComponents.Fragment):
+ if (_info.Offset.Scheme == 0 && _info.Offset.Fragment == _string.Length)
+ return _string;
+
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.Fragment - _info.Offset.Scheme);
+
+
+ // Strip scheme delimiter if was not requested
+ case UriComponents.Scheme:
+ if (uriParts != UriComponents.Scheme)
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme);
+
+ return _syntax.SchemeName;
+
+ // KeepDelimiter makes no sense for this component
+ case UriComponents.Host:
+ ushort idx = _info.Offset.Path;
+ if (InFact(Flags.NotDefaultPort | Flags.PortNotCanonical))
+ {
+ //Means we do have ':' after the host
+ while (_string[--idx] != ':')
+ ;
+ }
+ return (idx - _info.Offset.Host == 0) ? string.Empty : _string.Substring(_info.Offset.Host,
+ idx - _info.Offset.Host);
+
+ case UriComponents.Path:
+
+ // Strip the leading '/' for a hierarchical URI if no delimiter was requested
+ if (uriParts == UriComponents.Path && InFact(Flags.AuthorityFound) &&
+ _info.Offset.End > _info.Offset.Path && _string[_info.Offset.Path] == '/')
+ delimiterAwareIdx = (ushort)(_info.Offset.Path + 1);
+ else
+ delimiterAwareIdx = _info.Offset.Path;
+
+ if (delimiterAwareIdx >= _info.Offset.Query)
+ return string.Empty;
+
+
+ return _string.Substring(delimiterAwareIdx, _info.Offset.Query - delimiterAwareIdx);
+
+ case UriComponents.Query:
+ // Strip the '?' if no delimiter was requested
+ if (uriParts == UriComponents.Query)
+ delimiterAwareIdx = (ushort)(_info.Offset.Query + 1);
+ else
+ delimiterAwareIdx = _info.Offset.Query;
+
+ if (delimiterAwareIdx >= _info.Offset.Fragment)
+ return string.Empty;
+
+ return _string.Substring(delimiterAwareIdx, _info.Offset.Fragment - delimiterAwareIdx);
+
+ case UriComponents.Fragment:
+ // Strip the '#' if no delimiter was requested
+ if (uriParts == UriComponents.Fragment)
+ delimiterAwareIdx = (ushort)(_info.Offset.Fragment + 1);
+ else
+ delimiterAwareIdx = _info.Offset.Fragment;
+
+ if (delimiterAwareIdx >= _info.Offset.End)
+ return string.Empty;
+
+ return _string.Substring(delimiterAwareIdx, _info.Offset.End - delimiterAwareIdx);
+
+ case UriComponents.UserInfo | UriComponents.Host | UriComponents.Port:
+ return (_info.Offset.Path - _info.Offset.User == 0) ? string.Empty :
+ _string.Substring(_info.Offset.User, _info.Offset.Path - _info.Offset.User);
+
+ case UriComponents.StrongAuthority: //UserInfo|Host|StrongPort
+ if (InFact(Flags.NotDefaultPort) || _syntax.DefaultPort == UriParser.NoDefaultPort)
+ goto case UriComponents.UserInfo | UriComponents.Host | UriComponents.Port;
+
+ return _string.Substring(_info.Offset.User, _info.Offset.Path - _info.Offset.User)
+ + ':' + _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+
+ case UriComponents.PathAndQuery: //Path|Query,
+ return _string.Substring(_info.Offset.Path, _info.Offset.Fragment - _info.Offset.Path);
+
+ case UriComponents.HttpRequestUrl | UriComponents.Fragment: //Scheme|Host|Port|Path|Query|Fragment,
+ if (InFact(Flags.HasUserInfo))
+ {
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme)
+ + _string.Substring(_info.Offset.Host, _info.Offset.End - _info.Offset.Host);
+ }
+ if (_info.Offset.Scheme == 0 && _info.Offset.End == _string.Length)
+ return _string;
+
+ return _string.Substring(_info.Offset.Scheme, _info.Offset.End - _info.Offset.Scheme);
+
+ case UriComponents.PathAndQuery | UriComponents.Fragment: //LocalUrl|Fragment
+ return _string.Substring(_info.Offset.Path, _info.Offset.End - _info.Offset.Path);
+
+ case UriComponents.UserInfo:
+ // Strip the '@' if no delimiter was requested
+
+ if (NotAny(Flags.HasUserInfo))
+ return string.Empty;
+
+ if (uriParts == UriComponents.UserInfo)
+ delimiterAwareIdx = (ushort)(_info.Offset.Host - 1);
+ else
+ delimiterAwareIdx = _info.Offset.Host;
+
+ if (_info.Offset.User >= delimiterAwareIdx)
+ return string.Empty;
+
+ return _string.Substring(_info.Offset.User, delimiterAwareIdx - _info.Offset.User);
+
+ default:
+ return null;
+ }
+ }
+
+ //
+ //This method does:
+ // - Creates m_Info member
+ // - checks all componenets up to path on their canonical representation
+ // - continues parsing starting the path position
+ // - Sets the offsets of remaining components
+ // - Sets the Canonicalization flags if applied
+ // - Will NOT create MoreInfo members
+ //
+ private unsafe void ParseRemaining()
+ {
+ // ensure we parsed up to the path
+ EnsureUriInfo();
+
+ Flags cF = Flags.Zero;
+
+ if (UserDrivenParsing)
+ goto Done;
+
+ // Do we have to continue building Iri'zed string from original string
+ bool buildIriStringFromPath = _iriParsing && ((_flags & Flags.HasUnicode) != 0) && ((_flags & Flags.RestUnicodeNormalized) == 0);
+
+ ushort origIdx; // stores index to switched original string
+ ushort idx = _info.Offset.Scheme;
+ ushort length = (ushort)_string.Length;
+ Check result = Check.None;
+ UriSyntaxFlags syntaxFlags = _syntax.Flags; // perf
+
+ // m_Info.Offset values may be parsed twice but we lock only on m_Flags update.
+
+ fixed (char* str = _string)
+ {
+ // Cut trailing spaces in m_String
+ if (length > idx && IsLWS(str[length - 1]))
+ {
+ --length;
+ while (length != idx && IsLWS(str[--length]))
+ ;
+ ++length;
+ }
+
+ if (IsImplicitFile)
+ {
+ cF |= Flags.SchemeNotCanonical;
+ }
+ else
+ {
+ ushort i = 0;
+ ushort syntaxLength = (ushort)_syntax.SchemeName.Length;
+ for (; i < syntaxLength; ++i)
+ {
+ if (_syntax.SchemeName[i] != str[idx + i])
+ cF |= Flags.SchemeNotCanonical;
+ }
+ // For an authority Uri only // after the scheme would be canonical
+ // (for compatibility with: http:\\host)
+ if (((_flags & Flags.AuthorityFound) != 0) && (idx + i + 3 >= length || str[idx + i + 1] != '/' ||
+ str[idx + i + 2] != '/'))
+ {
+ cF |= Flags.SchemeNotCanonical;
+ }
+ }
+
+
+ //Check the form of the user info
+ if ((_flags & Flags.HasUserInfo) != 0)
+ {
+ idx = _info.Offset.User;
+ result = CheckCanonical(str, ref idx, _info.Offset.Host, '@');
+ if ((result & Check.DisplayCanonical) == 0)
+ {
+ cF |= Flags.UserNotCanonical;
+ }
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
+ {
+ cF |= Flags.E_UserNotCanonical;
+ }
+ if (_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii)))
+ {
+ cF |= Flags.UserIriCanonical;
+ }
+ }
+ }
+ //
+ // Delay canonical Host checking to avoid creation of a host string
+ // Will do that on demand.
+ //
+
+
+ //
+ //We have already checked on the port in EnsureUriInfo() that calls CreateUriInfo
+ //
+
+ //
+ // Parsing the Path if any
+ //
+
+ // For iri parsing if we found unicode the idx has offset into m_orig string..
+ // so restart parsing from there and make m_Info.Offset.Path as m_string.length
+
+ idx = _info.Offset.Path;
+ origIdx = _info.Offset.Path;
+
+ //Some uris do not have a query
+ // When '?' is passed as delimiter, then it's special case
+ // so both '?' and '#' will work as delimiters
+ if (buildIriStringFromPath)
+ {
+ // Dos paths have no host. Other schemes cleared/set m_String with host information in PrivateParseMinimal.
+ if (IsDosPath)
+ {
+ if (IsImplicitFile)
+ {
+ _string = string.Empty;
+ }
+ else
+ {
+ _string = _syntax.SchemeName + SchemeDelimiter;
+ }
+ }
+
+ _info.Offset.Path = (ushort)_string.Length;
+ idx = _info.Offset.Path;
+
+ ushort offset = origIdx;
+ if (IsImplicitFile || ((syntaxFlags & (UriSyntaxFlags.MayHaveQuery | UriSyntaxFlags.MayHaveFragment)) == 0))
+ {
+ FindEndOfComponent(_originalUnicodeString, ref origIdx, (ushort)_originalUnicodeString.Length, c_DummyChar);
+ }
+ else
+ {
+ FindEndOfComponent(_originalUnicodeString, ref origIdx, (ushort)_originalUnicodeString.Length,
+ (_syntax.InFact(UriSyntaxFlags.MayHaveQuery) ? '?' : _syntax.InFact(UriSyntaxFlags.MayHaveFragment) ? '#' : c_EOL));
+ }
+
+ // Correctly escape unescape
+ string escapedPath = EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Path);
+
+ // Normalize path
+ try
+ {
+ _string += escapedPath;
+ }
+ catch (ArgumentException)
+ {
+ UriFormatException e = GetException(ParsingError.BadFormat);
+ throw e;
+ }
+
+ length = (ushort)_string.Length;
+ }
+
+ fixed (char* str = _string)
+ {
+ if (IsImplicitFile || ((syntaxFlags & (UriSyntaxFlags.MayHaveQuery | UriSyntaxFlags.MayHaveFragment)) == 0))
+ {
+ result = CheckCanonical(str, ref idx, length, c_DummyChar);
+ }
+ else
+ {
+ result = CheckCanonical(str, ref idx, length, (((syntaxFlags & UriSyntaxFlags.MayHaveQuery) != 0)
+ ? '?' : _syntax.InFact(UriSyntaxFlags.MayHaveFragment) ? '#' : c_EOL));
+ }
+
+ // ATTN:
+ // This may render problems for unknown schemes, but in general for an authority based Uri
+ // (that has slashes) a path should start with "/"
+ // This becomes more interesting knowning how a file uri is used in "file://c:/path"
+ // It will be converted to file:///c:/path
+ //
+ // However, even more interesting is that vsmacros://c:\path will not add the third slash in the _canoical_ case
+ //
+ // We use special syntax flag to check if the path is rooted, i.e. has a first slash
+ //
+ if (((_flags & Flags.AuthorityFound) != 0) && ((syntaxFlags & UriSyntaxFlags.PathIsRooted) != 0)
+ && (_info.Offset.Path == length || (str[_info.Offset.Path] != '/' && str[_info.Offset.Path] != '\\')))
+ {
+ cF |= Flags.FirstSlashAbsent;
+ }
+ }
+ // Check the need for compression or backslashes conversion
+ // we included IsDosPath since it may come with other than FILE uri, for ex. scheme://C:\path
+ // (This is very unfortunate that the original design has included that feature)
+ bool nonCanonical = false;
+ if (IsDosPath || (((_flags & Flags.AuthorityFound) != 0) &&
+ (((syntaxFlags & (UriSyntaxFlags.CompressPath | UriSyntaxFlags.ConvertPathSlashes)) != 0) ||
+ _syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes))))
+ {
+ if (((result & Check.DotSlashEscaped) != 0) && _syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes))
+ {
+ cF |= (Flags.E_PathNotCanonical | Flags.PathNotCanonical);
+ nonCanonical = true;
+ }
+
+ if (((syntaxFlags & (UriSyntaxFlags.ConvertPathSlashes)) != 0) && (result & Check.BackslashInPath) != 0)
+ {
+ cF |= (Flags.E_PathNotCanonical | Flags.PathNotCanonical);
+ nonCanonical = true;
+ }
+
+ if (((syntaxFlags & (UriSyntaxFlags.CompressPath)) != 0) && ((cF & Flags.E_PathNotCanonical) != 0 ||
+ (result & Check.DotSlashAttn) != 0))
+ {
+ cF |= Flags.ShouldBeCompressed;
+ }
+
+ if ((result & Check.BackslashInPath) != 0)
+ cF |= Flags.BackslashInPath;
+ }
+ else if ((result & Check.BackslashInPath) != 0)
+ {
+ // for a "generic" path '\' should be escaped
+ cF |= Flags.E_PathNotCanonical;
+ nonCanonical = true;
+ }
+
+ if ((result & Check.DisplayCanonical) == 0)
+ {
+ // For implicit file the user string is usually in perfect display format,
+ // Hence, ignoring complains from CheckCanonical()
+ // V1 compat. In fact we should simply ignore dontEscape parameter for Implicit file.
+ // Currently we don't.
+ if (((_flags & Flags.ImplicitFile) == 0) || ((_flags & Flags.UserEscaped) != 0) ||
+ (result & Check.ReservedFound) != 0)
+ {
+ //means it's found as escaped or has unescaped Reserved Characters
+ cF |= Flags.PathNotCanonical;
+ nonCanonical = true;
+ }
+ }
+
+ if (((_flags & Flags.ImplicitFile) != 0) && (result & (Check.ReservedFound | Check.EscapedCanonical)) != 0)
+ {
+ // need to escape reserved chars or re-escape '%' if an "escaped sequence" was found
+ result &= ~Check.EscapedCanonical;
+ }
+
+ if ((result & Check.EscapedCanonical) == 0)
+ {
+ //means it's found as not completely escaped
+ cF |= Flags.E_PathNotCanonical;
+ }
+
+ if (_iriParsing && !nonCanonical & ((result & (Check.DisplayCanonical | Check.EscapedCanonical
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii)))
+ {
+ cF |= Flags.PathIriCanonical;
+ }
+
+ //
+ //Now we've got to parse the Query if any. Note that Query requires the presence of '?'
+ //
+ if (buildIriStringFromPath)
+ {
+ ushort offset = origIdx;
+
+ if (origIdx < _originalUnicodeString.Length && _originalUnicodeString[origIdx] == '?')
+ {
+ ++origIdx; // This is to exclude first '?' character from checking
+ FindEndOfComponent(_originalUnicodeString, ref origIdx, (ushort)_originalUnicodeString.Length, ((syntaxFlags & (UriSyntaxFlags.MayHaveFragment)) != 0) ? '#' : c_EOL);
+
+ // Correctly escape unescape
+ string escapedPath = EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Query);
+
+ // Normalize path
+ try
+ {
+ _string += escapedPath;
+ }
+ catch (ArgumentException)
+ {
+ UriFormatException e = GetException(ParsingError.BadFormat);
+ throw e;
+ }
+
+ length = (ushort)_string.Length;
+ }
+ }
+
+ _info.Offset.Query = idx;
+
+ fixed (char* str = _string)
+ {
+ if (idx < length && str[idx] == '?')
+ {
+ ++idx; // This is to exclude first '?' character from checking
+ result = CheckCanonical(str, ref idx, length, ((syntaxFlags & (UriSyntaxFlags.MayHaveFragment)) != 0)
+ ? '#' : c_EOL);
+ if ((result & Check.DisplayCanonical) == 0)
+ {
+ cF |= Flags.QueryNotCanonical;
+ }
+
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
+ {
+ cF |= Flags.E_QueryNotCanonical;
+ }
+
+ if (_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii)))
+ {
+ cF |= Flags.QueryIriCanonical;
+ }
+ }
+ }
+ //
+ //Now we've got to parse the Fragment if any. Note that Fragment requires the presense of '#'
+ //
+ if (buildIriStringFromPath)
+ {
+ ushort offset = origIdx;
+
+ if (origIdx < _originalUnicodeString.Length && _originalUnicodeString[origIdx] == '#')
+ {
+ ++origIdx; // This is to exclude first '#' character from checking
+ FindEndOfComponent(_originalUnicodeString, ref origIdx, (ushort)_originalUnicodeString.Length, c_EOL);
+
+ // Correctly escape unescape
+ string escapedPath = EscapeUnescapeIri(_originalUnicodeString, offset, origIdx, UriComponents.Fragment);
+
+ // Normalize path
+ try
+ {
+ _string += escapedPath;
+ }
+ catch (ArgumentException)
+ {
+ UriFormatException e = GetException(ParsingError.BadFormat);
+ throw e;
+ }
+
+ length = (ushort)_string.Length;
+ }
+ }
+
+ _info.Offset.Fragment = idx;
+
+ fixed (char* str = _string)
+ {
+ if (idx < length && str[idx] == '#')
+ {
+ ++idx; // This is to exclude first '#' character from checking
+ //We don't using c_DummyChar since want to allow '?' and '#' as unescaped
+ result = CheckCanonical(str, ref idx, length, c_EOL);
+ if ((result & Check.DisplayCanonical) == 0)
+ {
+ cF |= Flags.FragmentNotCanonical;
+ }
+
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
+ {
+ cF |= Flags.E_FragmentNotCanonical;
+ }
+
+ if (_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii)))
+ {
+ cF |= Flags.FragmentIriCanonical;
+ }
+ }
+ }
+ _info.Offset.End = idx;
+ Done:
+
+ cF |= Flags.AllUriInfoSet;
+ lock (_info)
+ {
+ _flags |= cF;
+ }
+ _flags |= Flags.RestUnicodeNormalized;
+ }
+
+ //
+ // verifies the syntax of the scheme part
+ // Checks on implicit File: scheme due to simple Dos/Unc path passed
+ // returns the start of the next component position
+ // throws UriFormatException if invalid scheme
+ //
+ unsafe static private ushort ParseSchemeCheckImplicitFile(char* uriString, ushort length,
+ ref ParsingError err, ref Flags flags, ref UriParser syntax)
+ {
+ ushort idx = 0;
+
+ //skip whitespaces
+ while (idx < length && IsLWS(uriString[idx]))
+ {
+ ++idx;
+ }
+
+ // sets the recognizer for well known registered schemes
+ // file, ftp, http, https, uuid, etc
+ // Note that we don't support one-letter schemes that will be put into a DOS path bucket
+
+ // TODO: change this to be more effective for relative Uris
+ ushort end = idx;
+ while (end < length && uriString[end] != ':')
+ {
+ ++end;
+ }
+
+ // NB: On 64-bits we will use less optimized code from CheckSchemeSyntax()
+ //
+ if (IntPtr.Size == 4)
+ {
+ // long = 4chars: The minimal size of a known scheme is 2 + ':'
+ if (end != length && end >= idx + 2 &&
+ CheckKnownSchemes((long*)(uriString + idx), (ushort)(end - idx), ref syntax))
+ {
+ return (ushort)(end + 1);
+ }
+ }
+
+ //NB: A string must have at least 3 characters and at least 1 before ':'
+ if (idx + 2 >= length || end == idx)
+ {
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+
+ //Check for supported special cases like a DOS file path OR a UNC share path
+ //NB: A string may not have ':' if this is a UNC path
+ {
+ char c;
+ if ((c = uriString[idx + 1]) == ':' || c == '|')
+ {
+ //DOS-like path?
+ if (IsAsciiLetter(uriString[idx]))
+ {
+ if ((c = uriString[idx + 2]) == '\\' || c == '/')
+ {
+ flags |= (Flags.DosPath | Flags.ImplicitFile | Flags.AuthorityFound);
+ syntax = UriParser.FileUri;
+ return idx;
+ }
+ err = ParsingError.MustRootedPath;
+ return 0;
+ }
+ if (c == ':')
+ err = ParsingError.BadScheme;
+ else
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+ else if ((c = uriString[idx]) == '/' || c == '\\')
+ {
+ //UNC share ?
+ if ((c = uriString[idx + 1]) == '\\' || c == '/')
+ {
+ flags |= (Flags.UncPath | Flags.ImplicitFile | Flags.AuthorityFound);
+ syntax = UriParser.FileUri;
+ idx += 2;
+ // V1.1 compat this will simply eat any slashes prepended to a UNC path
+ while (idx < length && ((c = uriString[idx]) == '/' || c == '\\'))
+ ++idx;
+
+ return idx;
+ }
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+ }
+
+ if (end == length)
+ {
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+
+ // Here could be a possibly valid, and not well-known scheme
+ // Finds the scheme delimiter
+ // we don;t work with the schemes names > c_MaxUriSchemeName (should be ~1k)
+ if ((end - idx) > c_MaxUriSchemeName)
+ {
+ err = ParsingError.SchemeLimit;
+ return 0;
+ }
+
+ //Check the syntax, canonicalize and avoid a GC call
+ char* schemePtr = stackalloc char[end - idx];
+ for (length = 0; idx < end; ++idx)
+ {
+ schemePtr[length++] = uriString[idx];
+ }
+ err = CheckSchemeSyntax(schemePtr, length, ref syntax);
+ if (err != ParsingError.None)
+ {
+ return 0;
+ }
+ return (ushort)(end + 1);
+ }
+
+ //
+ // Quickly parses well known schemes.
+ // nChars does not include the last ':'. Assuming there is one at the end of passed buffer
+ unsafe static private bool CheckKnownSchemes(long* lptr, ushort nChars, ref UriParser syntax)
+ {
+ //NOTE beware of too short input buffers!
+
+ const long _HTTP_Mask0 = 'h' | ('t' << 16) | ((long)'t' << 32) | ((long)'p' << 48);
+ const char _HTTPS_Mask1 = 's';
+ const int _WS_Mask = 'w' | ('s' << 16);
+ const long _WSS_Mask = 'w' | ('s' << 16) | ((long)'s' << 32) | ((long)':' << 48);
+ const long _FTP_Mask = 'f' | ('t' << 16) | ((long)'p' << 32) | ((long)':' << 48);
+ const long _FILE_Mask0 = 'f' | ('i' << 16) | ((long)'l' << 32) | ((long)'e' << 48);
+ const long _GOPHER_Mask0 = 'g' | ('o' << 16) | ((long)'p' << 32) | ((long)'h' << 48);
+ const int _GOPHER_Mask1 = 'e' | ('r' << 16);
+ const long _MAILTO_Mask0 = 'm' | ('a' << 16) | ((long)'i' << 32) | ((long)'l' << 48);
+ const int _MAILTO_Mask1 = 't' | ('o' << 16);
+ const long _NEWS_Mask0 = 'n' | ('e' << 16) | ((long)'w' << 32) | ((long)'s' << 48);
+ const long _NNTP_Mask0 = 'n' | ('n' << 16) | ((long)'t' << 32) | ((long)'p' << 48);
+ const long _UUID_Mask0 = 'u' | ('u' << 16) | ((long)'i' << 32) | ((long)'d' << 48);
+
+ const long _TELNET_Mask0 = 't' | ('e' << 16) | ((long)'l' << 32) | ((long)'n' << 48);
+ const int _TELNET_Mask1 = 'e' | ('t' << 16);
+
+ const long _NETXXX_Mask0 = 'n' | ('e' << 16) | ((long)'t' << 32) | ((long)'.' << 48);
+ const long _NETTCP_Mask1 = 't' | ('c' << 16) | ((long)'p' << 32) | ((long)':' << 48);
+ const long _NETPIPE_Mask1 = 'p' | ('i' << 16) | ((long)'p' << 32) | ((long)'e' << 48);
+
+ const long _LDAP_Mask0 = 'l' | ('d' << 16) | ((long)'a' << 32) | ((long)'p' << 48);
+
+
+ const long _LOWERCASE_Mask = 0x0020002000200020L;
+ const int _INT_LOWERCASE_Mask = 0x00200020;
+
+ if (nChars == 2)
+ {
+ // This is the only known scheme of length 2
+ if ((((int)*lptr) | _INT_LOWERCASE_Mask) == _WS_Mask)
+ {
+ syntax = UriParser.WsUri;
+ return true;
+ }
+ return false;
+ }
+
+ //Map to a known scheme if possible
+ //upgrade 4 letters to ASCII lower case, keep a false case to stay false
+ switch (*lptr | _LOWERCASE_Mask)
+ {
+ case _HTTP_Mask0:
+ if (nChars == 4)
+ {
+ syntax = UriParser.HttpUri;
+ return true;
+ }
+ if (nChars == 5 && ((*(char*)(lptr + 1)) | 0x20) == _HTTPS_Mask1)
+ {
+ syntax = UriParser.HttpsUri;
+ return true;
+ }
+ break;
+ case _WSS_Mask:
+ if (nChars == 3)
+ {
+ syntax = UriParser.WssUri;
+ return true;
+ }
+ break;
+ case _FILE_Mask0:
+ if (nChars == 4)
+ {
+ syntax = UriParser.FileUri;
+ return true;
+ }
+ break;
+ case _FTP_Mask:
+ if (nChars == 3)
+ {
+ syntax = UriParser.FtpUri;
+ return true;
+ }
+ break;
+
+ case _NEWS_Mask0:
+ if (nChars == 4)
+ {
+ syntax = UriParser.NewsUri;
+ return true;
+ }
+ break;
+
+ case _NNTP_Mask0:
+ if (nChars == 4)
+ {
+ syntax = UriParser.NntpUri;
+ return true;
+ }
+ break;
+
+ case _UUID_Mask0:
+ if (nChars == 4)
+ {
+ syntax = UriParser.UuidUri;
+ return true;
+ }
+ break;
+
+ case _GOPHER_Mask0:
+ if (nChars == 6 && (*(int*)(lptr + 1) | _INT_LOWERCASE_Mask) == _GOPHER_Mask1)
+ {
+ syntax = UriParser.GopherUri;
+ return true;
+ }
+ break;
+ case _MAILTO_Mask0:
+ if (nChars == 6 && (*(int*)(lptr + 1) | _INT_LOWERCASE_Mask) == _MAILTO_Mask1)
+ {
+ syntax = UriParser.MailToUri;
+ return true;
+ }
+ break;
+
+ case _TELNET_Mask0:
+ if (nChars == 6 && (*(int*)(lptr + 1) | _INT_LOWERCASE_Mask) == _TELNET_Mask1)
+ {
+ syntax = UriParser.TelnetUri;
+ return true;
+ }
+ break;
+
+ case _NETXXX_Mask0:
+ if (nChars == 8 && (*(lptr + 1) | _LOWERCASE_Mask) == _NETPIPE_Mask1)
+ {
+ syntax = UriParser.NetPipeUri;
+ return true;
+ }
+ else if (nChars == 7 && (*(lptr + 1) | _LOWERCASE_Mask) == _NETTCP_Mask1)
+ {
+ syntax = UriParser.NetTcpUri;
+ return true;
+ }
+ break;
+
+ case _LDAP_Mask0:
+ if (nChars == 4)
+ {
+ syntax = UriParser.LdapUri;
+ return true;
+ }
+ break;
+ default: break;
+ }
+ return false;
+ }
+
+ //
+ // This will check whether a scheme string follows the rules
+ //
+ unsafe static private ParsingError CheckSchemeSyntax(char* ptr, ushort length, ref UriParser syntax)
+ {
+ //First character must be an alpha
+ {
+ char c = *ptr;
+ if (c >= 'a' && c <= 'z')
+ {
+ ;
+ }
+ else if (c >= 'A' && c <= 'Z')
+ {
+ *ptr = (char)(c | 0x20); //make it lowercase
+ }
+ else
+ {
+ return ParsingError.BadScheme;
+ }
+ }
+
+ for (ushort i = 1; i < length; ++i)
+ {
+ char c = ptr[i];
+ if (c >= 'a' && c <= 'z')
+ {
+ ;
+ }
+ else if (c >= 'A' && c <= 'Z')
+ {
+ ptr[i] = (char)(c | 0x20); //make it lowercase
+ }
+ else if (c >= '0' && c <= '9')
+ {
+ ;
+ }
+ else if (c == '+' || c == '-' || c == '.')
+ {
+ ;
+ }
+ else
+ {
+ return ParsingError.BadScheme;
+ }
+ }
+ // A not well-known scheme, needs string creation
+ // Note it is already in the lower case as required.
+ string str = new string(ptr, 0, length);
+ syntax = UriParser.FindOrFetchAsUnknownV1Syntax(str);
+ return ParsingError.None;
+ }
+
+ //
+ // Checks the syntax of an authority component. It may also get a userInfo if present
+ // Returns an error if no/mailformed authority found
+ // Does not NOT touch m_Info
+ // Returns position of the Path component
+ //
+ // Must be called in the ctor only
+ private unsafe ushort CheckAuthorityHelper(char* pString, ushort idx, ushort length,
+ ref ParsingError err, ref Flags flags, UriParser syntax, ref string newHost)
+ {
+ int end = length;
+ char ch;
+ int startInput = idx;
+ ushort start = idx;
+ newHost = null;
+ bool justNormalized = false;
+ bool iriParsing = (s_IriParsing && IriParsingStatic(syntax)); // perf
+ bool hasUnicode = ((flags & Flags.HasUnicode) != 0); // perf
+ bool hostNotUnicodeNormalized = ((flags & Flags.HostUnicodeNormalized) == 0); // perf
+ UriSyntaxFlags syntaxFlags = syntax.Flags;
+
+ // need to build new Iri'zed string
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ {
+ newHost = _originalUnicodeString.Substring(0, startInput);
+ }
+
+ //Special case is an empty authority
+ if (idx == length || ((ch = pString[idx]) == '/' || (ch == '\\' && StaticIsFile(syntax)) || ch == '#' || ch == '?'))
+ {
+ if (syntax.InFact(UriSyntaxFlags.AllowEmptyHost))
+ {
+ flags &= ~Flags.UncPath; //UNC cannot have an empty hostname
+ if (StaticInFact(flags, Flags.ImplicitFile))
+ err = ParsingError.BadHostName;
+ else
+ flags |= Flags.BasicHostType;
+ }
+ else
+ err = ParsingError.BadHostName;
+
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ {
+ flags |= Flags.HostUnicodeNormalized;// no host
+ }
+
+ return idx;
+ }
+
+ string userInfoString = null;
+ // Attempt to parse user info first
+
+ if ((syntaxFlags & UriSyntaxFlags.MayHaveUserInfo) != 0)
+ {
+ for (; start < end; ++start)
+ {
+ if (start == end - 1 || pString[start] == '?' || pString[start] == '#' || pString[start] == '\\' ||
+ pString[start] == '/')
+ {
+ start = idx;
+ break;
+ }
+ else if (pString[start] == '@')
+ {
+ flags |= Flags.HasUserInfo;
+
+ // Iri'ze userinfo
+ if (iriParsing || (s_IdnScope != UriIdnScope.None))
+ {
+ if (iriParsing && hasUnicode && hostNotUnicodeNormalized)
+ {
+ // Normalize user info
+ userInfoString = IriHelper.EscapeUnescapeIri(pString, startInput, start + 1, UriComponents.UserInfo);
+ newHost += userInfoString;
+ }
+ else
+ {
+ userInfoString = new string(pString, startInput, start - startInput + 1);
+ }
+ }
+ ++start;
+ ch = pString[start];
+ break;
+ }
+ }
+ }
+
+ // DNS name only optimization
+ // Fo an overriden parsing the optimization is suppressed since hostname can be changed to anything
+ bool dnsNotCanonical = ((syntaxFlags & UriSyntaxFlags.SimpleUserSyntax) == 0);
+
+ if (ch == '[' && syntax.InFact(UriSyntaxFlags.AllowIPv6Host)
+ && IPv6AddressHelper.IsValid(pString, (int)start + 1, ref end))
+ {
+ flags |= Flags.IPv6HostType;
+
+ _iriParsing = (s_IriParsing && IriParsingStatic(syntax));
+
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ {
+ newHost += new string(pString, start, end - start);
+ flags |= Flags.HostUnicodeNormalized;
+ justNormalized = true;
+ }
+ }
+ else if (ch <= '9' && ch >= '0' && syntax.InFact(UriSyntaxFlags.AllowIPv4Host) &&
+ IPv4AddressHelper.IsValid(pString, (int)start, ref end, false, StaticNotAny(flags, Flags.ImplicitFile), syntax.InFact(UriSyntaxFlags.V1_UnknownUri)))
+ {
+ flags |= Flags.IPv4HostType;
+
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ {
+ newHost += new string(pString, start, end - start);
+ flags |= Flags.HostUnicodeNormalized;
+ justNormalized = true;
+ }
+ }
+ else if (((syntaxFlags & UriSyntaxFlags.AllowDnsHost) != 0) && !iriParsing &&
+ DomainNameHelper.IsValid(pString, start, ref end, ref dnsNotCanonical, StaticNotAny(flags, Flags.ImplicitFile)))
+ {
+ // comes here if there are only ascii chars in host with original parsing and no Iri
+
+ flags |= Flags.DnsHostType;
+ if (!dnsNotCanonical)
+ {
+ flags |= Flags.CanonicalDnsHost;
+ }
+
+ if ((s_IdnScope != UriIdnScope.None))
+ {
+ // check if intranet
+ //
+ if ((s_IdnScope == UriIdnScope.AllExceptIntranet) && IsIntranet(new string(pString, 0, end)))
+ {
+ flags |= Flags.IntranetUri;
+ }
+ if (AllowIdnStatic(syntax, flags))
+ {
+ bool allAscii = true;
+ bool atLeastOneIdn = false;
+
+ string idnValue = DomainNameHelper.UnicodeEquivalent(pString, start, end, ref allAscii, ref atLeastOneIdn);
+
+ // did we find at least one valid idn
+ if (atLeastOneIdn)
+ {
+ // need to switch string here since we didnt know before hand there there was an idn host
+ if (StaticNotAny(flags, Flags.HasUnicode))
+ _originalUnicodeString = _string; // lazily switching strings
+ flags |= Flags.IdnHost;
+
+ // need to build string for this special scenario
+ newHost = _originalUnicodeString.Substring(0, startInput) + userInfoString + idnValue;
+ flags |= Flags.CanonicalDnsHost;
+ _dnsSafeHost = new string(pString, start, end - start);
+ justNormalized = true;
+ }
+ flags |= Flags.HostUnicodeNormalized;
+ }
+ }
+ }
+ else if (((syntaxFlags & UriSyntaxFlags.AllowDnsHost) != 0)
+ && ((syntax.InFact(UriSyntaxFlags.AllowIriParsing) && hostNotUnicodeNormalized)
+ || syntax.InFact(UriSyntaxFlags.AllowIdn))
+ && DomainNameHelper.IsValidByIri(pString, start, ref end, ref dnsNotCanonical,
+ StaticNotAny(flags, Flags.ImplicitFile)))
+ {
+ CheckAuthorityHelperHandleDnsIri(pString, start, end, startInput, iriParsing, hasUnicode, syntax,
+ userInfoString, ref flags, ref justNormalized, ref newHost, ref err);
+ }
+ else if ((syntaxFlags & UriSyntaxFlags.AllowUncHost) != 0)
+ {
+ //
+ // This must remain as the last check befor BasicHost type
+ //
+ if (UncNameHelper.IsValid(pString, start, ref end, StaticNotAny(flags, Flags.ImplicitFile)))
+ {
+ if (end - start <= UncNameHelper.MaximumInternetNameLength)
+ flags |= Flags.UncHostType;
+ }
+ }
+
+ // The deal here is that we won't allow '\' host terminator except for the File scheme
+ // If we see '\' we try to make it a part of of a Basic host
+ if (end < length && pString[end] == '\\' && (flags & Flags.HostTypeMask) != Flags.HostNotParsed
+ && !StaticIsFile(syntax))
+ {
+ if (syntax.InFact(UriSyntaxFlags.V1_UnknownUri))
+ {
+ err = ParsingError.BadHostName;
+ flags |= Flags.UnknownHostType;
+ return (ushort)end;
+ }
+ flags &= ~Flags.HostTypeMask;
+ }
+ // Here we have checked the syntax up to the end of host
+ // The only thing that can cause an exception is the port value
+ // Spend some (duplicated) cycles on that.
+ else if (end < length && pString[end] == ':')
+ {
+ if (syntax.InFact(UriSyntaxFlags.MayHavePort))
+ {
+ int port = 0;
+ int startPort = end;
+ for (idx = (ushort)(end + 1); idx < length; ++idx)
+ {
+ ushort val = (ushort)((ushort)pString[idx] - (ushort)'0');
+ if ((val >= 0) && (val <= 9))
+ {
+ if ((port = (port * 10 + val)) > 0xFFFF)
+ break;
+ }
+ else if (val == unchecked((ushort)('/' - '0')) || val == (ushort)('?' - '0')
+ || val == unchecked((ushort)('#' - '0')))
+ {
+ break;
+ }
+ else
+ {
+ // The second check is to keep compatibility with V1 until the UriParser is registered
+ if (syntax.InFact(UriSyntaxFlags.AllowAnyOtherHost)
+ && syntax.NotAny(UriSyntaxFlags.V1_UnknownUri))
+ {
+ flags &= ~Flags.HostTypeMask;
+ break;
+ }
+ else
+ {
+ err = ParsingError.BadPort;
+ return idx;
+ }
+ }
+ }
+ // check on 0-ffff range
+ if (port > 0xFFFF)
+ {
+ if (syntax.InFact(UriSyntaxFlags.AllowAnyOtherHost))
+ {
+ flags &= ~Flags.HostTypeMask;
+ }
+ else
+ {
+ err = ParsingError.BadPort;
+ return idx;
+ }
+ }
+
+ if (iriParsing && hasUnicode && justNormalized)
+ {
+ newHost += new string(pString, startPort, idx - startPort);
+ }
+ }
+ else
+ {
+ flags &= ~Flags.HostTypeMask;
+ }
+ }
+
+ // check on whether nothing has worked out
+ if ((flags & Flags.HostTypeMask) == Flags.HostNotParsed)
+ {
+ //No user info for a Basic hostname
+ flags &= ~Flags.HasUserInfo;
+ // Some schemes do not allow HostType = Basic (plus V1 almost never understands this issue)
+ //
+ if (syntax.InFact(UriSyntaxFlags.AllowAnyOtherHost))
+ {
+ flags |= Flags.BasicHostType;
+ for (end = idx; end < length; ++end)
+ {
+ if (pString[end] == '/' || (pString[end] == '?' || pString[end] == '#'))
+ {
+ break;
+ }
+ }
+ CheckAuthorityHelperHandleAnyHostIri(pString, startInput, end, iriParsing, hasUnicode, syntax,
+ ref flags, ref newHost, ref err);
+ }
+ else
+ {
+ //
+ // ATTN V1 compat: V1 supports hostnames like ".." and ".", and so we do but only for unknown schemes.
+ //
+ if (syntax.InFact(UriSyntaxFlags.V1_UnknownUri))
+ {
+ // Can assert here that the host is not empty so we will set dotFound
+ // at least once or fail before exiting the loop
+ bool dotFound = false;
+ int startOtherHost = idx;
+ for (end = idx; end < length; ++end)
+ {
+ if (dotFound && (pString[end] == '/' || pString[end] == '?' || pString[end] == '#'))
+ break;
+ else if (end < (idx + 2) && pString[end] == '.')
+ {
+ // allow one or two dots
+ dotFound = true;
+ }
+ else
+ {
+ //failure
+ err = ParsingError.BadHostName;
+ flags |= Flags.UnknownHostType;
+ return idx;
+ }
+ }
+ //success
+ flags |= Flags.BasicHostType;
+
+ if (iriParsing && hasUnicode
+ && StaticNotAny(flags, Flags.HostUnicodeNormalized))
+ {
+ // Normalize any other host
+ string user = new string(pString, startOtherHost, startOtherHost - end);
+ try
+ {
+ newHost += user.Normalize(NormalizationForm.FormC);
+ }
+ catch (ArgumentException)
+ {
+ err = ParsingError.BadFormat;
+ return idx;
+ }
+
+ flags |= Flags.HostUnicodeNormalized;
+ }
+ }
+ else if (syntax.InFact(UriSyntaxFlags.MustHaveAuthority) ||
+ (syntax.InFact(UriSyntaxFlags.MailToLikeUri)))
+ {
+ err = ParsingError.BadHostName;
+ flags |= Flags.UnknownHostType;
+ return idx;
+ }
+ }
+ }
+ return (ushort)end;
+ }
+
+ private unsafe void CheckAuthorityHelperHandleDnsIri(char* pString, ushort start, int end, int startInput,
+ bool iriParsing, bool hasUnicode, UriParser syntax, string userInfoString, ref Flags flags,
+ ref bool justNormalized, ref string newHost, ref ParsingError err)
+ {
+ // comes here only if host has unicode chars and iri is on or idn is allowed
+
+ flags |= Flags.DnsHostType;
+
+ // check if intranet
+ //
+ if ((s_IdnScope == UriIdnScope.AllExceptIntranet) && IsIntranet(new string(pString, 0, end)))
+ {
+ flags |= Flags.IntranetUri;
+ }
+
+ if (AllowIdnStatic(syntax, flags))
+ {
+ bool allAscii = true;
+ bool atLeastOneIdn = false;
+
+ string idnValue = DomainNameHelper.IdnEquivalent(pString, start, end, ref allAscii, ref atLeastOneIdn);
+ string UniEquvlt = DomainNameHelper.UnicodeEquivalent(idnValue, pString, start, end);
+
+ if (!allAscii)
+ flags |= Flags.UnicodeHost; // we have a unicode host
+
+ if (atLeastOneIdn)
+ flags |= Flags.IdnHost; // we have at least one valid idn label
+
+ if (allAscii && atLeastOneIdn && StaticNotAny(flags, Flags.HasUnicode))
+ {
+ // original string location changed lazily
+ _originalUnicodeString = _string;
+ newHost = _originalUnicodeString.Substring(0, startInput) +
+ (StaticInFact(flags, Flags.HasUserInfo) ? userInfoString : null);
+ justNormalized = true;
+ }
+ else if (!iriParsing && (StaticInFact(flags, Flags.UnicodeHost) || StaticInFact(flags, Flags.IdnHost)))
+ {
+ // original string location changed lazily
+ _originalUnicodeString = _string;
+ newHost = _originalUnicodeString.Substring(0, startInput) +
+ (StaticInFact(flags, Flags.HasUserInfo) ? userInfoString : null);
+ justNormalized = true;
+ }
+
+ if (!(allAscii && !atLeastOneIdn))
+ {
+ _dnsSafeHost = idnValue;
+ newHost += UniEquvlt;
+ justNormalized = true;
+ }
+ else if (allAscii && !atLeastOneIdn && iriParsing && hasUnicode)
+ {
+ newHost += UniEquvlt;
+ justNormalized = true;
+ }
+ }
+ else
+ {
+ if (hasUnicode)
+ {
+ string temp = StripBidiControlCharacter(pString, start, end - start);
+ try
+ {
+ newHost += ((temp != null) ? temp.Normalize(NormalizationForm.FormC) : null);
+ }
+ catch (ArgumentException)
+ {
+ err = ParsingError.BadHostName;
+ }
+ justNormalized = true;
+ }
+ }
+ flags |= Flags.HostUnicodeNormalized;
+ }
+
+ private unsafe void CheckAuthorityHelperHandleAnyHostIri(char* pString, int startInput, int end,
+ bool iriParsing, bool hasUnicode, UriParser syntax,
+ ref Flags flags, ref string newHost, ref ParsingError err)
+ {
+ if (StaticNotAny(flags, Flags.HostUnicodeNormalized) && (AllowIdnStatic(syntax, flags) ||
+ (iriParsing && hasUnicode)))
+ {
+ // Normalize any other host or do idn
+ string user = new string(pString, startInput, end - startInput);
+
+ if (AllowIdnStatic(syntax, flags))
+ {
+ bool allAscii = true;
+ bool atLeastOneIdn = false;
+
+ string UniEquvlt = DomainNameHelper.UnicodeEquivalent(pString, startInput, end, ref allAscii,
+ ref atLeastOneIdn);
+
+ if (((allAscii && atLeastOneIdn) || !allAscii) && !(iriParsing && hasUnicode))
+ {
+ // original string location changed lazily
+ _originalUnicodeString = _string;
+ newHost = _originalUnicodeString.Substring(0, startInput);
+ flags |= Flags.HasUnicode;
+ }
+ if (atLeastOneIdn || !allAscii)
+ {
+ newHost += UniEquvlt;
+ string bidiStrippedHost = null;
+ _dnsSafeHost = DomainNameHelper.IdnEquivalent(pString, startInput, end, ref allAscii,
+ ref bidiStrippedHost);
+ if (atLeastOneIdn)
+ flags |= Flags.IdnHost;
+ if (!allAscii)
+ flags |= Flags.UnicodeHost;
+ }
+ else if (iriParsing && hasUnicode)
+ {
+ newHost += user;
+ }
+ }
+ else
+ {
+ try
+ {
+ newHost += user.Normalize(NormalizationForm.FormC);
+ }
+ catch (ArgumentException)
+ {
+ err = ParsingError.BadHostName;
+ }
+ }
+
+ flags |= Flags.HostUnicodeNormalized;
+ }
+ }
+
+ //
+ // The method checks whether a string needs transformation before going to display or wire
+ //
+ // Parameters:
+ // - escaped true = treat all valid escape sequences as escaped sequences, false = escape all %
+ // - delim a character signalling the termination of the component being checked
+ //
+ // When delim=='?', then '#' character is also considered as delimiter additionally to passed '?'.
+ //
+ // The method pays attention to the dots and slashes so to signal potential Path compression action needed.
+ // Even that is not required for other components, the cycles are still spent (little inefficiency)
+ //
+
+ internal const char c_DummyChar = (char)0xFFFF; //An Invalid Unicode character used as a dummy char passed into the parameter
+ internal const char c_EOL = (char)0xFFFE; //An Invalid Unicode character used by CheckCanonical as "no delimiter condition"
+ [Flags]
+ private enum Check
+ {
+ None = 0x0,
+ EscapedCanonical = 0x1,
+ DisplayCanonical = 0x2,
+ DotSlashAttn = 0x4,
+ DotSlashEscaped = 0x80,
+ BackslashInPath = 0x10,
+ ReservedFound = 0x20,
+ NotIriCanonical = 0x40,
+ FoundNonAscii = 0x8
+ }
+
+ //
+ // Finds the end of component
+ //
+
+ private unsafe void FindEndOfComponent(string input, ref ushort idx, ushort end, char delim)
+ {
+ fixed (char* str = input)
+ {
+ FindEndOfComponent(str, ref idx, end, delim);
+ }
+ }
+ private unsafe void FindEndOfComponent(char* str, ref ushort idx, ushort end, char delim)
+ {
+ char c = c_DummyChar;
+ ushort i = idx;
+ for (; i < end; ++i)
+ {
+ c = str[i];
+ if (c == delim)
+ {
+ break;
+ }
+ else if (delim == '?' && c == '#' && (_syntax != null && _syntax.InFact(UriSyntaxFlags.MayHaveFragment)))
+ {
+ // this is a special case when deciding on Query/Fragment
+ break;
+ }
+ }
+ idx = i;
+ }
+
+ //
+ // Used by ParseRemaining as well by InternalIsWellFormedOriginalString
+ //
+ private unsafe Check CheckCanonical(char* str, ref ushort idx, ushort end, char delim)
+ {
+ Check res = Check.None;
+ bool needsEscaping = false;
+ bool foundEscaping = false;
+
+ char c = c_DummyChar;
+ ushort i = idx;
+ for (; i < end; ++i)
+ {
+ c = str[i];
+ // Control chars usually should be escaped in any case
+ if (c <= '\x1F' || (c >= '\x7F' && c <= '\x9F'))
+ {
+ needsEscaping = true;
+ foundEscaping = true;
+ res |= Check.ReservedFound;
+ }
+ else if (c > 'z' && c != '~')
+ {
+ if (_iriParsing)
+ {
+ bool valid = false;
+ res |= Check.FoundNonAscii;
+
+ if (char.IsHighSurrogate(c))
+ {
+ if ((i + 1) < end)
+ {
+ bool surrPair = false;
+ valid = IriHelper.CheckIriUnicodeRange(c, str[i + 1], ref surrPair, true);
+ }
+ }
+ else
+ {
+ valid = IriHelper.CheckIriUnicodeRange(c, true);
+ }
+ if (!valid) res |= Check.NotIriCanonical;
+ }
+
+ if (!needsEscaping) needsEscaping = true;
+ }
+ else if (c == delim)
+ {
+ break;
+ }
+ else if (delim == '?' && c == '#' && (_syntax != null && _syntax.InFact(UriSyntaxFlags.MayHaveFragment)))
+ {
+ // this is a special case when deciding on Query/Fragment
+ break;
+ }
+ else if (c == '?')
+ {
+ if (IsImplicitFile || (_syntax != null && !_syntax.InFact(UriSyntaxFlags.MayHaveQuery)
+ && delim != c_EOL))
+ {
+ // If found as reserved this char is not suitable for safe unescaped display
+ // Will need to escape it when both escaping and unescaping the string
+ res |= Check.ReservedFound;
+ foundEscaping = true;
+ needsEscaping = true;
+ }
+ }
+ else if (c == '#')
+ {
+ needsEscaping = true;
+ if (IsImplicitFile || (_syntax != null && !_syntax.InFact(UriSyntaxFlags.MayHaveFragment)))
+ {
+ // If found as reserved this char is not suitable for safe unescaped display
+ // Will need to escape it when both escaping and unescaping the string
+ res |= Check.ReservedFound;
+ foundEscaping = true;
+ }
+ }
+ else if (c == '/' || c == '\\')
+ {
+ if ((res & Check.BackslashInPath) == 0 && c == '\\')
+ {
+ res |= Check.BackslashInPath;
+ }
+ if ((res & Check.DotSlashAttn) == 0 && i + 1 != end && (str[i + 1] == '/' || str[i + 1] == '\\'))
+ {
+ res |= Check.DotSlashAttn;
+ }
+ }
+ else if (c == '.')
+ {
+ if ((res & Check.DotSlashAttn) == 0 && i + 1 == end || str[i + 1] == '.' || str[i + 1] == '/'
+ || str[i + 1] == '\\' || str[i + 1] == '?' || str[i + 1] == '#')
+ {
+ res |= Check.DotSlashAttn;
+ }
+ }
+ else if (!needsEscaping && ((c <= '"' && c != '!') || (c >= '[' && c <= '^') || c == '>'
+ || c == '<' || c == '`'))
+ {
+ needsEscaping = true;
+ }
+ else if (c == '%')
+ {
+ if (!foundEscaping) foundEscaping = true;
+ //try unescape a byte hex escaping
+ if (i + 2 < end && (c = UriHelper.EscapedAscii(str[i + 1], str[i + 2])) != c_DummyChar)
+ {
+ if (c == '.' || c == '/' || c == '\\')
+ {
+ res |= Check.DotSlashEscaped;
+ }
+ i += 2;
+ continue;
+ }
+ // otherwise we follow to non escaped case
+ if (!needsEscaping)
+ {
+ needsEscaping = true;
+ }
+ }
+ }
+
+ if (foundEscaping)
+ {
+ if (!needsEscaping)
+ {
+ res |= Check.EscapedCanonical;
+ }
+ }
+ else
+ {
+ res |= Check.DisplayCanonical;
+ if (!needsEscaping)
+ {
+ res |= Check.EscapedCanonical;
+ }
+ }
+ idx = i;
+ return res;
+ }
+
+ //
+ // Returns the escaped and canonicalized path string
+ // the passed array must be long enough to hold at least
+ // canonical unescaped path representation (allocated by the caller)
+ //
+ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat formatAs)
+ {
+ if (InFact(Flags.FirstSlashAbsent))
+ dest[pos++] = '/';
+
+ if (_info.Offset.Path == _info.Offset.Query)
+ return dest;
+
+ int end = pos;
+
+ int dosPathIdx = SecuredPathIndex;
+
+ // Note that unescaping and then escapig back is not transitive hence not safe.
+ // We are vulnerable due to the way the UserEscaped flag is processed.
+ // Try to unescape only needed chars.
+ if (formatAs == UriFormat.UriEscaped)
+ {
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ _string.CopyTo(_info.Offset.Path, dest, end, _info.Offset.Query - _info.Offset.Path);
+ end += (_info.Offset.Query - _info.Offset.Path);
+
+ // If the path was found as needed compression and contains escaped characters, unescape only
+ // interesting characters (safe)
+
+ if (_syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes) && InFact(Flags.PathNotCanonical)
+ && !IsImplicitFile)
+ {
+ fixed (char* pdest = dest)
+ {
+ UnescapeOnly(pdest, pos, ref end, '.', '/',
+ _syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) ? '\\' : c_DummyChar);
+ }
+ }
+ }
+ else
+ {
+ //Note: we may produce non escaped Uri characters on the wire
+ if (InFact(Flags.E_PathNotCanonical) && NotAny(Flags.UserEscaped))
+ {
+ string str = _string;
+
+ // Check on not canonical disk designation like C|\, should be rare, rare case
+ if (dosPathIdx != 0 && str[dosPathIdx + _info.Offset.Path - 1] == '|')
+ {
+ str = str.Remove(dosPathIdx + _info.Offset.Path - 1, 1);
+ str = str.Insert(dosPathIdx + _info.Offset.Path - 1, ":");
+ }
+ dest = UriHelper.EscapeString(str, _info.Offset.Path, _info.Offset.Query, dest, ref end, true,
+ '?', '#', IsImplicitFile ? c_DummyChar : '%');
+ }
+ else
+ {
+ _string.CopyTo(_info.Offset.Path, dest, end, _info.Offset.Query - _info.Offset.Path);
+ end += (_info.Offset.Query - _info.Offset.Path);
+ }
+ }
+ }
+ else
+ {
+ _string.CopyTo(_info.Offset.Path, dest, end, _info.Offset.Query - _info.Offset.Path);
+ end += (_info.Offset.Query - _info.Offset.Path);
+
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ // If the path was found as needed compression and contains escaped characters,
+ // unescape only interesting characters (safe)
+
+ if (_syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes) && InFact(Flags.PathNotCanonical)
+ && !IsImplicitFile)
+ {
+ fixed (char* pdest = dest)
+ {
+ UnescapeOnly(pdest, pos, ref end, '.', '/',
+ _syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) ? '\\' : c_DummyChar);
+ }
+ }
+ }
+ }
+
+ // Here we already got output data as copied into dest array
+ // We just may need more processing of that data
+
+ //
+ // if this URI is using 'non-proprietary' disk drive designation, convert to MS-style
+ //
+ // (path is already >= 3 chars if recognized as a DOS-like)
+ //
+ if (dosPathIdx != 0 && dest[dosPathIdx + pos - 1] == '|')
+ dest[dosPathIdx + pos - 1] = ':';
+
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ // It will also convert back slashes if needed
+ dest = Compress(dest, (ushort)(pos + dosPathIdx), ref end, _syntax);
+ if (dest[pos] == '\\')
+ dest[pos] = '/';
+
+ // Escape path if requested and found as not fully escaped
+ if (formatAs == UriFormat.UriEscaped && NotAny(Flags.UserEscaped) && InFact(Flags.E_PathNotCanonical))
+ {
+ //Note: Flags.UserEscaped check is solely based on trusting the user
+ string srcString = new string(dest, pos, end - pos);
+ dest = UriHelper.EscapeString(srcString, 0, end - pos, dest, ref pos, true, '?', '#',
+ IsImplicitFile ? c_DummyChar : '%');
+ end = pos;
+ }
+ }
+ else if (_syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) && InFact(Flags.BackslashInPath))
+ {
+ for (int i = pos; i < end; ++i)
+ if (dest[i] == '\\') dest[i] = '/';
+ }
+
+ if (formatAs != UriFormat.UriEscaped && InFact(Flags.PathNotCanonical))
+ {
+ UnescapeMode mode;
+ if (InFact(Flags.PathNotCanonical))
+ {
+ switch (formatAs)
+ {
+ case V1ToStringUnescape:
+
+ mode = (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape)
+ | UnescapeMode.V1ToStringFlag;
+ if (IsImplicitFile)
+ mode &= ~UnescapeMode.Unescape;
+ break;
+
+ case UriFormat.Unescaped:
+ mode = IsImplicitFile ? UnescapeMode.CopyOnly
+ : UnescapeMode.Unescape | UnescapeMode.UnescapeAll;
+ break;
+
+ default: // UriFormat.SafeUnescaped
+
+ mode = InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape;
+ if (IsImplicitFile)
+ mode &= ~UnescapeMode.Unescape;
+ break;
+ }
+ }
+ else
+ {
+ mode = UnescapeMode.CopyOnly;
+ }
+
+ char[] dest1 = new char[dest.Length];
+ Buffer.BlockCopy(dest, 0, dest1, 0, end << 1);
+ fixed (char* pdest = dest1)
+ {
+ dest = UriHelper.UnescapeString(pdest, pos, end, dest, ref pos, '?', '#', c_DummyChar, mode,
+ _syntax, false);
+ }
+ }
+ else
+ {
+ pos = end;
+ }
+
+ return dest;
+ }
+
+ // works only with ASCII characters, used to partially unescape path before compressing
+ private unsafe static void UnescapeOnly(char* pch, int start, ref int end, char ch1, char ch2, char ch3)
+ {
+ if (end - start < 3)
+ {
+ //no chance that something is escaped
+ return;
+ }
+
+ char* pend = pch + end - 2;
+ pch += start;
+ char* pnew = null;
+
+ over:
+
+ // Just looking for a interested escaped char
+ if (pch >= pend) goto done;
+ if (*pch++ != '%') goto over;
+
+ char ch = UriHelper.EscapedAscii(*pch++, *pch++);
+ if (!(ch == ch1 || ch == ch2 || ch == ch3)) goto over;
+
+ // Here we found something and now start copying the scanned chars
+ pnew = pch - 2;
+ *(pnew - 1) = ch;
+
+ over_new:
+
+ if (pch >= pend) goto done;
+ if ((*pnew++ = *pch++) != '%') goto over_new;
+
+ ch = UriHelper.EscapedAscii((*pnew++ = *pch++), (*pnew++ = *pch++));
+ if (!(ch == ch1 || ch == ch2 || ch == ch3))
+ {
+ goto over_new;
+ }
+
+ pnew -= 2;
+ *(pnew - 1) = ch;
+
+ goto over_new;
+
+ done:
+ pend += 2;
+
+ if (pnew == null)
+ {
+ //nothing was found
+ return;
+ }
+
+ //the tail may be already processed
+ if (pch == pend)
+ {
+ end -= (int)(pch - pnew);
+ return;
+ }
+
+ *pnew++ = *pch++;
+ if (pch == pend)
+ {
+ end -= (int)(pch - pnew);
+ return;
+ }
+ *pnew++ = *pch++;
+ end -= (int)(pch - pnew);
+ }
+
+ //
+ // This will compress any "\" "/../" "/./" "///" "/..../" /XXX.../, etc found in the input
+ //
+ // The passed syntax controls whether to use agressive compression or the one specified in RFC 2396
+ //
+ private static char[] Compress(char[] dest, ushort start, ref int destLength, UriParser syntax)
+ {
+ ushort slashCount = 0;
+ ushort lastSlash = 0;
+ ushort dotCount = 0;
+ ushort removeSegments = 0;
+
+ unchecked
+ {
+ //ushort i == -1 and start == -1 overflow is ok here
+ ushort i = (ushort)((ushort)destLength - (ushort)1);
+ start = (ushort)(start - 1);
+
+ for (; i != start; --i)
+ {
+ char ch = dest[i];
+ if (ch == '\\' && syntax.InFact(UriSyntaxFlags.ConvertPathSlashes))
+ {
+ dest[i] = ch = '/';
+ }
+
+ //
+ // compress multiple '/' for file URI
+ //
+ if (ch == '/')
+ {
+ ++slashCount;
+ }
+ else
+ {
+ if (slashCount > 1)
+ {
+ // else preserve repeated slashes
+ lastSlash = (ushort)(i + 1);
+ }
+ slashCount = 0;
+ }
+
+ if (ch == '.')
+ {
+ ++dotCount;
+ continue;
+ }
+ else if (dotCount != 0)
+ {
+ bool skipSegment = syntax.NotAny(UriSyntaxFlags.CanonicalizeAsFilePath)
+ && (dotCount > 2 || ch != '/' || i == start);
+
+ //
+ // Cases:
+ // /./ = remove this segment
+ // /../ = remove this segment, mark next for removal
+ // /....x = DO NOT TOUCH, leave as is
+ // x.../ = DO NOT TOUCH, leave as is, except for V2 legacy mode
+ //
+ if (!skipSegment && ch == '/')
+ {
+ if ((lastSlash == i + dotCount + 1 // "/..../"
+ || (lastSlash == 0 && i + dotCount + 1 == destLength)) // "/..."
+ && (dotCount <= 2))
+ {
+ //
+ // /./ or /.<eos> or /../ or /..<eos>
+ //
+ // just reusing a variable slot we perform //dest.Remove(i+1, dotCount + (lastSlash==0?0:1));
+ lastSlash = (ushort)(i + 1 + dotCount + (lastSlash == 0 ? 0 : 1));
+ Buffer.BlockCopy(dest, lastSlash << 1, dest, (i + 1) << 1, (destLength - lastSlash) << 1);
+ destLength -= (lastSlash - i - 1);
+
+ lastSlash = i;
+ if (dotCount == 2)
+ {
+ //
+ // We have 2 dots in between like /../ or /..<eos>,
+ // Mark next segment for removal and remove this /../ or /..
+ //
+ ++removeSegments;
+ }
+ dotCount = 0;
+ continue;
+ }
+ }
+ // .NET 4.5 no longer removes trailing dots in a path segment x.../ or x...<eos>
+ dotCount = 0;
+
+ //
+ // Here all other cases go such as
+ // x.[..]y or /.[..]x or (/x.[...][/] && removeSegments !=0)
+ }
+
+ //
+ // Now we may want to remove a segment because of previous /../
+ //
+ if (ch == '/')
+ {
+ if (removeSegments != 0)
+ {
+ --removeSegments;
+
+ // just reusing a variable slot we perform //dest.Remove(i+1, lastSlash - i);
+ lastSlash = (ushort)(lastSlash + 1);
+ Buffer.BlockCopy(dest, lastSlash << 1, dest, (i + 1) << 1, (destLength - lastSlash) << 1);
+ destLength -= (lastSlash - i - 1);
+ }
+ lastSlash = i;
+ }
+ }
+
+ start = (ushort)((ushort)start + (ushort)1);
+ } //end of unchecked
+
+ if ((ushort)destLength > start && syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath))
+ {
+ if (slashCount <= 1)
+ {
+ if (removeSegments != 0 && dest[start] != '/')
+ {
+ //remove first not rooted segment
+ lastSlash = (ushort)(lastSlash + 1);
+ Buffer.BlockCopy(dest, lastSlash << 1, dest, start << 1, (destLength - lastSlash) << 1);
+ destLength -= lastSlash;
+ }
+ else if (dotCount != 0)
+ {
+ // If final string starts with a segment looking like .[...]/ or .[...]<eos>
+ // then we remove this fisrt segment
+ if (lastSlash == dotCount + 1 || (lastSlash == 0 && dotCount + 1 == destLength))
+ {
+ dotCount = (ushort)(dotCount + (lastSlash == 0 ? 0 : 1));
+ Buffer.BlockCopy(dest, dotCount << 1, dest, start << 1, (destLength - dotCount) << 1);
+ destLength -= dotCount;
+ }
+ }
+ }
+ }
+ return dest;
+ }
+
+ //used by DigestClient
+ internal static readonly char[] HexLowerChars = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ internal static int CalculateCaseInsensitiveHashCode(string text)
+ {
+ return text.ToLowerInvariant().GetHashCode();
+ }
+
+ //
+ // CombineUri
+ //
+ // Given 2 URI strings, combine them into a single resultant URI string
+ //
+ // Inputs:
+ // <argument> basePart
+ // Base URI to combine with
+ //
+ // <argument> relativePart
+ // String expected to be relative URI
+ //
+ // Assumes:
+ // <basePart> is in canonic form
+ //
+ // Returns:
+ // Resulting combined URI string
+ //
+ private static string CombineUri(Uri basePart, string relativePart, UriFormat uriFormat)
+ {
+ //NB: relativePart is ensured as not empty by the caller
+ // Another assumption is that basePart is an AbsoluteUri
+
+ // This method was not optimized for efficiency
+ // Means a relative Uri ctor may be relatively slow plus it increases the footprint of the baseUri
+
+ char c1 = relativePart[0];
+
+ //check a special case for the base as DOS path and a rooted relative string
+ if (basePart.IsDosPath &&
+ (c1 == '/' || c1 == '\\') &&
+ (relativePart.Length == 1 || (relativePart[1] != '/' && relativePart[1] != '\\')))
+ {
+ // take relative part appended to the base string after the drive letter
+ int idx = basePart.OriginalString.IndexOf(':');
+ if (basePart.IsImplicitFile)
+ {
+ return basePart.OriginalString.Substring(0, idx + 1) + relativePart;
+ }
+ // The basePart has explicit scheme (could be not file:), take the DOS drive ':' position
+ idx = basePart.OriginalString.IndexOf(':', idx + 1);
+ return basePart.OriginalString.Substring(0, idx + 1) + relativePart;
+ }
+
+ // Check special case for Unc or absolute path in relativePart when base is FILE
+ if (StaticIsFile(basePart.Syntax))
+ {
+ if (c1 == '\\' || c1 == '/')
+ {
+ if (relativePart.Length >= 2 && (relativePart[1] == '\\' || relativePart[1] == '/'))
+ {
+ //Assuming relative is a Unc path and base is a file uri.
+ return basePart.IsImplicitFile ? relativePart : "file:" + relativePart;
+ }
+
+ // here we got an absolute path in relativePart,
+ // For compatibility with V1.0 parser we restrict the compression scope to Unc Share, i.e. \\host\share\
+ if (basePart.IsUnc)
+ {
+ string share = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
+ UriFormat.Unescaped);
+ for (int i = 1; i < share.Length; ++i)
+ {
+ if (share[i] == '/')
+ {
+ share = share.Substring(0, i);
+ break;
+ }
+ }
+ if (basePart.IsImplicitFile)
+ {
+ return @"\\"
+ + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped)
+ + share
+ + relativePart;
+ }
+ return "file://"
+ + basePart.GetParts(UriComponents.Host, uriFormat)
+ + share
+ + relativePart;
+ }
+ // It's not obvious but we've checked (for this relativePart format) that baseUti is nor UNC nor DOS path
+ //
+ // Means base is a Unix style path and, btw, IsImplicitFile cannot be the case either
+ return "file://" + relativePart;
+ }
+ }
+
+ // If we are here we did not recognize absolute DOS/UNC path for a file: base uri
+ // Note that DOS path may still happen in the relativePart and if so it may override the base uri scheme.
+
+ bool convBackSlashes = basePart.Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes);
+
+ string left = null;
+
+ // check for network or local absolute path
+ if (c1 == '/' || (c1 == '\\' && convBackSlashes))
+ {
+ if (relativePart.Length >= 2 && relativePart[1] == '/')
+ {
+ // got an authority in relative path and the base scheme is not file (checked)
+ return basePart.Scheme + ':' + relativePart;
+ }
+
+ // Got absolute relative path, and the base is nor FILE nor a DOS path (checked at the method start)
+ if (basePart.HostType == Flags.IPv6HostType)
+ {
+ left = basePart.GetParts(UriComponents.Scheme | UriComponents.UserInfo, uriFormat)
+ + '[' + basePart.DnsSafeHost + ']'
+ + basePart.GetParts(UriComponents.KeepDelimiter | UriComponents.Port, uriFormat);
+ }
+ else
+ {
+ left = basePart.GetParts(UriComponents.SchemeAndServer | UriComponents.UserInfo, uriFormat);
+ }
+
+ if (convBackSlashes && c1 == '\\')
+ relativePart = '/' + relativePart.Substring(1);
+
+ return left + relativePart;
+ }
+
+ // Here we got a relative path
+ // Need to run path Compression because this is how relative Uri combining works
+
+ // Take the base part path up to and including the last slash
+ left = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
+ basePart.IsImplicitFile ? UriFormat.Unescaped : uriFormat);
+ int length = left.Length;
+ char[] path = new char[length + relativePart.Length];
+
+ if (length > 0)
+ {
+ left.CopyTo(0, path, 0, length);
+ while (length > 0)
+ {
+ if (path[--length] == '/')
+ {
+ ++length;
+ break;
+ }
+ }
+ }
+
+ //Append relative path to the result
+ relativePart.CopyTo(0, path, length, relativePart.Length);
+
+ // Split relative on path and extra (for compression)
+ c1 = basePart.Syntax.InFact(UriSyntaxFlags.MayHaveQuery) ? '?' : c_DummyChar;
+
+ // The implcit file check is to avoid a fragment in the implicit file combined uri.
+ char c2 = (!basePart.IsImplicitFile && basePart.Syntax.InFact(UriSyntaxFlags.MayHaveFragment)) ? '#' :
+ c_DummyChar;
+ string extra = string.Empty;
+
+ // assuming c_DummyChar may not happen in an unicode uri string
+ if (!(c1 == c_DummyChar && c2 == c_DummyChar))
+ {
+ int i = 0;
+ for (; i < relativePart.Length; ++i)
+ {
+ if (path[length + i] == c1 || path[length + i] == c2)
+ {
+ break;
+ }
+ }
+ if (i == 0)
+ {
+ extra = relativePart;
+ }
+ else if (i < relativePart.Length)
+ {
+ extra = relativePart.Substring(i);
+ }
+ length += i;
+ }
+ else
+ {
+ length += relativePart.Length;
+ }
+
+ // Take the base part up to the path
+ if (basePart.HostType == Flags.IPv6HostType)
+ {
+ if (basePart.IsImplicitFile)
+ {
+ left = @"\\[" + basePart.DnsSafeHost + ']';
+ }
+ else
+ {
+ left = basePart.GetParts(UriComponents.Scheme | UriComponents.UserInfo, uriFormat)
+ + '[' + basePart.DnsSafeHost + ']'
+ + basePart.GetParts(UriComponents.KeepDelimiter | UriComponents.Port, uriFormat);
+ }
+ }
+ else
+ {
+ if (basePart.IsImplicitFile)
+ {
+ if (basePart.IsDosPath)
+ {
+ // The FILE DOS path comes as /c:/path, we have to exclude first 3 chars from compression
+ path = Compress(path, 3, ref length, basePart.Syntax);
+ return new string(path, 1, length - 1) + extra;
+ }
+ else
+ {
+ left = @"\\" + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped);
+ }
+ }
+ else
+ {
+ left = basePart.GetParts(UriComponents.SchemeAndServer | UriComponents.UserInfo, uriFormat);
+ }
+ }
+ //compress the path
+ path = Compress(path, basePart.SecuredPathIndex, ref length, basePart.Syntax);
+ return left + new string(path, 0, length) + extra;
+ }
+
+ //
+ // PathDifference
+ //
+ // Performs the relative path calculation for MakeRelative()
+ //
+ // Inputs:
+ // <argument> path1
+ // <argument> path2
+ // Paths for which we calculate the difference
+ //
+ // <argument> compareCase
+ // False if we consider characters that differ only in case to be
+ // equal
+ //
+ // Returns:
+ // A string which is the relative path difference between <path1> and
+ // <path2> such that if <path1> and the calculated difference are used
+ // as arguments to Combine(), <path2> is returned
+ //
+ // Throws:
+ // Nothing
+ //
+ private static string PathDifference(string path1, string path2, bool compareCase)
+ {
+ int i;
+ int si = -1;
+
+ for (i = 0; (i < path1.Length) && (i < path2.Length); ++i)
+ {
+ if ((path1[i] != path2[i])
+ && (compareCase
+ || (char.ToLowerInvariant(path1[i])
+ != char.ToLowerInvariant(path2[i]))))
+ {
+ break;
+ }
+ else if (path1[i] == '/')
+ {
+ si = i;
+ }
+ }
+
+ if (i == 0)
+ {
+ return path2;
+ }
+ if ((i == path1.Length) && (i == path2.Length))
+ {
+ return string.Empty;
+ }
+
+ StringBuilder relPath = new StringBuilder();
+ // Walk down several dirs
+ for (; i < path1.Length; ++i)
+ {
+ if (path1[i] == '/')
+ {
+ relPath.Append("../");
+ }
+ }
+ // Same path except that path1 ended with a file name and path2 didn't
+ if (relPath.Length == 0 && path2.Length - 1 == si)
+ return "./"; // Truncate the file name
+ return relPath.ToString() + path2.Substring(si + 1);
+ }
+
+ //Used by Uribuilder
+ internal bool HasAuthority
+ {
+ get
+ {
+ return InFact(Flags.AuthorityFound);
+ }
+ }
+
+ private static readonly char[] s_WSchars = new char[] { ' ', '\n', '\r', '\t' };
+ private static bool IsLWS(char ch)
+ {
+ return (ch <= ' ') && (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t');
+ }
+
+ //Only consider ASCII characters
+ private static bool IsAsciiLetter(char character)
+ {
+ return (character >= 'a' && character <= 'z') ||
+ (character >= 'A' && character <= 'Z');
+ }
+
+ internal static bool IsAsciiLetterOrDigit(char character)
+ {
+ return IsAsciiLetter(character) || (character >= '0' && character <= '9');
+ }
+
+ //
+ // Is this a Bidirectional control char.. These get stripped
+ //
+ internal static bool IsBidiControlCharacter(char ch)
+ {
+ return (ch == '\u200E' /*LRM*/ || ch == '\u200F' /*RLM*/ || ch == '\u202A' /*LRE*/ ||
+ ch == '\u202B' /*RLE*/ || ch == '\u202C' /*PDF*/ || ch == '\u202D' /*LRO*/ ||
+ ch == '\u202E' /*RLO*/);
+ }
+
+ //
+ // Strip Bidirectional control charcters from this string
+ //
+ internal static unsafe string StripBidiControlCharacter(char* strToClean, int start, int length)
+ {
+ if (length <= 0) return "";
+
+ char[] cleanStr = new char[length];
+ int count = 0;
+ for (int i = 0; i < length; ++i)
+ {
+ char c = strToClean[start + i];
+ if (c < '\u200E' || c > '\u202E' || !IsBidiControlCharacter(c))
+ {
+ cleanStr[count++] = c;
+ }
+ }
+ return new string(cleanStr, 0, count);
+ }
+ } // class Uri
+} // namespace System
diff --git a/src/System.Private.Uri/src/System/UriBuilder.cs b/src/System.Private.Uri/src/System/UriBuilder.cs
new file mode 100644
index 0000000000..e078fceb3d
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriBuilder.cs
@@ -0,0 +1,441 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Text;
+using System.Globalization;
+using System.Threading;
+
+namespace System
+{
+ public class UriBuilder
+ {
+ // fields
+
+ private bool _changed = true;
+ private string _fragment = string.Empty;
+ private string _host = "localhost";
+ private string _password = string.Empty;
+ private string _path = "/";
+ private int _port = -1;
+ private string _query = string.Empty;
+ private string _scheme = "http";
+ private string _schemeDelimiter = Uri.SchemeDelimiter;
+ private Uri _uri;
+ private string _username = string.Empty;
+
+ // constructors
+
+ public UriBuilder()
+ {
+ }
+
+ public UriBuilder(string uri)
+ {
+ // setting allowRelative=true for a string like www.acme.org
+ Uri tryUri = new Uri(uri, UriKind.RelativeOrAbsolute);
+
+ if (tryUri.IsAbsoluteUri)
+ {
+ Init(tryUri);
+ }
+ else
+ {
+ uri = Uri.UriSchemeHttp + Uri.SchemeDelimiter + uri;
+ Init(new Uri(uri));
+ }
+ }
+
+ public UriBuilder(Uri uri)
+ {
+ if ((object)uri == null)
+ throw new ArgumentNullException("uri");
+
+ Init(uri);
+ }
+
+ private void Init(Uri uri)
+ {
+ _fragment = uri.Fragment;
+ _query = uri.Query;
+ _host = uri.Host;
+ _path = uri.AbsolutePath;
+ _port = uri.Port;
+ _scheme = uri.Scheme;
+ _schemeDelimiter = uri.HasAuthority ? Uri.SchemeDelimiter : ":";
+
+ string userInfo = uri.UserInfo;
+
+ if (!string.IsNullOrEmpty(userInfo))
+ {
+ int index = userInfo.IndexOf(':');
+
+ if (index != -1)
+ {
+ _password = userInfo.Substring(index + 1);
+ _username = userInfo.Substring(0, index);
+ }
+ else
+ {
+ _username = userInfo;
+ }
+ }
+ SetFieldsFromUri(uri);
+ }
+
+ public UriBuilder(string schemeName, string hostName)
+ {
+ Scheme = schemeName;
+ Host = hostName;
+ }
+
+ public UriBuilder(string scheme, string host, int portNumber) : this(scheme, host)
+ {
+ Port = portNumber;
+ }
+
+ public UriBuilder(string scheme,
+ string host,
+ int port,
+ string pathValue
+ ) : this(scheme, host, port)
+ {
+ Path = pathValue;
+ }
+
+ public UriBuilder(string scheme,
+ string host,
+ int port,
+ string path,
+ string extraValue
+ ) : this(scheme, host, port, path)
+ {
+ try
+ {
+ Extra = extraValue;
+ }
+ catch (Exception exception)
+ {
+ if (exception is OutOfMemoryException)
+ {
+ throw;
+ }
+
+ throw new ArgumentException("extraValue");
+ }
+ }
+
+ // properties
+
+ private string Extra
+ {
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ if (value.Length > 0)
+ {
+ if (value[0] == '#')
+ {
+ Fragment = value.Substring(1);
+ }
+ else if (value[0] == '?')
+ {
+ int end = value.IndexOf('#');
+ if (end == -1)
+ {
+ end = value.Length;
+ }
+ else
+ {
+ Fragment = value.Substring(end + 1);
+ }
+ Query = value.Substring(1, end - 1);
+ }
+ else
+ {
+ throw new ArgumentException("value");
+ }
+ }
+ else
+ {
+ Fragment = string.Empty;
+ Query = string.Empty;
+ }
+ }
+ }
+
+ public string Fragment
+ {
+ get
+ {
+ return _fragment;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ if (value.Length > 0)
+ {
+ value = '#' + value;
+ }
+ _fragment = value;
+ _changed = true;
+ }
+ }
+
+ public string Host
+ {
+ get
+ {
+ return _host;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ _host = value;
+ //probable ipv6 address - Note: this is only supported for cases where the authority is inet-based.
+ if (_host.IndexOf(':') >= 0)
+ {
+ //set brackets
+ if (_host[0] != '[')
+ _host = "[" + _host + "]";
+ }
+ _changed = true;
+ }
+ }
+
+ public string Password
+ {
+ get
+ {
+ return _password;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ _password = value;
+ _changed = true;
+ }
+ }
+
+ public string Path
+ {
+ get
+ {
+ return _path;
+ }
+ set
+ {
+ if ((value == null) || (value.Length == 0))
+ {
+ value = "/";
+ }
+ _path = Uri.InternalEscapeString(ConvertSlashes(value));
+ _changed = true;
+ }
+ }
+
+ public int Port
+ {
+ get
+ {
+ return _port;
+ }
+ set
+ {
+ if (value < -1 || value > 0xFFFF)
+ {
+ throw new ArgumentOutOfRangeException("value");
+ }
+ _port = value;
+ _changed = true;
+ }
+ }
+
+ public string Query
+ {
+ get
+ {
+ return _query;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ if (value.Length > 0)
+ {
+ value = '?' + value;
+ }
+ _query = value;
+ _changed = true;
+ }
+ }
+
+ public string Scheme
+ {
+ get
+ {
+ return _scheme;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ int index = value.IndexOf(':');
+ if (index != -1)
+ {
+ value = value.Substring(0, index);
+ }
+
+ if (value.Length != 0)
+ {
+ if (!Uri.CheckSchemeName(value))
+ {
+ throw new ArgumentException("value");
+ }
+ value = value.ToLowerInvariant();
+ }
+ _scheme = value;
+ _changed = true;
+ }
+ }
+
+ public Uri Uri
+ {
+ get
+ {
+ if (_changed)
+ {
+ _uri = new Uri(ToString());
+ SetFieldsFromUri(_uri);
+ _changed = false;
+ }
+ return _uri;
+ }
+ }
+
+ public string UserName
+ {
+ get
+ {
+ return _username;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ _username = value;
+ _changed = true;
+ }
+ }
+
+ // methods
+
+ private string ConvertSlashes(string path)
+ {
+ StringBuilder sb = new StringBuilder(path.Length);
+ char ch;
+
+ for (int i = 0; i < path.Length; ++i)
+ {
+ ch = path[i];
+ if (ch == '\\')
+ {
+ ch = '/';
+ }
+ sb.Append(ch);
+ }
+ return sb.ToString();
+ }
+
+ public override bool Equals(object rparam)
+ {
+ if (rparam == null)
+ {
+ return false;
+ }
+ return Uri.Equals(rparam.ToString());
+ }
+
+ public override int GetHashCode()
+ {
+ return Uri.GetHashCode();
+ }
+
+ private void SetFieldsFromUri(Uri uri)
+ {
+ _fragment = uri.Fragment;
+ _query = uri.Query;
+ _host = uri.Host;
+ _path = uri.AbsolutePath;
+ _port = uri.Port;
+ _scheme = uri.Scheme;
+ _schemeDelimiter = uri.HasAuthority ? Uri.SchemeDelimiter : ":";
+
+ string userInfo = uri.UserInfo;
+
+ if (userInfo.Length > 0)
+ {
+ int index = userInfo.IndexOf(':');
+
+ if (index != -1)
+ {
+ _password = userInfo.Substring(index + 1);
+ _username = userInfo.Substring(0, index);
+ }
+ else
+ {
+ _username = userInfo;
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ if (_username.Length == 0 && _password.Length > 0)
+ {
+ throw new UriFormatException(SR.net_uri_BadUserPassword);
+ }
+
+ if (_scheme.Length != 0)
+ {
+ UriParser syntax = UriParser.GetSyntax(_scheme);
+ if (syntax != null)
+ _schemeDelimiter = syntax.InFact(UriSyntaxFlags.MustHaveAuthority) ||
+ (_host.Length != 0 && syntax.NotAny(UriSyntaxFlags.MailToLikeUri) && syntax.InFact(UriSyntaxFlags.OptionalAuthority))
+ ? Uri.SchemeDelimiter
+ : ":";
+ else
+ _schemeDelimiter = _host.Length != 0 ? Uri.SchemeDelimiter : ":";
+ }
+
+ string result = _scheme.Length != 0 ? (_scheme + _schemeDelimiter) : string.Empty;
+ return result
+ + _username
+ + ((_password.Length > 0) ? (":" + _password) : string.Empty)
+ + ((_username.Length > 0) ? "@" : string.Empty)
+ + _host
+ + (((_port != -1) && (_host.Length > 0)) ? (":" + _port) : string.Empty)
+ + (((_host.Length > 0) && (_path.Length != 0) && (_path[0] != '/')) ? "/" : string.Empty) + _path
+ + _query
+ + _fragment;
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriEnumTypes.cs b/src/System.Private.Uri/src/System/UriEnumTypes.cs
new file mode 100644
index 0000000000..f96c06c451
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriEnumTypes.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System
+{
+ // Used to control whether absolute absolute or relative URIs are used
+ public enum UriKind
+ {
+ RelativeOrAbsolute = 0,
+ Absolute = 1,
+ Relative = 2
+ }
+
+ [Flags]
+ public enum UriComponents
+ {
+ // Generic parts.
+ // ATTN: The values must stay in sync with Uri.Flags.xxxNotCanonical
+ Scheme = 0x1,
+ UserInfo = 0x2,
+ Host = 0x4,
+ Port = 0x8,
+ Path = 0x10,
+ Query = 0x20,
+ Fragment = 0x40,
+
+ StrongPort = 0x80,
+ NormalizedHost = 0x100,
+
+ // This will also return respective delimiters for scheme, userinfo or port
+ // Valid only for a single componet requests.
+ KeepDelimiter = 0x40000000,
+
+ // This is used by GetObjectData and can also be used directly.
+ // Works for both absolute and relaitve Uris
+ SerializationInfoString = unchecked((int)0x80000000),
+
+ // Shortcuts for general cases
+ AbsoluteUri = Scheme | UserInfo | Host | Port | Path | Query | Fragment,
+ HostAndPort = Host | StrongPort, //includes port even if default
+ StrongAuthority = UserInfo | Host | StrongPort, //includes port even if default
+ SchemeAndServer = Scheme | Host | Port,
+ HttpRequestUrl = Scheme | Host | Port | Path | Query,
+ PathAndQuery = Path | Query,
+ }
+ public enum UriFormat
+ {
+ UriEscaped = 1,
+ Unescaped = 2, // Completely unescaped.
+ SafeUnescaped = 3 // Canonical unescaped. Allows same uri to be reconstructed from the output.
+ // If the unescaped sequence results in a new escaped sequence, it will revert to the original sequence.
+
+ // This value is reserved for the default ToString() format that is historically none of the above.
+ // V1ToStringUnescape = 0x7FFF
+ }
+
+ // This is used to control when host names are converted to idn names and
+ // vice versa
+ internal enum UriIdnScope
+ {
+ None, // Never use Idn
+ AllExceptIntranet, // Use Idn in Internet and not intranet
+ All // Internet and intranet
+ }
+
+ internal enum ParsingError
+ {
+ // looks good
+ None = 0,
+
+ // These first errors indicate that the Uri cannot be absolute, but may be relative.
+ BadFormat = 1,
+ BadScheme = 2,
+ BadAuthority = 3,
+ EmptyUriString = 4,
+ LastRelativeUriOkErrIndex = 4,
+
+ // All higher error values are fatal, indicating that neither an absolute or relative
+ // Uri could be generated.
+ SchemeLimit = 5,
+ SizeLimit = 6,
+ MustRootedPath = 7,
+
+ // derived class controlled
+ BadHostName = 8,
+ NonEmptyHost = 9, // unix only
+ BadPort = 10,
+ BadAuthorityTerminator = 11,
+
+ // The user requested only a relative Uri, but an absolute Uri was parsed.
+ CannotCreateRelative = 12
+ }
+
+ [Flags]
+ internal enum UnescapeMode
+ {
+ CopyOnly = 0x0, // used for V1.0 ToString() compatibility mode only
+ Escape = 0x1, // Only used by ImplicitFile, the string is already fully unescaped
+ Unescape = 0x2, // Only used as V1.0 UserEscaped compatibility mode
+ EscapeUnescape = Unescape | Escape, // does both escaping control+reserved and unescaping of safe characters
+ V1ToStringFlag = 0x4, // Only used as V1.0 ToString() compatibility mode, assumes DontEscape level also
+ UnescapeAll = 0x8, // just unescape everything, leave bad escaped sequences as is
+ UnescapeAllOrThrow = 0x10 | UnescapeAll, // just unescape everything plus throw on bad escaped sequences
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriExt.cs b/src/System.Private.Uri/src/System/UriExt.cs
new file mode 100644
index 0000000000..c39e190321
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriExt.cs
@@ -0,0 +1,934 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Globalization;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+
+namespace System
+{
+ public partial class Uri
+ {
+ //
+ // All public ctors go through here
+ //
+ private void CreateThis(string uri, bool dontEscape, UriKind uriKind)
+ {
+ // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow
+ // to be used here.
+ if ((int)uriKind < (int)UriKind.RelativeOrAbsolute || (int)uriKind > (int)UriKind.Relative)
+ {
+ throw new ArgumentException(SR.Format(SR.net_uri_InvalidUriKind, uriKind));
+ }
+
+ _string = uri == null ? string.Empty : uri;
+
+ if (dontEscape)
+ _flags |= Flags.UserEscaped;
+
+ ParsingError err = ParseScheme(_string, ref _flags, ref _syntax);
+ UriFormatException e;
+
+ InitializeUri(err, uriKind, out e);
+ if (e != null)
+ throw e;
+ }
+
+ private void InitializeUri(ParsingError err, UriKind uriKind, out UriFormatException e)
+ {
+ if (err == ParsingError.None)
+ {
+ if (IsImplicitFile)
+ {
+ // V1 compat
+ // A relative Uri wins over implicit UNC path unless the UNC path is of the form "\\something" and
+ // uriKind != Absolute
+ if (NotAny(Flags.DosPath) &&
+ uriKind != UriKind.Absolute &&
+ (uriKind == UriKind.Relative || (_string.Length >= 2 && (_string[0] != '\\' || _string[1] != '\\'))))
+
+ {
+ _syntax = null; //make it be relative Uri
+ _flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
+ e = null;
+ return;
+ // Otheriwse an absolute file Uri wins when it's of the form "\\something"
+ }
+ //
+ // V1 compat issue
+ // We should support relative Uris of the form c:\bla or c:/bla
+ //
+ else if (uriKind == UriKind.Relative && InFact(Flags.DosPath))
+ {
+ _syntax = null; //make it be relative Uri
+ _flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
+ e = null;
+ return;
+ // Otheriwse an absolute file Uri wins when it's of the form "c:\something"
+ }
+ }
+ }
+ else if (err > ParsingError.LastRelativeUriOkErrIndex)
+ {
+ //This is a fatal error based solely on scheme name parsing
+ _string = null; // make it be invalid Uri
+ e = GetException(err);
+ return;
+ }
+
+ bool hasUnicode = false;
+
+ _iriParsing = (s_IriParsing && ((_syntax == null) || _syntax.InFact(UriSyntaxFlags.AllowIriParsing)));
+
+ if (_iriParsing &&
+ (CheckForUnicode(_string) || CheckForEscapedUnreserved(_string)))
+ {
+ _flags |= Flags.HasUnicode;
+ hasUnicode = true;
+ // switch internal strings
+ _originalUnicodeString = _string; // original string location changed
+ }
+
+ if (_syntax != null)
+ {
+ if (_syntax.IsSimple)
+ {
+ if ((err = PrivateParseMinimal()) != ParsingError.None)
+ {
+ if (uriKind != UriKind.Absolute && err <= ParsingError.LastRelativeUriOkErrIndex)
+ {
+ // RFC 3986 Section 5.4.2 - http:(relativeUri) may be considered a valid relative Uri.
+ _syntax = null; // convert to relative uri
+ e = null;
+ _flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
+ }
+ else
+ e = GetException(err);
+ }
+ else if (uriKind == UriKind.Relative)
+ {
+ // Here we know that we can create an absolute Uri, but the user has requested only a relative one
+ e = GetException(ParsingError.CannotCreateRelative);
+ }
+ else
+ e = null;
+ // will return from here
+
+ if (_iriParsing && hasUnicode)
+ {
+ // In this scenario we need to parse the whole string
+ EnsureParseRemaining();
+ }
+ }
+ else
+ {
+ // offer custom parser to create a parsing context
+ _syntax = _syntax.InternalOnNewUri();
+
+ // incase they won't call us
+ _flags |= Flags.UserDrivenParsing;
+
+ // Ask a registered type to validate this uri
+ _syntax.InternalValidate(this, out e);
+
+ if (e != null)
+ {
+ // Can we still take it as a relative Uri?
+ if (uriKind != UriKind.Absolute && err != ParsingError.None
+ && err <= ParsingError.LastRelativeUriOkErrIndex)
+ {
+ _syntax = null; // convert it to relative
+ e = null;
+ _flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
+ }
+ }
+ else // e == null
+ {
+ if (err != ParsingError.None || InFact(Flags.ErrorOrParsingRecursion))
+ {
+ // User parser took over on an invalid Uri
+ SetUserDrivenParsing();
+ }
+ else if (uriKind == UriKind.Relative)
+ {
+ // Here we know that custom parser can create an absolute Uri, but the user has requested only a
+ // relative one
+ e = GetException(ParsingError.CannotCreateRelative);
+ }
+
+ if (_iriParsing && hasUnicode)
+ {
+ // In this scenario we need to parse the whole string
+ EnsureParseRemaining();
+ }
+ }
+ // will return from here
+ }
+ }
+ // If we encountered any parsing errors that indicate this may be a relative Uri,
+ // and we'll allow relative Uri's, then create one.
+ else if (err != ParsingError.None && uriKind != UriKind.Absolute
+ && err <= ParsingError.LastRelativeUriOkErrIndex)
+ {
+ e = null;
+ _flags &= (Flags.UserEscaped | Flags.HasUnicode); // the only flags that makes sense for a relative uri
+ if (_iriParsing && hasUnicode)
+ {
+ // Iri'ze and then normalize relative uris
+ _string = EscapeUnescapeIri(_originalUnicodeString, 0, _originalUnicodeString.Length,
+ (UriComponents)0);
+ }
+ }
+ else
+ {
+ _string = null; // make it be invalid Uri
+ e = GetException(err);
+ }
+ }
+
+ //
+ // Unescapes entire string and checks if it has unicode chars
+ //
+ private bool CheckForUnicode(string data)
+ {
+ bool hasUnicode = false;
+ char[] chars = new char[data.Length];
+ int count = 0;
+
+ chars = UriHelper.UnescapeString(data, 0, data.Length, chars, ref count, c_DummyChar, c_DummyChar,
+ c_DummyChar, UnescapeMode.Unescape | UnescapeMode.UnescapeAll, null, false);
+
+ for (int i = 0; i < count; ++i)
+ {
+ if (chars[i] > '\x7f')
+ {
+ // Unicode
+ hasUnicode = true;
+ break;
+ }
+ }
+ return hasUnicode;
+ }
+
+ // Does this string have any %6A sequences that are 3986 Unreserved characters? These should be un-escaped.
+ private unsafe bool CheckForEscapedUnreserved(string data)
+ {
+ fixed (char* tempPtr = data)
+ {
+ for (int i = 0; i < data.Length - 2; ++i)
+ {
+ if (tempPtr[i] == '%' && IsHexDigit(tempPtr[i + 1]) && IsHexDigit(tempPtr[i + 2])
+ && tempPtr[i + 1] >= '0' && tempPtr[i + 1] <= '7') // max 0x7F
+ {
+ char ch = UriHelper.EscapedAscii(tempPtr[i + 1], tempPtr[i + 2]);
+ if (ch != c_DummyChar && UriHelper.Is3986Unreserved(ch))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ //
+ // Returns true if the string represents a valid argument to the Uri ctor
+ // If uriKind != AbsoluteUri then certain parsing erros are ignored but Uri usage is limited
+ //
+ public static bool TryCreate(string uriString, UriKind uriKind, out Uri result)
+ {
+ if ((object)uriString == null)
+ {
+ result = null;
+ return false;
+ }
+ UriFormatException e = null;
+ result = CreateHelper(uriString, false, uriKind, ref e);
+ return (object)e == null && result != null;
+ }
+
+ public static bool TryCreate(Uri baseUri, string relativeUri, out Uri result)
+ {
+ Uri relativeLink;
+ if (TryCreate(relativeUri, UriKind.RelativeOrAbsolute, out relativeLink))
+ {
+ if (!relativeLink.IsAbsoluteUri)
+ return TryCreate(baseUri, relativeLink, out result);
+
+ result = relativeLink;
+ return true;
+ }
+ result = null;
+ return false;
+ }
+
+ public static bool TryCreate(Uri baseUri, Uri relativeUri, out Uri result)
+ {
+ result = null;
+
+ //TODO: Work out the baseUri==null case
+ if ((object)baseUri == null || (object)relativeUri == null)
+ return false;
+
+ if (baseUri.IsNotAbsoluteUri)
+ return false;
+
+ UriFormatException e;
+ string newUriString = null;
+
+ bool dontEscape;
+ if (baseUri.Syntax.IsSimple)
+ {
+ dontEscape = relativeUri.UserEscaped;
+ result = ResolveHelper(baseUri, relativeUri, ref newUriString, ref dontEscape, out e);
+ }
+ else
+ {
+ dontEscape = false;
+ newUriString = baseUri.Syntax.InternalResolve(baseUri, relativeUri, out e);
+ }
+
+ if (e != null)
+ return false;
+
+ if ((object)result == null)
+ result = CreateHelper(newUriString, dontEscape, UriKind.Absolute, ref e);
+
+ return (object)e == null && result != null && result.IsAbsoluteUri;
+ }
+
+ public string GetComponents(UriComponents components, UriFormat format)
+ {
+ if (((components & UriComponents.SerializationInfoString) != 0) && components != UriComponents.SerializationInfoString)
+ throw new ArgumentOutOfRangeException("components", components, SR.net_uri_NotJustSerialization);
+
+ if ((format & ~UriFormat.SafeUnescaped) != 0)
+ throw new ArgumentOutOfRangeException("format");
+
+ if (IsNotAbsoluteUri)
+ {
+ if (components == UriComponents.SerializationInfoString)
+ return GetRelativeSerializationString(format);
+ else
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+ }
+
+ if (Syntax.IsSimple)
+ return GetComponentsHelper(components, format);
+
+ return Syntax.InternalGetComponents(this, components, format);
+ }
+
+ //
+ // This is for languages that do not support == != operators overloading
+ //
+ // Note that Uri.Equals will get an optimized path but is limited to true/fasle result only
+ //
+ public static int Compare(Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat,
+ StringComparison comparisonType)
+ {
+ if ((object)uri1 == null)
+ {
+ if (uri2 == null)
+ return 0; // Equal
+ return -1; // null < non-null
+ }
+ if ((object)uri2 == null)
+ return 1; // non-null > null
+
+ // a relative uri is always less than an absolute one
+ if (!uri1.IsAbsoluteUri || !uri2.IsAbsoluteUri)
+ return uri1.IsAbsoluteUri ? 1 : uri2.IsAbsoluteUri ? -1 : string.Compare(uri1.OriginalString,
+ uri2.OriginalString, comparisonType);
+
+ return string.Compare(
+ uri1.GetParts(partsToCompare, compareFormat),
+ uri2.GetParts(partsToCompare, compareFormat),
+ comparisonType
+ );
+ }
+
+ public bool IsWellFormedOriginalString()
+ {
+ if (IsNotAbsoluteUri || Syntax.IsSimple)
+ return InternalIsWellFormedOriginalString();
+
+ return Syntax.InternalIsWellFormedOriginalString(this);
+ }
+
+ // TODO: (perf) Making it to not create a Uri internally
+ public static bool IsWellFormedUriString(string uriString, UriKind uriKind)
+ {
+ Uri result;
+
+ if (!Uri.TryCreate(uriString, uriKind, out result))
+ return false;
+
+ return result.IsWellFormedOriginalString();
+ }
+
+ //
+ // Internal stuff
+ //
+
+ // Returns false if OriginalString value
+ // (1) is not correctly escaped as per URI spec excluding intl UNC name case
+ // (2) or is an absolute Uri that represents implicit file Uri "c:\dir\file"
+ // (3) or is an absolute Uri that misses a slash before path "file://c:/dir/file"
+ // (4) or contains unescaped backslashes even if they will be treated
+ // as forward slashes like http:\\host/path\file or file:\\\c:\path
+ //
+ internal unsafe bool InternalIsWellFormedOriginalString()
+ {
+ if (UserDrivenParsing)
+ throw new InvalidOperationException(SR.Format(SR.net_uri_UserDrivenParsing, this.GetType().ToString()));
+
+ fixed (char* str = _string)
+ {
+ ushort idx = 0;
+ //
+ // For a relative Uri we only care about escaping and backslashes
+ //
+ if (!IsAbsoluteUri)
+ {
+ // my:scheme/path?query is not well formed because the colon is ambiguous
+ if (CheckForColonInFirstPathSegment(_string))
+ {
+ return false;
+ }
+ return (CheckCanonical(str, ref idx, (ushort)_string.Length, c_EOL)
+ & (Check.BackslashInPath | Check.EscapedCanonical)) == Check.EscapedCanonical;
+ }
+
+ //
+ // (2) or is an absolute Uri that represents implicit file Uri "c:\dir\file"
+ //
+ if (IsImplicitFile)
+ return false;
+
+ //This will get all the offsets, a Host name will be checked separatelly below
+ EnsureParseRemaining();
+
+ Flags nonCanonical = (_flags & (Flags.E_CannotDisplayCanonical | Flags.IriCanonical));
+ // User, Path, Query or Fragment may have some non escaped characters
+ if (((nonCanonical & Flags.E_CannotDisplayCanonical & (Flags.E_UserNotCanonical | Flags.E_PathNotCanonical |
+ Flags.E_QueryNotCanonical | Flags.E_FragmentNotCanonical)) != Flags.Zero) &&
+ (!_iriParsing || (_iriParsing &&
+ (((nonCanonical & Flags.E_UserNotCanonical) == 0) || ((nonCanonical & Flags.UserIriCanonical) == 0)) &&
+ (((nonCanonical & Flags.E_PathNotCanonical) == 0) || ((nonCanonical & Flags.PathIriCanonical) == 0)) &&
+ (((nonCanonical & Flags.E_QueryNotCanonical) == 0) || ((nonCanonical & Flags.QueryIriCanonical) == 0)) &&
+ (((nonCanonical & Flags.E_FragmentNotCanonical) == 0) || ((nonCanonical & Flags.FragmentIriCanonical) == 0)))))
+ {
+ return false;
+ }
+
+ // checking on scheme:\\ or file:////
+ if (InFact(Flags.AuthorityFound))
+ {
+ idx = (ushort)(_info.Offset.Scheme + _syntax.SchemeName.Length + 2);
+ if (idx >= _info.Offset.User || _string[idx - 1] == '\\' || _string[idx] == '\\')
+ return false;
+
+ if (InFact(Flags.UncPath | Flags.DosPath))
+ {
+ while (++idx < _info.Offset.User && (_string[idx] == '/' || _string[idx] == '\\'))
+ return false;
+ }
+ }
+
+
+ // (3) or is an absolute Uri that misses a slash before path "file://c:/dir/file"
+ // Note that for this check to be more general we assert that if Path is non empty and if it requires a first slash
+ // (which looks absent) then the method has to fail.
+ // Today it's only possible for a Dos like path, i.e. file://c:/bla would fail below check.
+ if (InFact(Flags.FirstSlashAbsent) && _info.Offset.Query > _info.Offset.Path)
+ return false;
+
+ // (4) or contains unescaped backslashes even if they will be treated
+ // as forward slashes like http:\\host/path\file or file:\\\c:\path
+ // Note we do not check for Flags.ShouldBeCompressed i.e. allow // /./ and alike as valid
+ if (InFact(Flags.BackslashInPath))
+ return false;
+
+ // Capturing a rare case like file:///c|/dir
+ if (IsDosPath && _string[_info.Offset.Path + SecuredPathIndex - 1] == '|')
+ return false;
+
+ //
+ // May need some real CPU processing to anwser the request
+ //
+ //
+ // Check escaping for authority
+ //
+ // IPv6 hosts cannot be properly validated by CheckCannonical
+ if ((_flags & Flags.CanonicalDnsHost) == 0 && HostType != Flags.IPv6HostType)
+ {
+ idx = _info.Offset.User;
+ Check result = CheckCanonical(str, ref idx, (ushort)_info.Offset.Path, '/');
+ if (((result & (Check.ReservedFound | Check.BackslashInPath | Check.EscapedCanonical))
+ != Check.EscapedCanonical)
+ && (!_iriParsing || (_iriParsing
+ && ((result & (Check.DisplayCanonical | Check.FoundNonAscii | Check.NotIriCanonical))
+ != (Check.DisplayCanonical | Check.FoundNonAscii)))))
+ {
+ return false;
+ }
+ }
+
+ // Want to ensure there are slashes after the scheme
+ if ((_flags & (Flags.SchemeNotCanonical | Flags.AuthorityFound))
+ == (Flags.SchemeNotCanonical | Flags.AuthorityFound))
+ {
+ idx = (ushort)_syntax.SchemeName.Length;
+ while (str[idx++] != ':') ;
+ if (idx + 1 >= _string.Length || str[idx] != '/' || str[idx + 1] != '/')
+ return false;
+ }
+ }
+ //
+ // May be scheme, host, port or path need some canonicalization but still the uri string is found to be a
+ // "well formed" one
+ //
+ return true;
+ }
+
+ public static string UnescapeDataString(string stringToUnescape)
+ {
+ if ((object)stringToUnescape == null)
+ throw new ArgumentNullException("stringToUnescape");
+
+ if (stringToUnescape.Length == 0)
+ return string.Empty;
+
+ unsafe
+ {
+ fixed (char* pStr = stringToUnescape)
+ {
+ int position;
+ for (position = 0; position < stringToUnescape.Length; ++position)
+ if (pStr[position] == '%')
+ break;
+
+ if (position == stringToUnescape.Length)
+ return stringToUnescape;
+
+ UnescapeMode unescapeMode = UnescapeMode.Unescape | UnescapeMode.UnescapeAll;
+ position = 0;
+ char[] dest = new char[stringToUnescape.Length];
+ dest = UriHelper.UnescapeString(stringToUnescape, 0, stringToUnescape.Length, dest, ref position,
+ c_DummyChar, c_DummyChar, c_DummyChar, unescapeMode, null, false);
+ return new string(dest, 0, position);
+ }
+ }
+ }
+
+ //
+ // Where stringToEscape is intented to be a completely unescaped URI string.
+ // This method will escape any character that is not a reserved or unreserved character, including percent signs.
+ // Note that EscapeUriString will also do not escape a '#' sign.
+ //
+ public static string EscapeUriString(string stringToEscape)
+ {
+ if ((object)stringToEscape == null)
+ throw new ArgumentNullException("stringToEscape");
+
+ if (stringToEscape.Length == 0)
+ return string.Empty;
+
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(stringToEscape, 0, stringToEscape.Length, null, ref position, true,
+ c_DummyChar, c_DummyChar, c_DummyChar);
+ if ((object)dest == null)
+ return stringToEscape;
+ return new string(dest, 0, position);
+ }
+
+ //
+ // Where stringToEscape is intended to be URI data, but not an entire URI.
+ // This method will escape any character that is not an unreserved character, including percent signs.
+ //
+ public static string EscapeDataString(string stringToEscape)
+ {
+ if ((object)stringToEscape == null)
+ throw new ArgumentNullException("stringToEscape");
+
+ if (stringToEscape.Length == 0)
+ return string.Empty;
+
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(stringToEscape, 0, stringToEscape.Length, null, ref position, false,
+ c_DummyChar, c_DummyChar, c_DummyChar);
+ if (dest == null)
+ return stringToEscape;
+ return new string(dest, 0, position);
+ }
+
+ //
+ // Cleans up the specified component according to Iri rules
+ // a) Chars allowed by iri in a component are unescaped if found escaped
+ // b) Bidi chars are stripped
+ //
+ // should be called only if IRI parsing is switched on
+ internal unsafe string EscapeUnescapeIri(string input, int start, int end, UriComponents component)
+ {
+ fixed (char* pInput = input)
+ {
+ return IriHelper.EscapeUnescapeIri(pInput, start, end, component);
+ }
+ }
+
+ // Should never be used except by the below method
+ private Uri(Flags flags, UriParser uriParser, string uri)
+ {
+ _flags = flags;
+ _syntax = uriParser;
+ _string = uri;
+ }
+
+ //
+ // a Uri.TryCreate() method goes through here.
+ //
+ internal static Uri CreateHelper(string uriString, bool dontEscape, UriKind uriKind, ref UriFormatException e)
+ {
+ // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow
+ // to be used here.
+ if ((int)uriKind < (int)UriKind.RelativeOrAbsolute || (int)uriKind > (int)UriKind.Relative)
+ {
+ throw new ArgumentException(SR.Format(SR.net_uri_InvalidUriKind, uriKind));
+ }
+
+ UriParser syntax = null;
+ Flags flags = Flags.Zero;
+ ParsingError err = ParseScheme(uriString, ref flags, ref syntax);
+
+ if (dontEscape)
+ flags |= Flags.UserEscaped;
+
+ // We won't use User factory for these errors
+ if (err != ParsingError.None)
+ {
+ // If it looks as a relative Uri, custom factory is ignored
+ if (uriKind != UriKind.Absolute && err <= ParsingError.LastRelativeUriOkErrIndex)
+ return new Uri((flags & Flags.UserEscaped), null, uriString);
+
+ return null;
+ }
+
+ // Cannot be relative Uri if came here
+ Uri result = new Uri(flags, syntax, uriString);
+
+ // Validate instance using ether built in or a user Parser
+ try
+ {
+ result.InitializeUri(err, uriKind, out e);
+
+ if (e == null)
+ return result;
+
+ return null;
+ }
+ catch (UriFormatException ee)
+ {
+ Debug.Assert(!syntax.IsSimple, "A UriPraser threw on InitializeAndValidate.");
+ e = ee;
+ // A precaution since custom Parser should never throw in this case.
+ return null;
+ }
+ }
+
+ //
+ // Resolves into either baseUri or relativeUri according to conditions OR if not possible it uses newUriString
+ // to return combined URI strings from both Uris
+ // otherwise if e != null on output the operation has failed
+ //
+ internal static Uri ResolveHelper(Uri baseUri, Uri relativeUri, ref string newUriString, ref bool userEscaped,
+ out UriFormatException e)
+ {
+ Debug.Assert(!baseUri.IsNotAbsoluteUri && !baseUri.UserDrivenParsing, "Uri::ResolveHelper()|baseUri is not Absolute or is controlled by User Parser.");
+
+ e = null;
+ string relativeStr = string.Empty;
+
+ if ((object)relativeUri != null)
+ {
+ if (relativeUri.IsAbsoluteUri)
+ return relativeUri;
+
+ relativeStr = relativeUri.OriginalString;
+ userEscaped = relativeUri.UserEscaped;
+ }
+ else
+ relativeStr = string.Empty;
+
+ // Here we can assert that passed "relativeUri" is indeed a relative one
+
+ if (relativeStr.Length > 0 && (IsLWS(relativeStr[0]) || IsLWS(relativeStr[relativeStr.Length - 1])))
+ relativeStr = relativeStr.Trim(s_WSchars);
+
+ if (relativeStr.Length == 0)
+ {
+ newUriString = baseUri.GetParts(UriComponents.AbsoluteUri,
+ baseUri.UserEscaped ? UriFormat.UriEscaped : UriFormat.SafeUnescaped);
+ return null;
+ }
+
+ // Check for a simple fragment in relative part
+ if (relativeStr[0] == '#' && !baseUri.IsImplicitFile && baseUri.Syntax.InFact(UriSyntaxFlags.MayHaveFragment))
+ {
+ newUriString = baseUri.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment,
+ UriFormat.UriEscaped) + relativeStr;
+ return null;
+ }
+
+ // Check for a simple query in relative part
+ if (relativeStr[0] == '?' && !baseUri.IsImplicitFile && baseUri.Syntax.InFact(UriSyntaxFlags.MayHaveQuery))
+ {
+ newUriString = baseUri.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Query & ~UriComponents.Fragment,
+ UriFormat.UriEscaped) + relativeStr;
+ return null;
+ }
+
+ // Check on the DOS path in the relative Uri (a special case)
+ if (relativeStr.Length >= 3
+ && (relativeStr[1] == ':' || relativeStr[1] == '|')
+ && IsAsciiLetter(relativeStr[0])
+ && (relativeStr[2] == '\\' || relativeStr[2] == '/'))
+ {
+ if (baseUri.IsImplicitFile)
+ {
+ // It could have file:/// prepended to the result but we want to keep it as *Implicit* File Uri
+ newUriString = relativeStr;
+ return null;
+ }
+ else if (baseUri.Syntax.InFact(UriSyntaxFlags.AllowDOSPath))
+ {
+ // The scheme is not changed just the path gets replaced
+ string prefix;
+ if (baseUri.InFact(Flags.AuthorityFound))
+ prefix = baseUri.Syntax.InFact(UriSyntaxFlags.PathIsRooted) ? ":///" : "://";
+ else
+ prefix = baseUri.Syntax.InFact(UriSyntaxFlags.PathIsRooted) ? ":/" : ":";
+
+ newUriString = baseUri.Scheme + prefix + relativeStr;
+ return null;
+ }
+ // If we are here then input like "http://host/path/" + "C:\x" will produce the result http://host/path/c:/x
+ }
+
+
+ ParsingError err = GetCombinedString(baseUri, relativeStr, userEscaped, ref newUriString);
+
+ if (err != ParsingError.None)
+ {
+ e = GetException(err);
+ return null;
+ }
+
+ if ((object)newUriString == (object)baseUri._string)
+ return baseUri;
+
+ return null;
+ }
+
+ private unsafe string GetRelativeSerializationString(UriFormat format)
+ {
+ if (format == UriFormat.UriEscaped)
+ {
+ if (_string.Length == 0)
+ return string.Empty;
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(_string, 0, _string.Length, null, ref position, true,
+ c_DummyChar, c_DummyChar, '%');
+ if ((object)dest == null)
+ return _string;
+ return new string(dest, 0, position);
+ }
+
+ else if (format == UriFormat.Unescaped)
+ return UnescapeDataString(_string);
+
+ else if (format == UriFormat.SafeUnescaped)
+ {
+ if (_string.Length == 0)
+ return string.Empty;
+
+ char[] dest = new char[_string.Length];
+ int position = 0;
+ dest = UriHelper.UnescapeString(_string, 0, _string.Length, dest, ref position, c_DummyChar,
+ c_DummyChar, c_DummyChar, UnescapeMode.EscapeUnescape, null, false);
+ return new string(dest, 0, position);
+ }
+ else
+ throw new ArgumentOutOfRangeException("format");
+ }
+
+ //
+ // UriParser helpers methods
+ //
+ internal string GetComponentsHelper(UriComponents uriComponents, UriFormat uriFormat)
+ {
+ if (uriComponents == UriComponents.Scheme)
+ return _syntax.SchemeName;
+
+ // A serialzation info is "almost" the same as AbsoluteUri except for IPv6 + ScopeID hostname case
+ if ((uriComponents & UriComponents.SerializationInfoString) != 0)
+ uriComponents |= UriComponents.AbsoluteUri;
+
+ //This will get all the offsets, HostString will be created below if needed
+ EnsureParseRemaining();
+
+ if ((uriComponents & UriComponents.NormalizedHost) != 0)
+ {
+ // Down the path we rely on Host to be ON for NormalizedHost
+ uriComponents |= UriComponents.Host;
+ }
+
+ //Check to see if we need the host/authotity string
+ if ((uriComponents & UriComponents.Host) != 0)
+ EnsureHostString(true);
+
+ //This, single Port request is always processed here
+ if (uriComponents == UriComponents.Port || uriComponents == UriComponents.StrongPort)
+ {
+ if (((_flags & Flags.NotDefaultPort) != 0) || (uriComponents == UriComponents.StrongPort
+ && _syntax.DefaultPort != UriParser.NoDefaultPort))
+ {
+ // recreate string from the port value
+ return _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+ }
+ return string.Empty;
+ }
+
+ if ((uriComponents & UriComponents.StrongPort) != 0)
+ {
+ // Down the path we rely on Port to be ON for StrongPort
+ uriComponents |= UriComponents.Port;
+ }
+
+ //This request sometime is faster to process here
+ if (uriComponents == UriComponents.Host && (uriFormat == UriFormat.UriEscaped
+ || ((_flags & (Flags.HostNotCanonical | Flags.E_HostNotCanonical)) == 0)))
+ {
+ EnsureHostString(false);
+ return _info.Host;
+ }
+
+ switch (uriFormat)
+ {
+ case UriFormat.UriEscaped:
+ return GetEscapedParts(uriComponents);
+
+ case V1ToStringUnescape:
+ case UriFormat.SafeUnescaped:
+ case UriFormat.Unescaped:
+ return GetUnescapedParts(uriComponents, uriFormat);
+
+ default:
+ throw new ArgumentOutOfRangeException("uriFormat");
+ }
+ }
+
+ public bool IsBaseOf(Uri uri)
+ {
+ if ((object)uri == null)
+ throw new ArgumentNullException("uri");
+
+ if (!IsAbsoluteUri)
+ return false;
+
+ if (Syntax.IsSimple)
+ return IsBaseOfHelper(uri);
+
+ return Syntax.InternalIsBaseOf(this, uri);
+ }
+
+
+ internal bool IsBaseOfHelper(Uri uriLink)
+ {
+ if (!IsAbsoluteUri || UserDrivenParsing)
+ return false;
+
+ if (!uriLink.IsAbsoluteUri)
+ {
+ //a relative uri could have quite tricky form, it's better to fix it now.
+ string newUriString = null;
+ UriFormatException e;
+ bool dontEscape = false;
+
+ uriLink = ResolveHelper(this, uriLink, ref newUriString, ref dontEscape, out e);
+ if (e != null)
+ return false;
+
+ if ((object)uriLink == null)
+ uriLink = CreateHelper(newUriString, dontEscape, UriKind.Absolute, ref e);
+
+ if (e != null)
+ return false;
+ }
+
+ if (Syntax.SchemeName != uriLink.Syntax.SchemeName)
+ return false;
+
+ // Canonicalize and test for substring match up to the last path slash
+ string self = GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment, UriFormat.SafeUnescaped);
+ string other = uriLink.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment, UriFormat.SafeUnescaped);
+
+ unsafe
+ {
+ fixed (char* selfPtr = self)
+ {
+ fixed (char* otherPtr = other)
+ {
+ return UriHelper.TestForSubPath(selfPtr, (ushort)self.Length, otherPtr, (ushort)other.Length,
+ IsUncOrDosPath || uriLink.IsUncOrDosPath);
+ }
+ }
+ }
+ }
+
+ //
+ // Only a ctor time call
+ //
+ private void CreateThisFromUri(Uri otherUri)
+ {
+ // Clone the other guy but develop own UriInfo member
+ _info = null;
+
+ _flags = otherUri._flags;
+ if (InFact(Flags.MinimalUriInfoSet))
+ {
+ _flags &= ~(Flags.MinimalUriInfoSet | Flags.AllUriInfoSet | Flags.IndexMask);
+ // Port / Path offset
+ int portIndex = otherUri._info.Offset.Path;
+ if (InFact(Flags.NotDefaultPort))
+ {
+ // Find the start of the port. Account for non-canonical ports like :00123
+ while (otherUri._string[portIndex] != ':' && portIndex > otherUri._info.Offset.Host)
+ {
+ portIndex--;
+ }
+ if (otherUri._string[portIndex] != ':')
+ {
+ // Something wrong with the NotDefaultPort flag. Reset to path index
+ Debug.Assert(false, "Uri failed to locate custom port at index: " + portIndex);
+ portIndex = otherUri._info.Offset.Path;
+ }
+ }
+ _flags |= (Flags)portIndex; // Port or path
+ }
+
+ _syntax = otherUri._syntax;
+ _string = otherUri._string;
+ _iriParsing = otherUri._iriParsing;
+ if (otherUri.OriginalStringSwitched)
+ {
+ _originalUnicodeString = otherUri._originalUnicodeString;
+ }
+ if (otherUri.AllowIdn && (otherUri.InFact(Flags.IdnHost) || otherUri.InFact(Flags.UnicodeHost)))
+ {
+ _dnsSafeHost = otherUri._dnsSafeHost;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriFormatException.cs b/src/System.Private.Uri/src/System/UriFormatException.cs
new file mode 100644
index 0000000000..3180cbf63c
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriFormatException.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System
+{
+ /// <summary>
+ /// An exception class used when an invalid Uniform Resource Identifier is detected.
+ /// </summary>
+ public class UriFormatException : FormatException
+ {
+ public UriFormatException() : base()
+ {
+ }
+
+ public UriFormatException(string textString) : base(textString)
+ {
+ }
+
+ public UriFormatException(string textString, Exception e) : base(textString, e)
+ {
+ }
+ }; // class UriFormatException
+} // namespace System
diff --git a/src/System.Private.Uri/src/System/UriHelper.cs b/src/System.Private.Uri/src/System/UriHelper.cs
new file mode 100644
index 0000000000..fe87974ed8
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriHelper.cs
@@ -0,0 +1,712 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Globalization;
+using System.Text;
+using System.Diagnostics;
+
+namespace System
+{
+ internal static class UriHelper
+ {
+ private static readonly char[] s_hexUpperChars = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ // http://host/Path/Path/File?Query is the base of
+ // - http://host/Path/Path/File/ ... (those "File" words may be different in semantic but anyway)
+ // - http://host/Path/Path/#Fragment
+ // - http://host/Path/Path/?Query
+ // - http://host/Path/Path/MoreDir/ ...
+ // - http://host/Path/Path/OtherFile?Query
+ // - http://host/Path/Path/Fl
+ // - http://host/Path/Path/
+ //
+ // It is not a base for
+ // - http://host/Path/Path (that last "Path" is not considered as a directory)
+ // - http://host/Path/Path?Query
+ // - http://host/Path/Path#Fragment
+ // - http://host/Path/Path2/
+ // - http://host/Path/Path2/MoreDir
+ // - http://host/Path/File
+ //
+ // ASSUMES that strings like http://host/Path/Path/MoreDir/../../ have been canonicalized before going to this method.
+ // ASSUMES that back slashes already have been converted if applicable.
+ //
+ internal static unsafe bool TestForSubPath(char* selfPtr, ushort selfLength, char* otherPtr, ushort otherLength,
+ bool ignoreCase)
+ {
+ ushort i = 0;
+ char chSelf;
+ char chOther;
+
+ bool AllSameBeforeSlash = true;
+
+ for (; i < selfLength && i < otherLength; ++i)
+ {
+ chSelf = *(selfPtr + i);
+ chOther = *(otherPtr + i);
+
+ if (chSelf == '?' || chSelf == '#')
+ {
+ // survived so far and selfPtr does not have any more path segments
+ return true;
+ }
+
+ // If selfPtr terminates a path segment, so must otherPtr
+ if (chSelf == '/')
+ {
+ if (chOther != '/')
+ {
+ // comparison has falied
+ return false;
+ }
+ // plus the segments must be the same
+ if (!AllSameBeforeSlash)
+ {
+ // comparison has falied
+ return false;
+ }
+ //so far so good
+ AllSameBeforeSlash = true;
+ continue;
+ }
+
+ // if otherPtr terminates then selfPtr must not have any more path segments
+ if (chOther == '?' || chOther == '#')
+ {
+ break;
+ }
+
+ if (!ignoreCase)
+ {
+ if (chSelf != chOther)
+ {
+ AllSameBeforeSlash = false;
+ }
+ }
+ else
+ {
+ if (char.ToLowerInvariant(chSelf) != char.ToLowerInvariant(chOther))
+ {
+ AllSameBeforeSlash = false;
+ }
+ }
+ }
+
+ // If self is longer then it must not have any more path segments
+ for (; i < selfLength; ++i)
+ {
+ if ((chSelf = *(selfPtr + i)) == '?' || chSelf == '#')
+ {
+ return true;
+ }
+ if (chSelf == '/')
+ {
+ return false;
+ }
+ }
+ //survived by getting to the end of selfPtr
+ return true;
+ }
+
+ // - forceX characters are always escaped if found
+ // - rsvd character will remain unescaped
+ //
+ // start - starting offset from input
+ // end - the exclusive ending offset in input
+ // destPos - starting offset in dest for output, on return this will be an exclusive "end" in the output.
+ //
+ // In case "dest" has lack of space it will be reallocated by preserving the _whole_ content up to current destPos
+ //
+ // Returns null if nothing has to be escaped AND passed dest was null, otherwise the resulting array with the updated destPos
+ //
+ private const short c_MaxAsciiCharsReallocate = 40;
+ private const short c_MaxUnicodeCharsReallocate = 40;
+ private const short c_MaxUTF_8BytesPerUnicodeChar = 4;
+ private const short c_EncodedCharsPerByte = 3;
+ internal unsafe static char[] EscapeString(string input, int start, int end, char[] dest, ref int destPos,
+ bool isUriString, char force1, char force2, char rsvd)
+ {
+ if (end - start >= Uri.c_MaxUriBufferSize)
+ throw new UriFormatException(SR.net_uri_SizeLimit);
+
+ int i = start;
+ int prevInputPos = start;
+ byte* bytes = stackalloc byte[c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar]; // 40*4=160
+
+ fixed (char* pStr = input)
+ {
+ for (; i < end; ++i)
+ {
+ char ch = pStr[i];
+
+ // a Unicode ?
+ if (ch > '\x7F')
+ {
+ short maxSize = (short)Math.Min(end - i, (int)c_MaxUnicodeCharsReallocate - 1);
+
+ short count = 1;
+ for (; count < maxSize && pStr[i + count] > '\x7f'; ++count)
+ ;
+
+ // Is the last a high surrogate?
+ if (pStr[i + count - 1] >= 0xD800 && pStr[i + count - 1] <= 0xDBFF)
+ {
+ // Should be a rare case where the app tries to feed an invalid Unicode surrogates pair
+ if (count == 1 || count == end - i)
+ throw new UriFormatException(SR.net_uri_BadString);
+ // need to grab one more char as a Surrogate except when it's a bogus input
+ ++count;
+ }
+
+ dest = EnsureDestinationSize(pStr, dest, i,
+ (short)(count * c_MaxUTF_8BytesPerUnicodeChar * c_EncodedCharsPerByte),
+ c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar * c_EncodedCharsPerByte,
+ ref destPos, prevInputPos);
+
+ short numberOfBytes = (short)Encoding.UTF8.GetBytes(pStr + i, count, bytes,
+ c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar);
+
+ // This is the only exception that built in UriParser can throw after a Uri ctor.
+ // Should not happen unless the app tries to feed an invalid Unicode String
+ if (numberOfBytes == 0)
+ throw new UriFormatException(SR.net_uri_BadString);
+
+ i += (count - 1);
+
+ for (count = 0; count < numberOfBytes; ++count)
+ EscapeAsciiChar((char)bytes[count], dest, ref destPos);
+
+ prevInputPos = i + 1;
+ }
+ else if (ch == '%' && rsvd == '%')
+ {
+ // Means we don't reEncode '%' but check for the possible escaped sequence
+ dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte,
+ c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos);
+ if (i + 2 < end && EscapedAscii(pStr[i + 1], pStr[i + 2]) != Uri.c_DummyChar)
+ {
+ // leave it escaped
+ dest[destPos++] = '%';
+ dest[destPos++] = pStr[i + 1];
+ dest[destPos++] = pStr[i + 2];
+ i += 2;
+ }
+ else
+ {
+ EscapeAsciiChar('%', dest, ref destPos);
+ }
+ prevInputPos = i + 1;
+ }
+ else if (ch == force1 || ch == force2)
+ {
+ dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte,
+ c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos);
+ EscapeAsciiChar(ch, dest, ref destPos);
+ prevInputPos = i + 1;
+ }
+ else if (ch != rsvd && (isUriString ? !IsReservedUnreservedOrHash(ch) : !IsUnreserved(ch)))
+ {
+ dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte,
+ c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos);
+ EscapeAsciiChar(ch, dest, ref destPos);
+ prevInputPos = i + 1;
+ }
+ }
+
+ if (prevInputPos != i)
+ {
+ // need to fill up the dest array ?
+ if (prevInputPos != start || dest != null)
+ dest = EnsureDestinationSize(pStr, dest, i, 0, 0, ref destPos, prevInputPos);
+ }
+ }
+
+ return dest;
+ }
+
+ //
+ // ensure destination array has enough space and contains all the needed input stuff
+ //
+ private unsafe static char[] EnsureDestinationSize(char* pStr, char[] dest, int currentInputPos,
+ short charsToAdd, short minReallocateChars, ref int destPos, int prevInputPos)
+ {
+ if ((object)dest == null || dest.Length < destPos + (currentInputPos - prevInputPos) + charsToAdd)
+ {
+ // allocating or reallocating array by ensuring enough space based on maxCharsToAdd.
+ char[] newresult = new char[destPos + (currentInputPos - prevInputPos) + minReallocateChars];
+
+ if ((object)dest != null && destPos != 0)
+ Buffer.BlockCopy(dest, 0, newresult, 0, destPos << 1);
+ dest = newresult;
+ }
+
+ // ensuring we copied everything form the input string left before last escaping
+ while (prevInputPos != currentInputPos)
+ dest[destPos++] = pStr[prevInputPos++];
+ return dest;
+ }
+
+ //
+ // This method will assume that any good Escaped Sequence will be unescaped in the output
+ // - Assumes Dest.Length - detPosition >= end-start
+ // - UnescapeLevel controls various modes of opearion
+ // - Any "bad" escape sequence will remain as is or '%' will be escaped.
+ // - destPosition tells the starting index in dest for placing the result.
+ // On return destPosition tells the last character + 1 postion in the "dest" array.
+ // - The control chars and chars passed in rsdvX parameters may be re-escaped depending on UnescapeLevel
+ // - It is a RARE case when Unescape actually needs escaping some characteres mentioned above.
+ // For this reason it returns a char[] that is usually the same ref as the input "dest" value.
+ //
+ internal unsafe static char[] UnescapeString(string input, int start, int end, char[] dest,
+ ref int destPosition, char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser syntax,
+ bool isQuery)
+ {
+ fixed (char* pStr = input)
+ {
+ return UnescapeString(pStr, start, end, dest, ref destPosition, rsvd1, rsvd2, rsvd3, unescapeMode,
+ syntax, isQuery);
+ }
+ }
+ internal unsafe static char[] UnescapeString(char* pStr, int start, int end, char[] dest, ref int destPosition,
+ char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser syntax, bool isQuery)
+ {
+ byte[] bytes = null;
+ byte escapedReallocations = 0;
+ bool escapeReserved = false;
+ int next = start;
+ bool iriParsing = Uri.IriParsingStatic(syntax)
+ && ((unescapeMode & UnescapeMode.EscapeUnescape) == UnescapeMode.EscapeUnescape);
+
+ while (true)
+ {
+ // we may need to re-pin dest[]
+ fixed (char* pDest = dest)
+ {
+ if ((unescapeMode & UnescapeMode.EscapeUnescape) == UnescapeMode.CopyOnly)
+ {
+ while (start < end)
+ pDest[destPosition++] = pStr[start++];
+ return dest;
+ }
+
+ while (true)
+ {
+ char ch = (char)0;
+
+ for (; next < end; ++next)
+ {
+ if ((ch = pStr[next]) == '%')
+ {
+ if ((unescapeMode & UnescapeMode.Unescape) == 0)
+ {
+ // re-escape, don't check anything else
+ escapeReserved = true;
+ }
+ else if (next + 2 < end)
+ {
+ ch = EscapedAscii(pStr[next + 1], pStr[next + 2]);
+ // Unescape a good sequence if full unescape is requested
+ if (unescapeMode >= UnescapeMode.UnescapeAll)
+ {
+ if (ch == Uri.c_DummyChar)
+ {
+ if (unescapeMode >= UnescapeMode.UnescapeAllOrThrow)
+ {
+ // Should be a rare case where the app tries to feed an invalid escaped sequence
+ throw new UriFormatException(SR.net_uri_BadString);
+ }
+ continue;
+ }
+ }
+ // re-escape % from an invalid sequence
+ else if (ch == Uri.c_DummyChar)
+ {
+ if ((unescapeMode & UnescapeMode.Escape) != 0)
+ escapeReserved = true;
+ else
+ continue; // we should throw instead but since v1.0 woudl just print '%'
+ }
+ // Do not unescape '%' itself unless full unescape is requested
+ else if (ch == '%')
+ {
+ next += 2;
+ continue;
+ }
+ // Do not unescape a reserved char unless full unescape is requested
+ else if (ch == rsvd1 || ch == rsvd2 || ch == rsvd3)
+ {
+ next += 2;
+ continue;
+ }
+ // Do not unescape a dangerous char unless it's V1ToStringFlags mode
+ else if ((unescapeMode & UnescapeMode.V1ToStringFlag) == 0 && IsNotSafeForUnescape(ch))
+ {
+ next += 2;
+ continue;
+ }
+ else if (iriParsing && ((ch <= '\x9F' && IsNotSafeForUnescape(ch)) ||
+ (ch > '\x9F' && !IriHelper.CheckIriUnicodeRange(ch, isQuery))))
+ {
+ // check if unenscaping gives a char ouside iri range
+ // if it does then keep it escaped
+ next += 2;
+ continue;
+ }
+ // unescape escaped char or escape %
+ break;
+ }
+ else if (unescapeMode >= UnescapeMode.UnescapeAll)
+ {
+ if (unescapeMode >= UnescapeMode.UnescapeAllOrThrow)
+ {
+ // Should be a rare case where the app tries to feed an invalid escaped sequence
+ throw new UriFormatException(SR.net_uri_BadString);
+ }
+ // keep a '%' as part of a bogus sequence
+ continue;
+ }
+ else
+ {
+ escapeReserved = true;
+ }
+ // escape (escapeReserved==ture) or otheriwse unescape the sequence
+ break;
+ }
+ else if ((unescapeMode & (UnescapeMode.Unescape | UnescapeMode.UnescapeAll))
+ == (UnescapeMode.Unescape | UnescapeMode.UnescapeAll))
+ {
+ continue;
+ }
+ else if ((unescapeMode & UnescapeMode.Escape) != 0)
+ {
+ // Could actually escape some of the characters
+ if (ch == rsvd1 || ch == rsvd2 || ch == rsvd3)
+ {
+ // found an unescaped reserved character -> escape it
+ escapeReserved = true;
+ break;
+ }
+ else if ((unescapeMode & UnescapeMode.V1ToStringFlag) == 0
+ && (ch <= '\x1F' || (ch >= '\x7F' && ch <= '\x9F')))
+ {
+ // found an unescaped reserved character -> escape it
+ escapeReserved = true;
+ break;
+ }
+ }
+ }
+
+ //copy off previous characters from input
+ while (start < next)
+ pDest[destPosition++] = pStr[start++];
+
+ if (next != end)
+ {
+ if (escapeReserved)
+ {
+ //escape that char
+ // Since this should be _really_ rare case, reallocate with constant size increase of 30 rsvd-type characters.
+ if (escapedReallocations == 0)
+ {
+ escapedReallocations = 30;
+ char[] newDest = new char[dest.Length + escapedReallocations * 3];
+ fixed (char* pNewDest = newDest)
+ {
+ for (int i = 0; i < destPosition; ++i)
+ pNewDest[i] = pDest[i];
+ }
+ dest = newDest;
+ // re-pin new dest[] array
+ goto dest_fixed_loop_break;
+ }
+ else
+ {
+ --escapedReallocations;
+ EscapeAsciiChar(pStr[next], dest, ref destPosition);
+ escapeReserved = false;
+ start = ++next;
+ continue;
+ }
+ }
+
+ // unescaping either one Ascii or possibly multiple Unicode
+
+ if (ch <= '\x7F')
+ {
+ //ASCII
+ dest[destPosition++] = ch;
+ next += 3;
+ start = next;
+ continue;
+ }
+
+ // Unicode
+
+ int byteCount = 1;
+ // lazy initialization of max size, will reuse the array for next sequences
+ if ((object)bytes == null)
+ bytes = new byte[end - next];
+
+ bytes[0] = (byte)ch;
+ next += 3;
+ while (next < end)
+ {
+ // Check on exit criterion
+ if ((ch = pStr[next]) != '%' || next + 2 >= end)
+ break;
+
+ // already made sure we have 3 characters in str
+ ch = EscapedAscii(pStr[next + 1], pStr[next + 2]);
+
+ //invalid hex sequence ?
+ if (ch == Uri.c_DummyChar)
+ break;
+ // character is not part of a UTF-8 sequence ?
+ else if (ch < '\x80')
+ break;
+ else
+ {
+ //a UTF-8 sequence
+ bytes[byteCount++] = (byte)ch;
+ next += 3;
+ }
+ }
+
+ Encoding noFallbackCharUTF8 = Encoding.GetEncoding(
+ Encoding.UTF8.CodePage,
+ new EncoderReplacementFallback(""),
+ new DecoderReplacementFallback(""));
+
+ char[] unescapedChars = new char[bytes.Length];
+ int charCount = noFallbackCharUTF8.GetChars(bytes, 0, byteCount, unescapedChars, 0);
+
+ start = next;
+
+ // match exact bytes
+ // Do not unescape chars not allowed by Iri
+ // need to check for invalid utf sequences that may not have given any chars
+
+ MatchUTF8Sequence(pDest, dest, ref destPosition, unescapedChars, charCount, bytes,
+ byteCount, isQuery, iriParsing);
+ }
+
+ if (next == end)
+ goto done;
+ }
+ dest_fixed_loop_break:;
+ }
+ }
+
+ done: return dest;
+ }
+
+ //
+ // Need to check for invalid utf sequences that may not have given any chars.
+ // We got the unescaped chars, we then reencode them and match off the bytes
+ // to get the invalid sequence bytes that we just copy off
+ //
+ internal static unsafe void MatchUTF8Sequence(char* pDest, char[] dest, ref int destOffset, char[] unescapedChars,
+ int charCount, byte[] bytes, int byteCount, bool isQuery, bool iriParsing)
+ {
+ int count = 0;
+ fixed (char* unescapedCharsPtr = unescapedChars)
+ {
+ for (int j = 0; j < charCount; ++j)
+ {
+ bool isHighSurr = char.IsHighSurrogate(unescapedCharsPtr[j]);
+
+ byte[] encodedBytes = Encoding.UTF8.GetBytes(unescapedChars, j, isHighSurr ? 2 : 1);
+ int encodedBytesLength = encodedBytes.Length;
+
+ // we have to keep unicode chars outside Iri range escaped
+ bool inIriRange = false;
+ if (iriParsing)
+ {
+ if (!isHighSurr)
+ inIriRange = IriHelper.CheckIriUnicodeRange(unescapedChars[j], isQuery);
+ else
+ {
+ bool surrPair = false;
+ inIriRange = IriHelper.CheckIriUnicodeRange(unescapedChars[j], unescapedChars[j + 1],
+ ref surrPair, isQuery);
+ }
+ }
+
+ while (true)
+ {
+ // Escape any invalid bytes that were before this character
+ while (bytes[count] != encodedBytes[0])
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ EscapeAsciiChar((char)bytes[count++], dest, ref destOffset);
+ }
+
+ // check if all bytes match
+ bool allBytesMatch = true;
+ int k = 0;
+ for (; k < encodedBytesLength; ++k)
+ {
+ if (bytes[count + k] != encodedBytes[k])
+ {
+ allBytesMatch = false;
+ break;
+ }
+ }
+
+ if (allBytesMatch)
+ {
+ count += encodedBytesLength;
+ if (iriParsing)
+ {
+ if (!inIriRange)
+ {
+ // need to keep chars not allowed as escaped
+ for (int l = 0; l < encodedBytes.Length; ++l)
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ EscapeAsciiChar((char)encodedBytes[l], dest, ref destOffset);
+ }
+ }
+ else if (!Uri.IsBidiControlCharacter(unescapedCharsPtr[j]))
+ {
+ //copy chars
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = unescapedCharsPtr[j];
+ if (isHighSurr)
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = unescapedCharsPtr[j + 1];
+ }
+ }
+ }
+ else
+ {
+ //copy chars
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = unescapedCharsPtr[j];
+
+ if (isHighSurr)
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ pDest[destOffset++] = unescapedCharsPtr[j + 1];
+ }
+ }
+
+ break; // break out of while (true) since we've matched this char bytes
+ }
+ else
+ {
+ // copy bytes till place where bytes dont match
+ for (int l = 0; l < k; ++l)
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ EscapeAsciiChar((char)bytes[count++], dest, ref destOffset);
+ }
+ }
+ }
+
+ if (isHighSurr) j++;
+ }
+ }
+
+ // Include any trailing invalid sequences
+ while (count < byteCount)
+ {
+ Debug.Assert(dest.Length > destOffset, "Destination length exceeded destination offset.");
+ EscapeAsciiChar((char)bytes[count++], dest, ref destOffset);
+ }
+ }
+
+ internal static void EscapeAsciiChar(char ch, char[] to, ref int pos)
+ {
+ to[pos++] = '%';
+ to[pos++] = s_hexUpperChars[(ch & 0xf0) >> 4];
+ to[pos++] = s_hexUpperChars[ch & 0xf];
+ }
+
+ internal static char EscapedAscii(char digit, char next)
+ {
+ if (!(((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))))
+ {
+ return Uri.c_DummyChar;
+ }
+
+ int res = (digit <= '9')
+ ? ((int)digit - (int)'0')
+ : (((digit <= 'F')
+ ? ((int)digit - (int)'A')
+ : ((int)digit - (int)'a'))
+ + 10);
+
+ if (!(((next >= '0') && (next <= '9'))
+ || ((next >= 'A') && (next <= 'F'))
+ || ((next >= 'a') && (next <= 'f'))))
+ {
+ return Uri.c_DummyChar;
+ }
+
+ return (char)((res << 4) + ((next <= '9')
+ ? ((int)next - (int)'0')
+ : (((next <= 'F')
+ ? ((int)next - (int)'A')
+ : ((int)next - (int)'a'))
+ + 10)));
+ }
+
+ // Do not unescape these in safe mode:
+ // 1) reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
+ // 2) excluded = control | "#" | "%" | "\"
+ //
+ // That will still give plenty characters unescaped by SafeUnesced mode such as
+ // 1) Unicode characters
+ // 2) Unreserved = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ // 3) DelimitersAndUnwise = "<" | ">" | <"> | "{" | "}" | "|" | "^" | "[" | "]" | "`"
+ internal static bool IsNotSafeForUnescape(char ch)
+ {
+ if (ch <= '\x1F' || (ch >= '\x7F' && ch <= '\x9F'))
+ return true;
+ else if ((ch >= ';' && ch <= '@' && (ch | '\x2') != '>') ||
+ (ch >= '#' && ch <= '&') ||
+ ch == '+' || ch == ',' || ch == '/' || ch == '\\')
+ return true;
+
+ return false;
+ }
+
+ private const string RFC2396ReservedMarks = @";/?:@&=+$,";
+ private const string RFC3986ReservedMarks = @":/?#[]@!$&'()*+,;=";
+ private const string RFC2396UnreservedMarks = @"-_.!~*'()";
+ private const string RFC3986UnreservedMarks = @"-._~";
+
+ private static unsafe bool IsReservedUnreservedOrHash(char c)
+ {
+ if (IsUnreserved(c))
+ {
+ return true;
+ }
+ return (RFC3986ReservedMarks.IndexOf(c) >= 0);
+ }
+
+ internal static unsafe bool IsUnreserved(char c)
+ {
+ if (Uri.IsAsciiLetterOrDigit(c))
+ {
+ return true;
+ }
+ return (RFC3986UnreservedMarks.IndexOf(c) >= 0);
+ }
+
+ internal static bool Is3986Unreserved(char c)
+ {
+ if (Uri.IsAsciiLetterOrDigit(c))
+ {
+ return true;
+ }
+ return (RFC3986UnreservedMarks.IndexOf(c) >= 0);
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriHostNameType.cs b/src/System.Private.Uri/src/System/UriHostNameType.cs
new file mode 100644
index 0000000000..26a2e49b94
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriHostNameType.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System
+{
+ public enum UriHostNameType
+ {
+ Unknown,
+ Basic,
+ Dns,
+ IPv4,
+ IPv6
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriPartial.cs b/src/System.Private.Uri/src/System/UriPartial.cs
new file mode 100644
index 0000000000..4d47796ac6
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriPartial.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System
+{
+ internal enum UriPartial
+ {
+ Scheme,
+ Authority,
+ Path,
+ Query
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriScheme.cs b/src/System.Private.Uri/src/System/UriScheme.cs
new file mode 100644
index 0000000000..36d7114429
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriScheme.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Globalization;
+
+namespace System
+{
+ //
+ // The class is used as a base for custom uri parsing and derived Uri factoring.
+ // A set of protected .ctors allows to hookup on the builtin parser behaviors.
+ //
+ // A developer must implement at least internal default .ctor to participate in the Uri extensibility game.
+ //
+ internal abstract partial class UriParser
+ {
+ internal string SchemeName
+ {
+ get
+ {
+ return _scheme;
+ }
+ }
+ internal int DefaultPort
+ {
+ get
+ {
+ return _port;
+ }
+ }
+
+ private const UriSyntaxFlags SchemeOnlyFlags = UriSyntaxFlags.MayHavePath;
+ // This is a "scheme-only" base parser, everything after the scheme is
+ // returned as the path component.
+ // The user parser will need to do the majority of the work itself.
+ //
+ // However when the ctor is called from OnCreateUri context the calling parser
+ // settings will later override the result on the base class
+ //
+ protected UriParser() : this(SchemeOnlyFlags) { }
+
+ //
+ // Is called on each Uri ctor for every non-simple parser i.e. the one that does have
+ // user code.
+ //
+ protected virtual UriParser OnNewUri()
+ {
+ return this;
+ }
+
+ //
+ // Is called whenever a parser gets registered with some scheme
+ // The base implementaion is a nop.
+ //
+ protected virtual void OnRegister(string schemeName, int defaultPort)
+ {
+ }
+
+ //
+ // Parses and validates a Uri object, is called at the Uri ctor time.
+ //
+ // This method returns a non null parsingError if Uri being created is invalid:
+ //
+ protected virtual void InitializeAndValidate(Uri uri, out UriFormatException parsingError)
+ {
+ parsingError = uri.ParseMinimal();
+ }
+
+ //
+ // Resolves a relative Uri object into new AbsoluteUri.
+ //
+ // baseUri - The baseUri used to resolve this Uri.
+ // relativeuri - A relative Uri string passed by the application.
+ //
+ // This method returns:
+ // The result Uri value used to represent a new Uri
+ //
+ protected virtual string Resolve(Uri baseUri, Uri relativeUri, out UriFormatException parsingError)
+ {
+ if (baseUri.UserDrivenParsing)
+ throw new InvalidOperationException(SR.Format(SR.net_uri_UserDrivenParsing, this.GetType().ToString()));
+
+ if (!baseUri.IsAbsoluteUri)
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+
+
+ string newUriString = null;
+ bool userEscaped = false;
+ Uri result = Uri.ResolveHelper(baseUri, relativeUri, ref newUriString, ref userEscaped, out parsingError);
+
+ if (parsingError != null)
+ return null;
+
+ if (result != null)
+ return result.OriginalString;
+
+ return newUriString;
+ }
+
+ protected virtual bool IsBaseOf(Uri baseUri, Uri relativeUri)
+ {
+ return baseUri.IsBaseOfHelper(relativeUri);
+ }
+
+ //
+ // This method is invoked to allow a cutsom parser to override the
+ // internal parser when serving application with Uri componenet strings.
+ // The output format depends on the "format" parameter
+ //
+ // Parameters:
+ // uriComponents - Which components are to be retrieved.
+ // uriFormat - The requested output format.
+ //
+ // This method returns:
+ // The final result. The base impementaion could be invoked to get a suggested value
+ //
+ protected virtual string GetComponents(Uri uri, UriComponents components, UriFormat format)
+ {
+ if (((components & UriComponents.SerializationInfoString) != 0) && components != UriComponents.SerializationInfoString)
+ throw new ArgumentOutOfRangeException("components", components, SR.net_uri_NotJustSerialization);
+
+ if ((format & ~UriFormat.SafeUnescaped) != 0)
+ throw new ArgumentOutOfRangeException("format");
+
+ if (uri.UserDrivenParsing)
+ throw new InvalidOperationException(SR.Format(SR.net_uri_UserDrivenParsing, this.GetType().ToString()));
+
+ if (!uri.IsAbsoluteUri)
+ throw new InvalidOperationException(SR.net_uri_NotAbsolute);
+
+ return uri.GetComponentsHelper(components, format);
+ }
+
+ protected virtual bool IsWellFormedOriginalString(Uri uri)
+ {
+ return uri.InternalIsWellFormedOriginalString();
+ }
+
+ //
+ // Is a Uri scheme known to System.Uri?
+ //
+ public static bool IsKnownScheme(string schemeName)
+ {
+ if (schemeName == null)
+ throw new ArgumentNullException("schemeName");
+
+ if (!Uri.CheckSchemeName(schemeName))
+ throw new ArgumentOutOfRangeException("schemeName");
+
+ UriParser syntax = UriParser.GetSyntax(schemeName.ToLowerInvariant());
+ return syntax != null && syntax.NotAny(UriSyntaxFlags.V1_UnknownUri);
+ }
+ }
+}
diff --git a/src/System.Private.Uri/src/System/UriSyntax.cs b/src/System.Private.Uri/src/System/UriSyntax.cs
new file mode 100644
index 0000000000..f4353a427c
--- /dev/null
+++ b/src/System.Private.Uri/src/System/UriSyntax.cs
@@ -0,0 +1,590 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// This file utilizes partial class feature and contains
+// only internal implementation of UriParser type
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System
+{
+ // This enum specifies the Uri syntax flags that is understood by builtin Uri parser.
+ [Flags]
+ internal enum UriSyntaxFlags
+ {
+ None = 0x0,
+
+ MustHaveAuthority = 0x1, // must have "//" after scheme:
+ OptionalAuthority = 0x2, // used by generic parser due to unknown Uri syntax
+ MayHaveUserInfo = 0x4,
+ MayHavePort = 0x8,
+ MayHavePath = 0x10,
+ MayHaveQuery = 0x20,
+ MayHaveFragment = 0x40,
+
+ AllowEmptyHost = 0x80,
+ AllowUncHost = 0x100,
+ AllowDnsHost = 0x200,
+ AllowIPv4Host = 0x400,
+ AllowIPv6Host = 0x800,
+ AllowAnInternetHost = AllowDnsHost|AllowIPv4Host|AllowIPv6Host,
+ AllowAnyOtherHost = 0x1000, // Relaxed authority syntax
+
+ FileLikeUri = 0x2000, //Special case to allow file:\\balbla or file://\\balbla
+ MailToLikeUri = 0x4000, //V1 parser inheritance mailTo:AuthorityButNoSlashes
+
+ V1_UnknownUri = 0x10000, // a Compatibility with V1 parser for an unknown scheme
+ SimpleUserSyntax = 0x20000, // It is safe to not call virtual UriParser methods
+ BuiltInSyntax = 0x40000, // This is a simple Uri plus it is hardcoded in the product
+ ParserSchemeOnly = 0x80000, // This is a Parser that does only Uri scheme parsing
+
+ AllowDOSPath = 0x100000, // will check for "x:\"
+ PathIsRooted = 0x200000, // For an authority based Uri the first path char is '/'
+ ConvertPathSlashes = 0x400000, // will turn '\' into '/'
+ CompressPath = 0x800000, // For an authority based Uri remove/compress /./ /../ in the path
+ CanonicalizeAsFilePath = 0x1000000, // remove/convert sequences /.../ /x../ /x./ dangerous for a DOS path
+ UnEscapeDotsAndSlashes = 0x2000000, // additionally unescape dots and slashes before doing path compression
+ AllowIdn = 0x4000000, // IDN host conversion allowed
+ AllowIriParsing = 0x10000000, // Iri parsing. String is normalized, bidi control
+ // characters are removed, unicode char limits are checked etc.
+
+// KeepTailLWS = 0x8000000,
+ }
+
+ //
+ // Only internal members are included here
+ //
+ internal abstract partial class UriParser
+ {
+ private static readonly LowLevelDictionary<string, UriParser> s_table;
+ private static LowLevelDictionary<string, UriParser> s_tempTable;
+
+ private UriSyntaxFlags _flags;
+
+ // Some flags (specified in c_UpdatableFlags) besides being set in the ctor, can also be set at a later
+ // point. Such "updatable" flags can be set using SetUpdatableFlags(); if this method is called,
+ // the value specified in the ctor is ignored (i.e. for all c_UpdatableFlags the value in m_Flags is
+ // ignored), and the new value is used (i.e. for all c_UpdatableFlags the value in m_UpdatableFlags is used).
+ private volatile UriSyntaxFlags _updatableFlags;
+ private volatile bool _updatableFlagsUsed;
+
+ // The following flags can be updated at any time.
+ private const UriSyntaxFlags c_UpdatableFlags = UriSyntaxFlags.UnEscapeDotsAndSlashes;
+
+ private int _port;
+ private string _scheme;
+
+ internal const int NoDefaultPort = -1;
+ private const int c_InitialTableSize = 25;
+
+ // These are always available without paying hashtable lookup cost
+ // Note: see UpdateStaticSyntaxReference()
+ internal static UriParser HttpUri;
+ internal static UriParser HttpsUri;
+ internal static UriParser WsUri;
+ internal static UriParser WssUri;
+ internal static UriParser FtpUri;
+ internal static UriParser FileUri;
+ internal static UriParser GopherUri;
+ internal static UriParser NntpUri;
+ internal static UriParser NewsUri;
+ internal static UriParser MailToUri;
+ internal static UriParser UuidUri;
+ internal static UriParser TelnetUri;
+ internal static UriParser LdapUri;
+ internal static UriParser NetTcpUri;
+ internal static UriParser NetPipeUri;
+
+ internal static UriParser VsMacrosUri;
+
+
+ private enum UriQuirksVersion
+ {
+ // V1 = 1, // RFC 1738 - Not supported
+ V2 = 2, // RFC 2396
+ V3 = 3, // RFC 3986, 3987
+ }
+
+ static UriParser()
+ {
+ s_table = new LowLevelDictionary<string, UriParser>(c_InitialTableSize);
+ s_tempTable = new LowLevelDictionary<string, UriParser>(c_InitialTableSize);
+
+ //Now we will call for the instance constructors that will interrupt this static one.
+
+ // Below we simulate calls into FetchSyntax() but avoid using lock() and other things redundant for a .cctor
+
+ HttpUri = new BuiltInUriParser("http", 80, HttpSyntaxFlags);
+ s_table[HttpUri.SchemeName] = HttpUri; //HTTP
+
+ HttpsUri = new BuiltInUriParser("https", 443, HttpUri._flags);
+ s_table[HttpsUri.SchemeName] = HttpsUri; //HTTPS cloned from HTTP
+
+ WsUri = new BuiltInUriParser("ws", 80, HttpSyntaxFlags);
+ s_table[WsUri.SchemeName] = WsUri; // WebSockets
+
+ WssUri = new BuiltInUriParser("wss", 443, HttpSyntaxFlags);
+ s_table[WssUri.SchemeName] = WssUri; // Secure WebSockets
+
+ FtpUri = new BuiltInUriParser("ftp", 21, FtpSyntaxFlags);
+ s_table[FtpUri.SchemeName] = FtpUri; //FTP
+
+ FileUri = new BuiltInUriParser("file", NoDefaultPort, s_fileSyntaxFlags);
+ s_table[FileUri.SchemeName] = FileUri; //FILE
+
+ GopherUri = new BuiltInUriParser("gopher", 70, GopherSyntaxFlags);
+ s_table[GopherUri.SchemeName] = GopherUri; //GOPHER
+
+ NntpUri = new BuiltInUriParser("nntp", 119, NntpSyntaxFlags);
+ s_table[NntpUri.SchemeName] = NntpUri; //NNTP
+
+ NewsUri = new BuiltInUriParser("news", NoDefaultPort, NewsSyntaxFlags);
+ s_table[NewsUri.SchemeName] = NewsUri; //NEWS
+
+ MailToUri = new BuiltInUriParser("mailto", 25, MailtoSyntaxFlags);
+ s_table[MailToUri.SchemeName] = MailToUri; //MAILTO
+
+ UuidUri = new BuiltInUriParser("uuid", NoDefaultPort, NewsUri._flags);
+ s_table[UuidUri.SchemeName] = UuidUri; //UUID cloned from NEWS
+
+ TelnetUri = new BuiltInUriParser("telnet", 23, TelnetSyntaxFlags);
+ s_table[TelnetUri.SchemeName] = TelnetUri; //TELNET
+
+ LdapUri = new BuiltInUriParser("ldap", 389, LdapSyntaxFlags);
+ s_table[LdapUri.SchemeName] = LdapUri; //LDAP
+
+ NetTcpUri = new BuiltInUriParser("net.tcp", 808, NetTcpSyntaxFlags);
+ s_table[NetTcpUri.SchemeName] = NetTcpUri;
+
+ NetPipeUri = new BuiltInUriParser("net.pipe", NoDefaultPort, NetPipeSyntaxFlags);
+ s_table[NetPipeUri.SchemeName] = NetPipeUri;
+
+ VsMacrosUri = new BuiltInUriParser("vsmacros", NoDefaultPort, VsmacrosSyntaxFlags);
+ s_table[VsMacrosUri.SchemeName] = VsMacrosUri; //VSMACROS
+ }
+
+ private class BuiltInUriParser : UriParser
+ {
+ //
+ // All BuiltIn parsers use that ctor. They are marked with "simple" and "built-in" flags
+ //
+ internal BuiltInUriParser(string lwrCaseScheme, int defaultPort, UriSyntaxFlags syntaxFlags)
+ : base((syntaxFlags | UriSyntaxFlags.SimpleUserSyntax | UriSyntaxFlags.BuiltInSyntax))
+ {
+ _scheme = lwrCaseScheme;
+ _port = defaultPort;
+ }
+ }
+
+ internal UriSyntaxFlags Flags
+ {
+ get
+ {
+ return _flags;
+ }
+ }
+
+ internal bool NotAny(UriSyntaxFlags flags)
+ {
+ // Return true if none of the flags specified in 'flags' are set.
+ return IsFullMatch(flags, UriSyntaxFlags.None);
+ }
+
+ internal bool InFact(UriSyntaxFlags flags)
+ {
+ // Return true if at least one of the flags in 'flags' is set.
+ return !IsFullMatch(flags, UriSyntaxFlags.None);
+ }
+
+ internal bool IsAllSet(UriSyntaxFlags flags)
+ {
+ // Return true if all flags in 'flags' are set.
+ return IsFullMatch(flags, flags);
+ }
+
+ private bool IsFullMatch(UriSyntaxFlags flags, UriSyntaxFlags expected)
+ {
+ // Return true, if masking the current set of flags with 'flags' equals 'expected'.
+ // Definition 'current set of flags':
+ // a) if updatable flags were never set: m_Flags
+ // b) if updatable flags were set: set union between all flags in m_Flags which are not updatable
+ // (i.e. not part of c_UpdatableFlags) and all flags in m_UpdatableFlags
+
+ UriSyntaxFlags mergedFlags;
+
+ // if none of the flags in 'flags' is an updatable flag, we ignore m_UpdatableFlags
+ if (((flags & c_UpdatableFlags) == 0) || !_updatableFlagsUsed)
+ {
+ mergedFlags = _flags;
+ }
+ else
+ {
+ // mask m_Flags to only use the flags not in c_UpdatableFlags
+ mergedFlags = (_flags & (~c_UpdatableFlags)) | _updatableFlags;
+ }
+
+ return (mergedFlags & flags) == expected;
+ }
+
+ //
+ // Internal .ctor, any ctor eventually goes through this one
+ //
+ internal UriParser(UriSyntaxFlags flags)
+ {
+ _flags = flags;
+ _scheme = string.Empty;
+ }
+
+ private static void FetchSyntax(UriParser syntax, string lwrCaseSchemeName, int defaultPort)
+ {
+ if (syntax.SchemeName.Length != 0)
+ throw new InvalidOperationException(SR.Format(SR.net_uri_NeedFreshParser, syntax.SchemeName));
+
+ lock (s_table)
+ {
+ syntax._flags &= ~UriSyntaxFlags.V1_UnknownUri;
+ UriParser oldSyntax = null;
+ s_table.TryGetValue(lwrCaseSchemeName, out oldSyntax);
+ if (oldSyntax != null)
+ throw new InvalidOperationException(SR.Format(SR.net_uri_AlreadyRegistered, oldSyntax.SchemeName));
+
+ s_tempTable.TryGetValue(syntax.SchemeName, out oldSyntax);
+ if (oldSyntax != null)
+ {
+ // optimization on schemeName, will try to keep the first reference
+ lwrCaseSchemeName = oldSyntax._scheme;
+ s_tempTable.Remove(lwrCaseSchemeName);
+ }
+
+ syntax.OnRegister(lwrCaseSchemeName, defaultPort);
+ syntax._scheme = lwrCaseSchemeName;
+ syntax._port = defaultPort;
+
+ s_table[syntax.SchemeName] = syntax;
+ }
+ }
+
+ private const int c_MaxCapacity = 512;
+ //schemeStr must be in lower case!
+ internal static UriParser FindOrFetchAsUnknownV1Syntax(string lwrCaseScheme)
+ {
+ // check may be other thread just added one
+ UriParser syntax = null;
+ s_table.TryGetValue(lwrCaseScheme, out syntax);
+ if (syntax != null)
+ {
+ return syntax;
+ }
+ s_tempTable.TryGetValue(lwrCaseScheme, out syntax);
+ if (syntax != null)
+ {
+ return syntax;
+ }
+ lock (s_table)
+ {
+ if (s_tempTable.Count >= c_MaxCapacity)
+ {
+ s_tempTable = new LowLevelDictionary<string, UriParser>(c_InitialTableSize);
+ }
+ syntax = new BuiltInUriParser(lwrCaseScheme, NoDefaultPort, UnknownV1SyntaxFlags);
+ s_tempTable[lwrCaseScheme] = syntax;
+ return syntax;
+ }
+ }
+
+ internal static UriParser GetSyntax(string lwrCaseScheme)
+ {
+ UriParser ret = null;
+ s_table.TryGetValue(lwrCaseScheme, out ret);
+ if (ret == null)
+ {
+ s_tempTable.TryGetValue(lwrCaseScheme, out ret);
+ }
+ return ret;
+ }
+
+ //
+ // Builtin and User Simple syntaxes do not need custom validation/parsing (i.e. virtual method calls),
+ //
+ internal bool IsSimple
+ {
+ get
+ {
+ return InFact(UriSyntaxFlags.SimpleUserSyntax);
+ }
+ }
+
+ //
+ // This method is used to update flags. The scenario where this is needed is when the user specifies
+ // flags in the config file. The config file is read after UriParser instances were created.
+ //
+ internal void SetUpdatableFlags(UriSyntaxFlags flags)
+ {
+ Debug.Assert(!_updatableFlagsUsed,
+ "SetUpdatableFlags() already called. It can only be called once per parser.");
+ Debug.Assert((flags & (~c_UpdatableFlags)) == 0, "Only updatable flags can be set.");
+
+ // No locks necessary. Reordering won't happen due to volatile.
+ _updatableFlags = flags;
+ _updatableFlagsUsed = true;
+ }
+
+ //
+ // These are simple internal wrappers that will call virtual protected methods
+ // (to avoid "protected internal" siganures in the public docs)
+ //
+ internal UriParser InternalOnNewUri()
+ {
+ UriParser effectiveParser = OnNewUri();
+ if ((object)this != (object)effectiveParser)
+ {
+ effectiveParser._scheme = _scheme;
+ effectiveParser._port = _port;
+ effectiveParser._flags = _flags;
+ }
+ return effectiveParser;
+ }
+
+ internal void InternalValidate(Uri thisUri, out UriFormatException parsingError)
+ {
+ InitializeAndValidate(thisUri, out parsingError);
+ }
+
+ internal string InternalResolve(Uri thisBaseUri, Uri uriLink, out UriFormatException parsingError)
+ {
+ return Resolve(thisBaseUri, uriLink, out parsingError);
+ }
+
+ internal bool InternalIsBaseOf(Uri thisBaseUri, Uri uriLink)
+ {
+ return IsBaseOf(thisBaseUri, uriLink);
+ }
+
+ internal string InternalGetComponents(Uri thisUri, UriComponents uriComponents, UriFormat uriFormat)
+ {
+ return GetComponents(thisUri, uriComponents, uriFormat);
+ }
+
+ internal bool InternalIsWellFormedOriginalString(Uri thisUri)
+ {
+ return IsWellFormedOriginalString(thisUri);
+ }
+
+ //
+ // Various Uri scheme syntax flags
+ //
+ private const UriSyntaxFlags UnknownV1SyntaxFlags =
+ UriSyntaxFlags.V1_UnknownUri | // This flag must be always set here
+ UriSyntaxFlags.OptionalAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ // UriSyntaxFlags.AllowAnyOtherHost | // V1.1 has a bug and so does not support this case
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowDOSPath | // V1 compat, actually we should not parse DOS file out of an unknown scheme
+ UriSyntaxFlags.ConvertPathSlashes | // V1 compat, it will always convert backslashes
+ UriSyntaxFlags.CompressPath | // V1 compat, it will always compress path even for non hierarchical Uris
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags HttpSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ //
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags FtpSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ //
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private static readonly UriSyntaxFlags s_fileSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost |
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.MayHaveQuery |
+ //
+ UriSyntaxFlags.FileLikeUri |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowDOSPath |
+ //
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.UnEscapeDotsAndSlashes |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags VsmacrosSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost |
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.FileLikeUri |
+ //
+ UriSyntaxFlags.AllowDOSPath |
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.UnEscapeDotsAndSlashes |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags GopherSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ // UriSyntaxFlags.KeepTailLWS |
+
+ //Note that NNTP and NEWS are quite different in syntax
+ private const UriSyntaxFlags NewsSyntaxFlags =
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags NntpSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags TelnetSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags LdapSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags MailtoSyntaxFlags =
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost | // V1 compat
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.MayHaveQuery | //to maintain compat
+ //
+ UriSyntaxFlags.MailToLikeUri |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+
+ private const UriSyntaxFlags NetPipeSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.AllowAnInternetHost |
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.UnEscapeDotsAndSlashes |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags NetTcpSyntaxFlags = NetPipeSyntaxFlags | UriSyntaxFlags.MayHavePort;
+ }
+}
diff --git a/src/System.Private.Uri/src/project.json b/src/System.Private.Uri/src/project.json
new file mode 100644
index 0000000000..54912e4a28
--- /dev/null
+++ b/src/System.Private.Uri/src/project.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "Microsoft.DotNet.CoreCLR": "1.0.0-prerelease"
+ }
+} \ No newline at end of file
diff --git a/src/System.Runtime/System.Runtime.sln b/src/System.Runtime/System.Runtime.sln
index acf7ef700f..f6a264062a 100644
--- a/src/System.Runtime/System.Runtime.sln
+++ b/src/System.Runtime/System.Runtime.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.22129.1
+VisualStudioVersion = 14.0.22817.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.Tests", "tests\System.Runtime.Tests.csproj", "{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}"
EndProject
@@ -11,16 +11,44 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{1CA59C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XunitTraitsDiscoverers", "..\Common\tests\XunitTraitsDiscoverers\XunitTraitsDiscoverers.csproj", "{BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.CoreCLR", "src\System.Runtime.CoreCLR.csproj", "{56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Private.Uri.CoreCLR", "..\System.Private.Uri\src\System.Private.Uri.CoreCLR.csproj", "{4AC5343E-6E31-4BA5-A795-0493AE7E9008}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
+ Linux_Debug|Any CPU = Linux_Debug|Any CPU
+ Linux_Debug|ARM = Linux_Debug|ARM
+ Linux_Debug|x64 = Linux_Debug|x64
+ Linux_Debug|x86 = Linux_Debug|x86
+ Linux_Release|Any CPU = Linux_Release|Any CPU
+ Linux_Release|ARM = Linux_Release|ARM
+ Linux_Release|x64 = Linux_Release|x64
+ Linux_Release|x86 = Linux_Release|x86
+ OSX_Debug|Any CPU = OSX_Debug|Any CPU
+ OSX_Debug|ARM = OSX_Debug|ARM
+ OSX_Debug|x64 = OSX_Debug|x64
+ OSX_Debug|x86 = OSX_Debug|x86
+ OSX_Release|Any CPU = OSX_Release|Any CPU
+ OSX_Release|ARM = OSX_Release|ARM
+ OSX_Release|x64 = OSX_Release|x64
+ OSX_Release|x86 = OSX_Release|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
+ Windows_Debug|Any CPU = Windows_Debug|Any CPU
+ Windows_Debug|ARM = Windows_Debug|ARM
+ Windows_Debug|x64 = Windows_Debug|x64
+ Windows_Debug|x86 = Windows_Debug|x86
+ Windows_Release|Any CPU = Windows_Release|Any CPU
+ Windows_Release|ARM = Windows_Release|ARM
+ Windows_Release|x64 = Windows_Release|x64
+ Windows_Release|x86 = Windows_Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -28,11 +56,251 @@ Global
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Debug|x64.ActiveCfg = Debug|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|ARM.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|ARM.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|x64.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|x64.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|x86.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Debug|x86.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|Any CPU.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|ARM.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|ARM.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|x64.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|x64.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|x86.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Linux_Release|x86.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|ARM.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|ARM.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|x64.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|x64.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|x86.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Debug|x86.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|Any CPU.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|ARM.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|ARM.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|x64.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|x64.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|x86.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.OSX_Release|x86.Build.0 = Release|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Release|Any CPU.Build.0 = Release|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Release|ARM.ActiveCfg = Release|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Release|x64.ActiveCfg = Release|Any CPU
{7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Release|x86.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|ARM.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|ARM.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|x64.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|x64.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|x86.ActiveCfg = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Debug|x86.Build.0 = Debug|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|Any CPU.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|ARM.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|ARM.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|x64.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|x64.Build.0 = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|x86.ActiveCfg = Release|Any CPU
+ {7F5F5134-00FE-4DE8-B20C-3DA8BA2EBA68}.Windows_Release|x86.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|ARM.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|x64.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Debug|x86.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|ARM.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|ARM.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|x64.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|x64.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|x86.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Debug|x86.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|Any CPU.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|ARM.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|ARM.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|x64.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|x64.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|x86.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Linux_Release|x86.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|ARM.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|ARM.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|x64.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|x64.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|x86.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Debug|x86.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|Any CPU.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|ARM.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|ARM.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|x64.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|x64.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|x86.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.OSX_Release|x86.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|ARM.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|ARM.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|x64.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|x64.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|x86.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Release|x86.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|ARM.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|ARM.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|x64.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|x64.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|x86.ActiveCfg = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Debug|x86.Build.0 = Debug|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|Any CPU.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|ARM.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|ARM.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|x64.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|x64.Build.0 = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|x86.ActiveCfg = Release|Any CPU
+ {BE8ED8C1-C314-4C4E-A929-64C9C8B3552A}.Windows_Release|x86.Build.0 = Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|Any CPU.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|ARM.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|ARM.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|x64.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|x64.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|x86.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Debug|x86.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|Any CPU.ActiveCfg = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|Any CPU.Build.0 = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|ARM.ActiveCfg = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|ARM.Build.0 = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|x64.ActiveCfg = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|x64.Build.0 = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|x86.ActiveCfg = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Debug|x86.Build.0 = Linux_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|Any CPU.ActiveCfg = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|Any CPU.Build.0 = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|ARM.ActiveCfg = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|ARM.Build.0 = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|x64.ActiveCfg = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|x64.Build.0 = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|x86.ActiveCfg = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Linux_Release|x86.Build.0 = Linux_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|Any CPU.ActiveCfg = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|Any CPU.Build.0 = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|ARM.ActiveCfg = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|ARM.Build.0 = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|x64.ActiveCfg = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|x64.Build.0 = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|x86.ActiveCfg = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Debug|x86.Build.0 = OSX_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|Any CPU.ActiveCfg = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|Any CPU.Build.0 = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|ARM.ActiveCfg = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|ARM.Build.0 = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|x64.ActiveCfg = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|x64.Build.0 = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|x86.ActiveCfg = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.OSX_Release|x86.Build.0 = OSX_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|Any CPU.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|Any CPU.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|ARM.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|ARM.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|x64.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|x64.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|x86.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Release|x86.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|Any CPU.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|ARM.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|ARM.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|x64.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|x64.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|x86.ActiveCfg = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Debug|x86.Build.0 = Windows_Debug|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|Any CPU.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|Any CPU.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|ARM.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|ARM.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|x64.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|x64.Build.0 = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|x86.ActiveCfg = Windows_Release|Any CPU
+ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2}.Windows_Release|x86.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|Any CPU.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|ARM.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|ARM.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|x64.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|x64.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|x86.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|x86.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|Any CPU.ActiveCfg = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|Any CPU.Build.0 = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|ARM.ActiveCfg = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|ARM.Build.0 = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|x64.ActiveCfg = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|x64.Build.0 = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|x86.ActiveCfg = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Debug|x86.Build.0 = Linux_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|Any CPU.ActiveCfg = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|Any CPU.Build.0 = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|ARM.ActiveCfg = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|ARM.Build.0 = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|x64.ActiveCfg = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|x64.Build.0 = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|x86.ActiveCfg = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Linux_Release|x86.Build.0 = Linux_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|Any CPU.ActiveCfg = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|Any CPU.Build.0 = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|ARM.ActiveCfg = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|ARM.Build.0 = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|x64.ActiveCfg = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|x64.Build.0 = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|x86.ActiveCfg = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Debug|x86.Build.0 = OSX_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|Any CPU.ActiveCfg = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|Any CPU.Build.0 = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|ARM.ActiveCfg = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|ARM.Build.0 = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|x64.ActiveCfg = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|x64.Build.0 = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|x86.ActiveCfg = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.OSX_Release|x86.Build.0 = OSX_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|Any CPU.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|Any CPU.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|ARM.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|ARM.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|x64.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|x64.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|x86.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|x86.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|Any CPU.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|ARM.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|ARM.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|x64.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|x64.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|x86.ActiveCfg = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Debug|x86.Build.0 = Windows_Debug|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|Any CPU.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|Any CPU.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|ARM.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|ARM.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|x64.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|x64.Build.0 = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|x86.ActiveCfg = Windows_Release|Any CPU
+ {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Windows_Release|x86.Build.0 = Windows_Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/System.Runtime/src/System.Runtime.CoreCLR.csproj b/src/System.Runtime/src/System.Runtime.CoreCLR.csproj
new file mode 100644
index 0000000000..21b7734d5d
--- /dev/null
+++ b/src/System.Runtime/src/System.Runtime.CoreCLR.csproj
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+
+ <PropertyGroup>
+ <AssemblyName>System.Runtime</AssemblyName>
+ <AssemblyVersion>4.0.20.0</AssemblyVersion>
+ <OutputType>Library</OutputType>
+ <IsPartialFacadeAssembly>true</IsPartialFacadeAssembly>
+ <NuGetTargetFrameworkMoniker>ASP.NetCore, version=v5.0</NuGetTargetFrameworkMoniker>
+ </PropertyGroup>
+
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Debug|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Release|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Debug|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Release|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Debug|AnyCPU' " />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Release|AnyCPU' " />
+
+ <ItemGroup>
+ <Compile Include="System\Action.cs" />
+ <Compile Include="System\Function.cs" />
+ <Compile Include="System\LazyOfTTMetadata.cs" />
+ <Compile Include="System\Collections\Generic\ISet.cs" />
+ <Compile Include="System\ComponentModel\DefaultValueAttribute.cs" />
+ <Compile Include="System\ComponentModel\EditorBrowsableAttribute.cs" />
+ <Compile Include="System\Runtime\CompilerServices\StrongBox.cs" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\System.Private.Uri\src\System.Private.Uri.CoreCLR.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Include="project.json" />
+ </ItemGroup>
+
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project> \ No newline at end of file
diff --git a/src/System.Runtime/src/System/Action.cs b/src/System.Runtime/src/System/Action.cs
new file mode 100644
index 0000000000..af286bc60a
--- /dev/null
+++ b/src/System.Runtime/src/System/Action.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
+}
diff --git a/src/System.Runtime/src/System/Collections/Generic/ISet.cs b/src/System.Runtime/src/System/Collections/Generic/ISet.cs
new file mode 100644
index 0000000000..2b57e5d483
--- /dev/null
+++ b/src/System.Runtime/src/System/Collections/Generic/ISet.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ /// <summary>
+ /// Generic collection that guarantees the uniqueness of its elements, as defined
+ /// by some comparer. It also supports basic set operations such as Union, Intersection,
+ /// Complement and Exclusive Complement.
+ /// </summary>
+ public interface ISet<T> : ICollection<T>
+ {
+ //Add ITEM to the set, return true if added, false if duplicate
+ new bool Add(T item);
+
+ //Transform this set into its union with the IEnumerable<T> other
+ void UnionWith(IEnumerable<T> other);
+
+ //Transform this set into its intersection with the IEnumberable<T> other
+ void IntersectWith(IEnumerable<T> other);
+
+ //Transform this set so it contains no elements that are also in other
+ void ExceptWith(IEnumerable<T> other);
+
+ //Transform this set so it contains elements initially in this or in other, but not both
+ void SymmetricExceptWith(IEnumerable<T> other);
+
+ //Check if this set is a subset of other
+ bool IsSubsetOf(IEnumerable<T> other);
+
+ //Check if this set is a superset of other
+ bool IsSupersetOf(IEnumerable<T> other);
+
+ //Check if this set is a subset of other, but not the same as it
+ bool IsProperSupersetOf(IEnumerable<T> other);
+
+ //Check if this set is a superset of other, but not the same as it
+ bool IsProperSubsetOf(IEnumerable<T> other);
+
+ //Check if this set has any elements in common with other
+ bool Overlaps(IEnumerable<T> other);
+
+ //Check if this set contains the same and only the same elements as other
+ bool SetEquals(IEnumerable<T> other);
+ }
+}
diff --git a/src/System.Runtime/src/System/ComponentModel/DefaultValueAttribute.cs b/src/System.Runtime/src/System/ComponentModel/DefaultValueAttribute.cs
new file mode 100644
index 0000000000..d9e1454fba
--- /dev/null
+++ b/src/System.Runtime/src/System/ComponentModel/DefaultValueAttribute.cs
@@ -0,0 +1,183 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System.ComponentModel
+{
+ /// <devdoc>
+ /// <para>Specifies the default value for a property.</para>
+ /// </devdoc>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")]
+ [AttributeUsage(AttributeTargets.All)]
+ public class DefaultValueAttribute : Attribute
+ {
+ /// <devdoc>
+ /// This is the default value.
+ /// </devdoc>
+ private object value;
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class, converting the
+ /// specified value to the
+ /// specified type, and using the U.S. English culture as the
+ /// translation
+ /// context.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(Type type, string value)
+ {
+ // The try/catch here is because attributes should never throw exceptions. We would fail to
+ // load an otherwise normal class.
+ try
+ {
+ if (type.IsSubclassOf(typeof(Enum)))
+ {
+ this.value = Enum.Parse(type, value, true);
+ }
+ else if (type == typeof(TimeSpan))
+ {
+ this.value = TimeSpan.Parse(value);
+ }
+ else
+ {
+ this.value = Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
+ }
+ }
+ catch
+ {
+ //Debug.WriteLine("Default value attribute of type " + type.ToString() + " threw converting from the string '" + value + "'.");
+ }
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a Unicode
+ /// character.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(char value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using an 8-bit unsigned
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(byte value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a 16-bit signed
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(short value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a 32-bit signed
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(int value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a 64-bit signed
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(long value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a
+ /// single-precision floating point
+ /// number.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(float value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a
+ /// double-precision floating point
+ /// number.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(double value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.Boolean'/>
+ /// value.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(bool value)
+ {
+ this.value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.String'/>.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(string value)
+ {
+ this.value = value;
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/>
+ /// class.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(object value)
+ {
+ this.value = value;
+ }
+
+ /// <devdoc>
+ /// <para>
+ /// Gets the default value of the property this
+ /// attribute is
+ /// bound to.
+ /// </para>
+ /// </devdoc>
+ public virtual object Value
+ {
+ get
+ {
+ return value;
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == this)
+ {
+ return true;
+ }
+
+ DefaultValueAttribute other = obj as DefaultValueAttribute;
+
+ if (other != null)
+ {
+ if (Value != null)
+ {
+ return Value.Equals(other.Value);
+ }
+ else
+ {
+ return (other.Value == null);
+ }
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+}
diff --git a/src/System.Runtime/src/System/ComponentModel/EditorBrowsableAttribute.cs b/src/System.Runtime/src/System/ComponentModel/EditorBrowsableAttribute.cs
new file mode 100644
index 0000000000..c8efb09a96
--- /dev/null
+++ b/src/System.Runtime/src/System/ComponentModel/EditorBrowsableAttribute.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.ComponentModel
+{
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate | AttributeTargets.Interface)]
+ public sealed class EditorBrowsableAttribute : Attribute
+ {
+ private EditorBrowsableState browsableState;
+
+
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ public EditorBrowsableAttribute(EditorBrowsableState state)
+ {
+ browsableState = state;
+ }
+
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ public EditorBrowsableState State
+ {
+ get { return browsableState; }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == this)
+ {
+ return true;
+ }
+
+ EditorBrowsableAttribute other = obj as EditorBrowsableAttribute;
+
+ return (other != null) && other.browsableState == browsableState;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ public enum EditorBrowsableState
+ {
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ Always,
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ Never,
+ /// <devdoc>
+ /// <para>[To be supplied.]</para>
+ /// </devdoc>
+ Advanced
+ }
+}
diff --git a/src/System.Runtime/src/System/Function.cs b/src/System.Runtime/src/System/Function.cs
new file mode 100644
index 0000000000..3ea8418fd7
--- /dev/null
+++ b/src/System.Runtime/src/System/Function.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
+}
diff --git a/src/System.Runtime/src/System/LazyOfTTMetadata.cs b/src/System.Runtime/src/System/LazyOfTTMetadata.cs
new file mode 100644
index 0000000000..d283d60467
--- /dev/null
+++ b/src/System.Runtime/src/System/LazyOfTTMetadata.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace System
+{
+ public class Lazy<T, TMetadata> : Lazy<T>
+ {
+ private readonly TMetadata _metadata;
+
+ public Lazy(Func<T> valueFactory, TMetadata metadata) :
+ base(valueFactory)
+ {
+ _metadata = metadata;
+ }
+
+ public Lazy(TMetadata metadata) :
+ base()
+ {
+ _metadata = metadata;
+ }
+
+
+ public Lazy(TMetadata metadata, bool isThreadSafe) :
+ base(isThreadSafe)
+ {
+ _metadata = metadata;
+ }
+
+ public Lazy(Func<T> valueFactory, TMetadata metadata, bool isThreadSafe) :
+ base(valueFactory, isThreadSafe)
+ {
+ _metadata = metadata;
+ }
+
+ public Lazy(TMetadata metadata, LazyThreadSafetyMode mode) :
+ base(mode)
+ {
+ _metadata = metadata;
+ }
+
+ public Lazy(Func<T> valueFactory, TMetadata metadata, LazyThreadSafetyMode mode) :
+ base(valueFactory, mode)
+ {
+ _metadata = metadata;
+ }
+
+ public TMetadata Metadata
+ {
+ get
+ {
+ return _metadata;
+ }
+ }
+ }
+}
diff --git a/src/System.Runtime/src/System/Runtime/CompilerServices/StrongBox.cs b/src/System.Runtime/src/System/Runtime/CompilerServices/StrongBox.cs
new file mode 100644
index 0000000000..e04c697c14
--- /dev/null
+++ b/src/System.Runtime/src/System/Runtime/CompilerServices/StrongBox.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Holds a reference to a value.
+ /// </summary>
+ /// <typeparam name="T">The type of the value that the <see cref = "StrongBox{T}"></see> references.</typeparam>
+ public class StrongBox<T> : IStrongBox
+ {
+ /// <summary>
+ /// Gets the strongly typed value associated with the <see cref = "StrongBox{T}"></see>
+ /// <remarks>This is explicitly exposed as a field instead of a property to enable loading the address of the field.</remarks>
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
+ public T Value;
+
+ /// <summary>
+ /// Initializes a new StrongBox which can receive a value when used in a reference call.
+ /// </summary>
+ public StrongBox()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref = "StrongBox{T}"></see> with the specified value.
+ /// </summary>
+ /// <param name="value">A value that the <see cref = "StrongBox{T}"></see> will reference.</param>
+ public StrongBox(T value)
+ {
+ Value = value;
+ }
+
+ object IStrongBox.Value
+ {
+ get
+ {
+ return Value;
+ }
+ set
+ {
+ Value = (T)value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Defines a property for accessing the value that an object references.
+ /// </summary>
+ public interface IStrongBox
+ {
+ /// <summary>
+ /// Gets or sets the value the object references.
+ /// </summary>
+ object Value { get; set; }
+ }
+}
diff --git a/src/System.Runtime/src/project.json b/src/System.Runtime/src/project.json
new file mode 100644
index 0000000000..54912e4a28
--- /dev/null
+++ b/src/System.Runtime/src/project.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "Microsoft.DotNet.CoreCLR": "1.0.0-prerelease"
+ }
+} \ No newline at end of file
diff --git a/src/System.Runtime/tests/System.Runtime.Tests.csproj b/src/System.Runtime/tests/System.Runtime.Tests.csproj
index 6fa2987580..a668e4967e 100644
--- a/src/System.Runtime/tests/System.Runtime.Tests.csproj
+++ b/src/System.Runtime/tests/System.Runtime.Tests.csproj
@@ -94,6 +94,12 @@
<Project>{be8ed8c1-c314-4c4e-a929-64c9c8b3552a}</Project>
<Name>XunitTraitsDiscoverers</Name>
</ProjectReference>
+ <!-- Compile tests against the System.Runtime contract, but copy our local-built implementation for testing -->
+ <ProjectReference Include="..\src\System.Runtime.CoreCLR.csproj">
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ <OutputItemType>Content</OutputItemType>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </ProjectReference>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project> \ No newline at end of file