2009-04-20 Carlos Alberto Cortez <calberto.cortez@gmail.com>
[mono.git] / mcs / class / System.Web.Routing / System.Web.Routing / UrlPattern.cs
1 //
2 // Route.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2008 Novell Inc. http://novell.com
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Collections.Generic;
32 using System.Security.Permissions;
33 using System.Web;
34
35 namespace System.Web.Routing
36 {
37         internal class UrlPattern
38         {
39                 int segmentsCount;
40                 string [] segments;
41                 int [] segmentLengths;
42                 bool [] segment_flags;          
43                 string [] tokens;
44
45                 public UrlPattern (string url)
46                 {
47                         Url = url;
48                         Parse ();
49                 }
50
51                 public string Url { get; private set; }
52
53                 void Parse ()
54                 {
55                         if (String.IsNullOrEmpty (Url))
56                                 throw new SystemException ("INTERNAL ERROR: it should not try to parse null or empty string");
57                         if (Url [0] == '~' || Url [0] == '/')
58                                 throw new ArgumentException ("Url must not start with '~' or '/'");
59                         if (Url.IndexOf ('?') >= 0)
60                                 throw new ArgumentException ("Url must not contain '?'");
61
62                         var tokens = new List<string> ();
63
64                         segments = Url.Split ('/');
65                         segmentsCount = segments.Length;
66                         segment_flags = new bool [segmentsCount];
67                         segmentLengths = new int [segmentsCount];
68                         
69                         for (int i = 0; i < segmentsCount; i++) {
70                                 string s = segments [i];
71                                 int slen = s.Length;
72                                 segmentLengths [i] = slen;
73                                 if (slen == 0 && i < segmentsCount - 1)
74                                         throw new ArgumentException ("Consecutive URL segment separators '/' are not allowed");
75                                 int from = 0;
76                                 while (from < slen) {
77                                         int start = s.IndexOf ('{', from);
78                                         if (start == slen - 1)
79                                                 throw new ArgumentException ("Unterminated URL parameter. It must contain matching '}'");
80                                         if (start < 0) {
81                                                 if (s.IndexOf ('}', from) >= from)
82                                                         throw new ArgumentException ("Unmatched URL parameter closer '}'. A corresponding '{' must precede");
83                                                 from = slen;
84                                                 continue;
85                                         }
86                                         segment_flags [i] = true;
87                                         int end = s.IndexOf ('}', start + 1);
88                                         int next = s.IndexOf ('{', start + 1);
89                                         if (end < 0 || next >= 0 && next < end)
90                                                 throw new ArgumentException ("Unterminated URL parameter. It must contain matching '}'");
91                                         if (end == start + 1)
92                                                 throw new ArgumentException ("Empty URL parameter name is not allowed");
93                                         if (next == end + 1)
94                                                 throw new ArgumentException ("Two consecutive URL parameters are not allowed. Split into a different segment by '/', or a literal string.");
95                                         string token = s.Substring (start + 1, end - start - 1);
96                                         if (!tokens.Contains (token))
97                                                 tokens.Add (token);
98                                         from = end + 1;
99                                 }
100                         }
101
102                         this.tokens = tokens.ToArray ();
103                 }
104
105                 string SegmentToKey (string segment)
106                 {
107                         int start = segment.IndexOf ('{');
108                         int end = segment.IndexOf ('}');
109                         if (start == -1)
110                                 start = 0;
111                         else
112                                 start++;
113                         
114                         if (end == -1)
115                                 end = segment.Length;
116                         return segment.Substring (start, end - start);
117                 }
118                 
119                 RouteValueDictionary tmp = new RouteValueDictionary ();
120                 public RouteValueDictionary Match (string path, RouteValueDictionary defaults)
121                 {
122                         tmp.Clear ();
123
124                         // quick check
125                         if (Url == path && Url.IndexOf ('{') < 0)
126                                 return tmp;
127
128                         string [] argSegs = path.Split ('/');
129                         int argsLen = argSegs.Length;
130                         bool haveDefaults = defaults != null && defaults.Count > 0;
131                         if (!haveDefaults && argsLen != segmentsCount)
132                                 return null;
133
134                         for (int i = 0; i < segmentsCount; i++) {
135                                 if (segment_flags [i]) {
136                                         string t = segments [i];
137                                         string v = i < argsLen ? argSegs [i] : null;
138
139                                         if (String.IsNullOrEmpty (v)) {
140                                                 string key = SegmentToKey (segments [i]);
141                                                 object o;
142                                                 if (haveDefaults && !defaults.TryGetValue (key, out o))
143                                                         return null; // ends with '/' while more
144                                                                      // tokens are expected and
145                                                                      // there are is no default
146                                                                      // value in the defaults
147                                                                      // dictionary.
148
149                                                 v = o as string;
150                                                 if (v == null)
151                                                         throw new InvalidOperationException ("The RouteData must contain an item named '" + key + "' with a string value.");
152                                         }
153                                         
154                                         int tfrom = 0, vfrom = 0;
155                                         while (tfrom < t.Length) {
156                                                 int start = t.IndexOf ('{', tfrom);
157                                                 if (start < 0) {
158                                                         int tlen = t.Length - tfrom;
159                                                         int vlen = v.Length - vfrom;
160                                                         if (tlen != vlen ||
161                                                             String.Compare (t, tfrom, v, vfrom, tlen, StringComparison.Ordinal) != 0)
162                                                                 return null; // mismatch
163                                                         break;
164                                                 }
165
166                                                 // if there is a string literal before next template item, check it in the value string.
167                                                 int len = start - tfrom;
168                                                 if (len > 0 && String.CompareOrdinal (t, tfrom, v, vfrom, len) != 0)
169                                                         return null; // mismatch
170                                                 vfrom += len;
171
172                                                 int end = t.IndexOf ('}', start + 1);
173                                                 int next = t.IndexOf ('{', end + 1);
174                                                 string key = t.Substring (start + 1, end - start - 1);
175                                                 string nextToken = next < 0 ? t.Substring (end + 1) : t.Substring (end + 1, next - end - 1);
176                                                 int vnext = nextToken.Length > 0 ? v.IndexOf (nextToken, vfrom + 1, StringComparison.Ordinal) : -1;
177
178                                                 if (next < 0) {
179                                                         var vs = vnext < 0 ? v.Substring (vfrom) : v.Substring (vfrom, vnext - vfrom);
180                                                         tmp.Add (key, vs);
181                                                         vfrom = vs.Length;
182                                                 } else {
183                                                         if (vnext < 0)
184                                                                 return null; // mismatch
185                                                         tmp.Add (key, v.Substring (vfrom, vnext - vfrom));
186                                                         vfrom = vnext;
187                                                 }
188                                                 tfrom = end + 1;
189                                         }
190                                 } else if (i > argsLen || segments [i] != argSegs [i])
191                                         return null;
192                         }
193
194                         return tmp;
195                 }
196
197                 static readonly string [] substsep = {"{{"};
198
199                 // it may return null for invalid values.
200                 public bool TrySubstitute (RouteValueDictionary values, RouteValueDictionary defaults, out string value)
201                 {
202                         var replacements = new RouteValueDictionary ();
203                         if (values == null) {
204                                 value = Url;
205                                 return true;
206                         } else {
207                                 object val;
208                                 bool missing;
209                                 foreach (string token in tokens) {
210                                         val = null;
211                                         missing = false;
212                                         if (!values.TryGetValue (token, out val))
213                                                 if (defaults == null || !defaults.TryGetValue (token, out val))
214                                                         missing = true;
215                                         if (missing) {
216                                                 value = null;
217                                                 return false;
218                                         }
219
220                                         replacements.Add (token, val);
221                                 }
222                         }
223
224                         // horrible hack, but should work
225                         string [] arr = Url.Split (substsep, StringSplitOptions.None);
226                         for (int i = 0; i < arr.Length; i++) {
227                                 string s = arr [i];
228                                 foreach (var p in replacements)
229                                         s = s.Replace ("{" + p.Key + "}", p.Value.ToString ());
230                                 arr [i] = s;
231                         }
232                         value = String.Join ("{{", arr);
233                         return true;
234                 }
235         }
236 }