|  |  | 1 |  | // Copyright (c) Microsoft Corporation. All rights reserved. | 
|  |  | 2 |  | // Licensed under the MIT License. | 
|  |  | 3 |  |  | 
|  |  | 4 |  | #nullable enable | 
|  |  | 5 |  |  | 
|  |  | 6 |  | using System; | 
|  |  | 7 |  | using System.Globalization; | 
|  |  | 8 |  |  | 
|  |  | 9 |  | namespace Azure.Core | 
|  |  | 10 |  | { | 
|  |  | 11 |  |     internal class RawRequestUriBuilder: RequestUriBuilder | 
|  |  | 12 |  |     { | 
|  |  | 13 |  |         private const string SchemeSeparator = "://"; | 
|  |  | 14 |  |         private const char HostSeparator = '/'; | 
|  |  | 15 |  |         private const char PortSeparator = ':'; | 
|  | 0 | 16 |  |         private static readonly char[] HostOrPort = { HostSeparator, PortSeparator }; | 
|  |  | 17 |  |         private const char QueryBeginSeparator = '?'; | 
|  |  | 18 |  |         private const char QueryContinueSeparator = '&'; | 
|  |  | 19 |  |         private const char QueryValueSeparator = '='; | 
|  |  | 20 |  |  | 
|  |  | 21 |  |         private RawWritingPosition? _position; | 
|  |  | 22 |  |  | 
|  |  | 23 |  |         private static (string Name, string Value) GetQueryParts(string queryUnparsed) | 
|  |  | 24 |  |         { | 
|  | 0 | 25 |  |             int separatorIndex = queryUnparsed.IndexOf(QueryValueSeparator); | 
|  | 0 | 26 |  |             if (separatorIndex == -1) | 
|  |  | 27 |  |             { | 
|  | 0 | 28 |  |                 return (queryUnparsed, string.Empty); | 
|  |  | 29 |  |             } | 
|  | 0 | 30 |  |             return (queryUnparsed.Substring(0, separatorIndex), queryUnparsed.Substring(separatorIndex + 1)); | 
|  |  | 31 |  |         } | 
|  |  | 32 |  |  | 
|  |  | 33 |  |         public void AppendRaw(string value, bool escape) | 
|  |  | 34 |  |         { | 
|  | 0 | 35 |  |             if (_position == null) | 
|  |  | 36 |  |             { | 
|  | 0 | 37 |  |                 if (!string.IsNullOrEmpty(Query)) | 
|  |  | 38 |  |                 { | 
|  | 0 | 39 |  |                     _position = RawWritingPosition.Query; | 
|  |  | 40 |  |                 } | 
|  | 0 | 41 |  |                 else if (!string.IsNullOrEmpty(Path)) | 
|  |  | 42 |  |                 { | 
|  | 0 | 43 |  |                     _position = RawWritingPosition.Path; | 
|  |  | 44 |  |                 } | 
|  | 0 | 45 |  |                 else if (!string.IsNullOrEmpty(Host)) | 
|  |  | 46 |  |                 { | 
|  | 0 | 47 |  |                     _position = RawWritingPosition.Host; | 
|  |  | 48 |  |                 } | 
|  |  | 49 |  |                 else | 
|  |  | 50 |  |                 { | 
|  | 0 | 51 |  |                     _position = RawWritingPosition.Scheme; | 
|  |  | 52 |  |                 } | 
|  |  | 53 |  |             } | 
|  | 0 | 54 |  |             while (!string.IsNullOrWhiteSpace(value)) | 
|  |  | 55 |  |             { | 
|  | 0 | 56 |  |                 if (_position == RawWritingPosition.Scheme) | 
|  |  | 57 |  |                 { | 
|  | 0 | 58 |  |                     int separator = value.IndexOf(SchemeSeparator, StringComparison.InvariantCultureIgnoreCase); | 
|  | 0 | 59 |  |                     if (separator == -1) | 
|  |  | 60 |  |                     { | 
|  | 0 | 61 |  |                         Scheme += value; | 
|  | 0 | 62 |  |                         value = string.Empty; | 
|  |  | 63 |  |                     } | 
|  |  | 64 |  |                     else | 
|  |  | 65 |  |                     { | 
|  | 0 | 66 |  |                         Scheme += value.Substring(0, separator); | 
|  |  | 67 |  |                         // TODO: Find a better way to map schemes to default ports | 
|  | 0 | 68 |  |                         Port = string.Equals(Scheme, "https", StringComparison.OrdinalIgnoreCase) ? 443 : 80; | 
|  | 0 | 69 |  |                         value = value.Substring(separator + SchemeSeparator.Length); | 
|  | 0 | 70 |  |                         _position = RawWritingPosition.Host; | 
|  |  | 71 |  |                     } | 
|  |  | 72 |  |                 } | 
|  | 0 | 73 |  |                 else if (_position == RawWritingPosition.Host) | 
|  |  | 74 |  |                 { | 
|  | 0 | 75 |  |                     int separator = value.IndexOfAny(HostOrPort); | 
|  | 0 | 76 |  |                     if (separator == -1) | 
|  |  | 77 |  |                     { | 
|  | 0 | 78 |  |                         if (string.IsNullOrEmpty(Path)) | 
|  |  | 79 |  |                         { | 
|  | 0 | 80 |  |                             Host += value; | 
|  | 0 | 81 |  |                             value = string.Empty; | 
|  |  | 82 |  |                         } | 
|  |  | 83 |  |                         else | 
|  |  | 84 |  |                         { | 
|  |  | 85 |  |                             // All Host information must be written before Path information | 
|  |  | 86 |  |                             // If Path already has information, we transition to writing Path | 
|  | 0 | 87 |  |                             _position = RawWritingPosition.Path; | 
|  |  | 88 |  |                         } | 
|  |  | 89 |  |                     } | 
|  |  | 90 |  |                     else | 
|  |  | 91 |  |                     { | 
|  | 0 | 92 |  |                         Host += value.Substring(0, separator); | 
|  | 0 | 93 |  |                         _position = value[separator] == HostSeparator ? RawWritingPosition.Path : RawWritingPosition.Por | 
|  | 0 | 94 |  |                         value = value.Substring(separator + 1); | 
|  |  | 95 |  |                     } | 
|  |  | 96 |  |                 } | 
|  | 0 | 97 |  |                 else if (_position == RawWritingPosition.Port) | 
|  |  | 98 |  |                 { | 
|  | 0 | 99 |  |                     int separator = value.IndexOf(HostSeparator); | 
|  | 0 | 100 |  |                     if (separator == -1) | 
|  |  | 101 |  |                     { | 
|  | 0 | 102 |  |                         Port = int.Parse(value, CultureInfo.InvariantCulture); | 
|  | 0 | 103 |  |                         value = string.Empty; | 
|  |  | 104 |  |                     } | 
|  |  | 105 |  |                     else | 
|  |  | 106 |  |                     { | 
|  | 0 | 107 |  |                         Port = int.Parse(value.Substring(0, separator), CultureInfo.InvariantCulture); | 
|  | 0 | 108 |  |                         value = value.Substring(separator + 1); | 
|  |  | 109 |  |                     } | 
|  |  | 110 |  |                     // Port cannot be split (like Host), so always transition to Path when Port is parsed | 
|  | 0 | 111 |  |                     _position = RawWritingPosition.Path; | 
|  |  | 112 |  |                 } | 
|  | 0 | 113 |  |                 else if (_position == RawWritingPosition.Path) | 
|  |  | 114 |  |                 { | 
|  | 0 | 115 |  |                     int separator = value.IndexOf(QueryBeginSeparator); | 
|  | 0 | 116 |  |                     if (separator == -1) | 
|  |  | 117 |  |                     { | 
|  | 0 | 118 |  |                         AppendPath(value, escape); | 
|  | 0 | 119 |  |                         value = string.Empty; | 
|  |  | 120 |  |                     } | 
|  |  | 121 |  |                     else | 
|  |  | 122 |  |                     { | 
|  | 0 | 123 |  |                         AppendPath(value.Substring(0, separator), escape); | 
|  | 0 | 124 |  |                         value = value.Substring(separator + 1); | 
|  | 0 | 125 |  |                         _position = RawWritingPosition.Query; | 
|  |  | 126 |  |                     } | 
|  |  | 127 |  |                 } | 
|  | 0 | 128 |  |                 else if (_position == RawWritingPosition.Query) | 
|  |  | 129 |  |                 { | 
|  | 0 | 130 |  |                     int separator = value.IndexOf(QueryContinueSeparator); | 
|  | 0 | 131 |  |                     if (separator == 0) | 
|  |  | 132 |  |                     { | 
|  | 0 | 133 |  |                         value = value.Substring(1); | 
|  |  | 134 |  |                     } | 
|  | 0 | 135 |  |                     else if (separator == -1) | 
|  |  | 136 |  |                     { | 
|  | 0 | 137 |  |                         (string queryName, string queryValue) = GetQueryParts(value); | 
|  | 0 | 138 |  |                         AppendQuery(queryName, queryValue, escape); | 
|  | 0 | 139 |  |                         value = string.Empty; | 
|  |  | 140 |  |                     } | 
|  |  | 141 |  |                     else | 
|  |  | 142 |  |                     { | 
|  | 0 | 143 |  |                         (string queryName, string queryValue) = GetQueryParts(value.Substring(0, separator)); | 
|  | 0 | 144 |  |                         AppendQuery(queryName, queryValue, escape); | 
|  | 0 | 145 |  |                         value = value.Substring(separator + 1); | 
|  |  | 146 |  |                     } | 
|  |  | 147 |  |                 } | 
|  |  | 148 |  |             } | 
|  | 0 | 149 |  |         } | 
|  |  | 150 |  |  | 
|  |  | 151 |  |         private enum RawWritingPosition | 
|  |  | 152 |  |         { | 
|  |  | 153 |  |             Scheme, | 
|  |  | 154 |  |             Host, | 
|  |  | 155 |  |             Port, | 
|  |  | 156 |  |             Path, | 
|  |  | 157 |  |             Query | 
|  |  | 158 |  |         } | 
|  |  | 159 |  |  | 
|  |  | 160 |  |         public void AppendRawNextLink(string nextLink, bool escape) | 
|  |  | 161 |  |         { | 
|  |  | 162 |  |             // If it is an absolute link, we use the nextLink as the entire url | 
|  | 0 | 163 |  |             if (nextLink.StartsWith(Uri.UriSchemeHttp, StringComparison.InvariantCultureIgnoreCase)) | 
|  |  | 164 |  |             { | 
|  | 0 | 165 |  |                 Reset(new Uri(nextLink)); | 
|  | 0 | 166 |  |                 return; | 
|  |  | 167 |  |             } | 
|  |  | 168 |  |  | 
|  | 0 | 169 |  |             AppendPath(nextLink, escape); | 
|  | 0 | 170 |  |         } | 
|  |  | 171 |  |     } | 
|  |  | 172 |  | } |