5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2008 Novell Inc. http://novell.com
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections.Generic;
32 using System.Security.Permissions;
35 namespace System.Web.Routing
37 internal class UrlPattern
41 int [] segmentLengths;
42 bool [] segment_flags;
45 public UrlPattern (string url)
51 public string Url { get; private set; }
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 '?'");
62 var tokens = new List<string> ();
64 segments = Url.Split ('/');
65 segmentsCount = segments.Length;
66 segment_flags = new bool [segmentsCount];
67 segmentLengths = new int [segmentsCount];
69 for (int i = 0; i < segmentsCount; i++) {
70 string s = segments [i];
72 segmentLengths [i] = slen;
73 if (slen == 0 && i < segmentsCount - 1)
74 throw new ArgumentException ("Consecutive URL segment separators '/' are not allowed");
77 int start = s.IndexOf ('{', from);
78 if (start == slen - 1)
79 throw new ArgumentException ("Unterminated URL parameter. It must contain matching '}'");
81 if (s.IndexOf ('}', from) >= from)
82 throw new ArgumentException ("Unmatched URL parameter closer '}'. A corresponding '{' must precede");
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 '}'");
92 throw new ArgumentException ("Empty URL parameter name is not allowed");
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))
102 this.tokens = tokens.ToArray ();
105 string SegmentToKey (string segment)
107 int start = segment.IndexOf ('{');
108 int end = segment.IndexOf ('}');
115 end = segment.Length;
116 return segment.Substring (start, end - start);
119 RouteValueDictionary tmp = new RouteValueDictionary ();
120 public RouteValueDictionary Match (string path, RouteValueDictionary defaults)
125 if (Url == path && Url.IndexOf ('{') < 0)
128 string [] argSegs = path.Split ('/');
129 int argsLen = argSegs.Length;
130 bool haveDefaults = defaults != null && defaults.Count > 0;
131 if (!haveDefaults && argsLen != segmentsCount)
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;
139 if (String.IsNullOrEmpty (v)) {
140 string key = SegmentToKey (segments [i]);
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
151 throw new InvalidOperationException ("The RouteData must contain an item named '" + key + "' with a string value.");
154 int tfrom = 0, vfrom = 0;
155 while (tfrom < t.Length) {
156 int start = t.IndexOf ('{', tfrom);
158 int tlen = t.Length - tfrom;
159 int vlen = v.Length - vfrom;
161 String.Compare (t, tfrom, v, vfrom, tlen, StringComparison.Ordinal) != 0)
162 return null; // mismatch
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
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;
179 var vs = vnext < 0 ? v.Substring (vfrom) : v.Substring (vfrom, vnext - vfrom);
184 return null; // mismatch
185 tmp.Add (key, v.Substring (vfrom, vnext - vfrom));
190 } else if (i > argsLen || segments [i] != argSegs [i])
197 static readonly string [] substsep = {"{{"};
199 // it may return null for invalid values.
200 public bool TrySubstitute (RouteValueDictionary values, RouteValueDictionary defaults, out string value)
202 var replacements = new RouteValueDictionary ();
203 if (values == null) {
209 foreach (string token in tokens) {
212 if (!values.TryGetValue (token, out val))
213 if (defaults == null || !defaults.TryGetValue (token, out val))
220 replacements.Add (token, val);
224 // horrible hack, but should work
225 string [] arr = Url.Split (substsep, StringSplitOptions.None);
226 for (int i = 0; i < arr.Length; i++) {
228 foreach (var p in replacements)
229 s = s.Replace ("{" + p.Key + "}", p.Value.ToString ());
232 value = String.Join ("{{", arr);