Merge pull request #820 from brendanzagaeski/master
[mono.git] / mcs / class / System / System / UriParseComponents.cs
1 //
2 // Internal UriParseComponents class
3 //
4 // Author:
5 //      Vinicius Jarina  <vinicius.jarina@xamarin.com>
6 //
7 // Copyright (C) 2012 Xamarin, Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System.Text;
30
31
32 namespace System {
33         
34         internal class ParserState
35         {
36                 public ParserState (string uri)
37                 {
38                         remaining = uri;
39                         elements  = new UriElements ();
40                 }
41                 
42                 public string remaining;
43                 public UriElements elements;
44         }
45         
46         // Parse Uri components (scheme, userinfo, host, query, fragment)
47         // http://www.ietf.org/rfc/rfc3986.txt
48         internal static class UriParseComponents
49         {
50                 public static UriElements ParseComponents (string uri)
51                 {
52                         ParserState state = new ParserState (uri);
53                         
54                         bool ok = ParseScheme (ref state);
55                         if (ok)
56                             ok = ParseAuthority (ref state);
57                         if (ok)
58                             ok = ParsePath (ref state);
59                         if (ok)
60                             ok = ParseQuery (ref state);
61                         if (ok)
62                             ParseFragment (ref state);
63                         
64                         return state.elements;
65                 }
66                                 // ALPHA
67                 private static bool IsAlpha (char ch)
68                 {
69                         return (('a' <= ch) && (ch <= 'z')) ||
70                                    (('A' <= ch) && (ch <= 'Z'));
71                 }
72                 
73                 // 3.1) scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
74                 private static bool ParseScheme (ref ParserState state) 
75                 {
76                         string part = state.remaining;
77                         
78                         if (!IsAlpha (part [0]))
79                                 return part.Length > 0;
80                         
81                         StringBuilder sb = new StringBuilder ();
82                         sb.Append (part [0]);
83                         
84                         int index;
85                         for (index = 1; index < part.Length; index++ ) {
86                                 char ch = part [index];
87                                 if (ch != '.' && ch != '-' && ch != '+' && !IsAlpha (ch))
88                                         break;
89                                 
90                                 sb.Append (ch);
91                         }
92                         
93                         if (index + 1 <= part.Length && part [index] == ':') {
94                                 state.elements.scheme = sb.ToString ();
95                                 state.remaining = part.Substring (index + 1);
96                         }
97                                 
98                         return state.remaining.Length > 0;
99                 }
100                 
101                 private static bool ParseAuthority (ref ParserState state)
102                 {
103                         string part = state.remaining;
104                         
105                         if (part.Length < 2 || part [0] != '/' || part [1] != '/')
106                                 return part.Length > 0;
107                         
108                         state.remaining = part.Substring (2);
109                         
110                         bool ok = ParseUser (ref state);
111                         if (ok)
112                                 ok = ParseHost (ref state);
113                         if (ok)
114                                 ok = ParsePort (ref state);
115                         return ok;
116                 }
117
118                 static bool IsUnreserved (char ch)
119                 {
120                         return ch == '-' || ch == '.' || ch == '_' || ch == '~';
121                 }
122
123
124                 static bool IsSubDelim (char ch)
125                 {
126                         return ch == '!' || ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' ||
127                                 ch == '*' || ch == '+' || ch == ',' || ch == ';' || ch == '=';
128                 }
129                 
130                 // userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
131                 private static bool ParseUser (ref ParserState state)
132                 {
133                         string part = state.remaining;
134                         StringBuilder sb = null;
135
136                         int index;
137                         for (index = 0; index < part.Length; index++) {
138                                 char ch = part [index];
139
140                                 if (ch == '%'){
141                                         if (!Uri.IsHexEncoding (part, index))
142                                                 return false;
143                                         ch = Uri.HexUnescape (part, ref index);
144                                 }
145
146                                 if (Char.IsLetterOrDigit (ch) || IsUnreserved (ch) || IsSubDelim (ch) || ch == ':'){
147                                         if (sb == null)
148                                                 sb = new StringBuilder ();
149                                         sb.Append (ch);
150                                 } else
151                                         break;
152                         }
153
154                         if (index + 1 <= part.Length && part [index] == '@') {
155                                 state.elements.user = sb == null ? "" : sb.ToString ();
156                                 state.remaining = state.remaining.Substring (index + 1);
157                         }
158                                 
159                         return state.remaining.Length > 0;
160                 }
161                 
162                 // host        = IP-literal / IPv4address / reg-name
163                 private static bool ParseHost (ref ParserState state)
164                 {
165                         string part = state.remaining;
166                         StringBuilder sb = new StringBuilder ();
167                         
168                         int index;
169                         for (index = 0; index < part.Length; index++) { 
170                                 
171                                 char ch = part [index];
172                                 
173                                 if (ch == '/' || ch == ':' || ch == '#' || ch == '?')
174                                         break;
175                                 
176                                 sb.Append (ch);
177                         }
178                         
179                         if (index  <= part.Length)
180                                 state.remaining = part.Substring (index);
181                         
182                         state.elements.host = sb.ToString();
183                                 
184                         return state.remaining.Length > 0;
185                 }
186                 
187                 // port          = *DIGIT
188                 private static bool ParsePort (ref ParserState state)
189                 {
190                         string part = state.remaining;
191                         if (part.Length == 0 || part [0] != ':')
192                                 return part.Length > 0;
193                         
194                         StringBuilder sb = new StringBuilder ();
195                         
196                         int index;
197                         for (index = 1; index < part.Length; index++ ) {
198                                 char ch = part [index];
199                                 
200                                 if (!char.IsDigit (ch))
201                                         break;
202                                 
203                                 sb.Append (ch);
204                         }
205                         
206                         if (index <= part.Length)
207                                 state.remaining = part.Substring (index);
208                         
209                         state.elements.port = sb.ToString();
210                                 
211                         return state.remaining.Length > 0;
212                 }
213                 
214                 private static bool ParsePath (ref ParserState state)
215                 {
216                         string part = state.remaining;
217                         StringBuilder sb = new StringBuilder ();
218                         
219                         int index;
220                         for (index = 0; index < part.Length; index++) {
221                                 
222                                 char ch = part [index];
223                                 
224                                 if (ch == '#' || ch == '?')
225                                         break;
226                                 
227                                 sb.Append (ch);
228                         }
229                         
230                         if (index <= part.Length)
231                                 state.remaining = part.Substring (index);
232                         
233                         state.elements.path  = sb.ToString ();
234                                 
235                         return state.remaining.Length > 0;
236                 }
237                 
238                 private static bool ParseQuery (ref ParserState state)
239                 {
240                         string part = state.remaining;
241                         
242                         if (part.Length == 0 || part [0] != '?')
243                                 return part.Length > 0;
244                         
245                         StringBuilder sb = new StringBuilder ();
246                         
247                         int index;
248                         for (index = 1; index < part.Length; index++) {
249                                 
250                                 char ch = part [index];
251                                 
252                                 if (ch == '#')
253                                         break;
254                                 
255                                 sb.Append (ch);
256                         }
257                         
258                         if (index <= part.Length)
259                                 state.remaining = part.Substring (index);
260                         
261                         state.elements.query  = sb.ToString ();
262                                 
263                         return state.remaining.Length > 0;
264                 }
265                 
266                 private static bool ParseFragment (ref ParserState state)
267                 {
268                         string part = state.remaining;
269                         
270                         if (part.Length == 0 || part [0] != '#')
271                                 return part.Length > 0;
272                         
273                         StringBuilder sb = new StringBuilder ();
274                         
275                         int index;
276                         for (index = 1; index < part.Length; index++) { 
277                                 
278                                 char ch = part [index];
279                                 
280                                 sb.Append (ch);
281                         }
282                         
283                         state.elements.fragment = sb.ToString ();
284                         
285                         return false;
286                 }
287         }
288 }