PageRenderTime 54ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/Library/Internal/Network/Networking/HttpWebServer/URLParser.cs

https://github.com/kuba85/sones
C# | 440 lines | 247 code | 129 blank | 64 comment | 48 complexity | 8a6d4bdd3ca677cfa47fddc555019d47 MD5 | raw file
  1. /*
  2. * sones GraphDB - Community Edition - http://www.sones.com
  3. * Copyright (C) 2007-2011 sones GmbH
  4. *
  5. * This file is part of sones GraphDB Community Edition.
  6. *
  7. * sones GraphDB is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as published by
  9. * the Free Software Foundation, version 3 of the License.
  10. *
  11. * sones GraphDB is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with sones GraphDB. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. #region usings
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Linq;
  24. using System.Reflection;
  25. using sones.Library.LanguageExtensions;
  26. #endregion
  27. namespace sones.Networking.HTTP
  28. {
  29. /// <summary>
  30. /// A URL node which stores some childnodes and a callback
  31. /// </summary>
  32. public class UrlNode
  33. {
  34. public Dictionary<String, UrlNode> ChildNodes { get; set; }
  35. public MethodInfo Callback { get; set; }
  36. public Boolean? NeedsExplicitAuthentication { get; set; }
  37. public UrlNode()
  38. {
  39. ChildNodes = new Dictionary<String, UrlNode>();
  40. }
  41. }
  42. /// <summary>
  43. /// The URLParser class which parses some URLs
  44. /// </summary>
  45. public class URLParser
  46. {
  47. #region data
  48. //NLOG: temporarily commented
  49. //private static Logger //_Logger = LogManager.GetCurrentClassLogger();
  50. private char[] _Separators;
  51. /// <summary>
  52. /// &lt;WebMethod, &lt;location, UrlNode&gt;&gt;
  53. /// </summary>
  54. private Dictionary<String, Dictionary<String, UrlNode>> _RootNodes;
  55. #endregion
  56. #region constructors
  57. public URLParser(char[] separators)
  58. {
  59. _RootNodes = new Dictionary<String, Dictionary<String, UrlNode>>();
  60. _Separators = separators;
  61. }
  62. #endregion
  63. #region Initialize the parser by adding some URL definitions
  64. /// <summary>
  65. /// Adds a url
  66. /// </summary>
  67. /// <param name="url">The url</param>
  68. /// <param name="method">The method which should be invoked for any match</param>
  69. public void AddUrl(string url, MethodInfo method, string webMethod = "GET", Boolean? needsExplicitAuthentication = null)
  70. {
  71. url = url.ToLower();
  72. String[] ValueArray = url.Split(_Separators);
  73. if (ValueArray.Length == 0)
  74. {
  75. return;
  76. }
  77. var node = new Dictionary<String, UrlNode>();
  78. if (_RootNodes.ContainsKey(webMethod))
  79. {
  80. node = _RootNodes[webMethod];
  81. }
  82. else
  83. {
  84. _RootNodes.Add(webMethod, node);
  85. }
  86. AddNode(node, ValueArray, method, url, 0, needsExplicitAuthentication);
  87. }
  88. /// <summary>
  89. ///
  90. /// </summary>
  91. /// <param name="urlNodes"></param>
  92. /// <param name="urlParts"></param>
  93. /// <param name="methodInfo"></param>
  94. /// <param name="url">The uriginal url - this is needed to get the real parameter before splitting. Due to the split we lost the seperator char</param>
  95. /// <param name="posInUrl">The current pos in the url - this is needed to get the real parameter before splitting. Due to the split we lost the seperator char</param>
  96. private void AddNode(Dictionary<String, UrlNode> urlNodes, IEnumerable<String> urlParts, MethodInfo methodInfo, String url, Int32 posInUrl, Boolean? needsExplicitAuthentication)
  97. {
  98. var val = urlParts.FirstOrDefault();
  99. if (val == null)
  100. {
  101. throw new ArgumentNullException("urlParts.First");
  102. }
  103. #region SpeedUp by removing all between {...}
  104. if (val.StartsWith("{") && val.EndsWith("}")) // something like /{....} or .{....} or ?{.....} will be changed to /{} .{} ?{}
  105. {
  106. val = "{}";
  107. }
  108. #endregion
  109. UrlNode curUrlNode = null;
  110. #region Use an existing node or add a new one
  111. if (urlNodes.ContainsKey(val))
  112. {
  113. curUrlNode = urlNodes[val];
  114. }
  115. else
  116. {
  117. curUrlNode = new UrlNode();
  118. curUrlNode.NeedsExplicitAuthentication = needsExplicitAuthentication;
  119. urlNodes.Add(val, curUrlNode);
  120. }
  121. #endregion
  122. #region If there are some more parts proceed or set the methodInfo for the last part
  123. if (urlParts.CountIsGreater(1))
  124. {
  125. // there are still some more parts of the URL
  126. AddNode(curUrlNode.ChildNodes, urlParts.Skip(1), methodInfo, url, posInUrl, needsExplicitAuthentication);
  127. }
  128. else
  129. {
  130. // this was the last one - take the methodInfo
  131. curUrlNode.Callback = methodInfo;
  132. }
  133. #endregion
  134. }
  135. #endregion
  136. #region Get a callback for an url on the parser
  137. /// <summary>
  138. /// Get the next matching callback for the <paramref name="url"/>
  139. /// </summary>
  140. /// <param name="url">The url</param>
  141. /// <returns>The methodInfo callback and the optional parameters</returns>
  142. public Tuple<UrlNode, List<Object>> GetCallback(String url, String webMethod = "GET")
  143. {
  144. #region What should happen with ? params???
  145. if (url.IndexOf('?') > 0)
  146. {
  147. url = url.Substring(0, url.IndexOf('?'));
  148. }
  149. #endregion
  150. #region The WCF rest version seems to replace al // with / - so do the same
  151. //url = url.Replace("//", "/");
  152. #endregion
  153. if (!_RootNodes.ContainsKey(webMethod)) // try to find the method
  154. {
  155. return null;
  156. }
  157. var urlParts = url.Split(_Separators); // skip the first one because this is all in front of the first "/" and this is odd
  158. return GetCallback(_RootNodes[webMethod], urlParts, new List<Object>(), url, 0);
  159. }
  160. /// <summary>
  161. /// This will check
  162. /// </summary>
  163. /// <param name="urlNodes"></param>
  164. /// <param name="urlParts">the splittet</param>
  165. /// <param name="parameters">The parameters, parsed from the url</param>
  166. /// <param name="url">The uriginal url - this is needed to get the real parameter before splitting. Due to the split we lost the seperator char</param>
  167. /// <param name="posInUrl">The current pos in the url - this is needed to get the real parameter before splitting. Due to the split we lost the seperator char</param>
  168. /// <returns></returns>
  169. private Tuple<UrlNode, List<Object>> GetCallback(Dictionary<String, UrlNode> urlNodes, IEnumerable<String> urlParts, List<Object> parameters, String url, Int32 posInUrl)
  170. {
  171. var val = urlParts.FirstOrDefault();
  172. if (val == null)
  173. {
  174. throw new ArgumentNullException("urlParts.First");
  175. }
  176. val = val.ToLower();
  177. UrlNode curUrlNode = null;
  178. if (urlNodes.ContainsKey(val))
  179. {
  180. #region The current url node has this part of the URL
  181. curUrlNode = urlNodes[val];
  182. posInUrl += (val.Length == 0) ? 1 : val.Length + 1; // for just a / (which is now a empty string because of the split)
  183. #endregion
  184. }
  185. else if (urlNodes.ContainsKey("{}"))
  186. {
  187. #region If there is a wildcard in this node, use this
  188. curUrlNode = urlNodes["{}"];
  189. if (curUrlNode.ChildNodes.IsNullOrEmpty())
  190. {
  191. // this is the last parameter - so add the rest of the url as well
  192. parameters.Add(url.Substring(posInUrl));
  193. posInUrl = url.Length;
  194. return new Tuple<UrlNode, List<object>>(curUrlNode, parameters);
  195. }
  196. else
  197. {
  198. // just add this part and proceed
  199. if (url.Length > posInUrl)
  200. {
  201. //parameters.Add(url.Substring(posInUrl, (val.Length == 0) ? 1 : val.Length));
  202. parameters.Add(url.Substring(posInUrl, val.Length));
  203. posInUrl += val.Length + 1; // add 1 for the missing seperator char
  204. }
  205. else
  206. {
  207. parameters.Add("");
  208. }
  209. }
  210. #endregion
  211. }
  212. else
  213. {
  214. #region The node does not have this part and a wildcard neither
  215. return null;
  216. #endregion
  217. }
  218. if (urlParts.CountIsGreater(1) && !curUrlNode.ChildNodes.IsNullOrEmpty())
  219. {
  220. #region There are more url parts AND childs in the current node
  221. // we have some more childs defined
  222. Tuple<UrlNode, List<object>> retval = null;
  223. var newParams = new List<Object>();
  224. do
  225. {
  226. #region As long as we can go deeper lets do it
  227. retval = GetCallback(curUrlNode.ChildNodes, urlParts.Skip(1), newParams, url, posInUrl);
  228. if (retval == null)
  229. {
  230. #region There is no hit for the current nodes childs and the next url parts
  231. if (curUrlNode.ChildNodes.ContainsKey("{}"))
  232. {
  233. #region But the childs contains a wildcard we could use
  234. curUrlNode = curUrlNode.ChildNodes["{}"];
  235. if (curUrlNode.ChildNodes.IsNullOrEmpty())
  236. {
  237. #region The wildcard child has no more childs to verify, so lets take it
  238. parameters.Add(url.Substring(posInUrl));
  239. retval = new Tuple<UrlNode, List<object>>(curUrlNode, newParams);//parameters);
  240. #endregion
  241. }
  242. else
  243. {
  244. #region The wildcard child have mor childs which needs to be verified
  245. urlParts = urlParts.Skip(1);
  246. if (GetCallback(curUrlNode.ChildNodes, urlParts.Skip(1), newParams, url, posInUrl) == null)
  247. {
  248. #region The next parts do not leed into a successfull mapping, lets use this wildcard
  249. parameters.Add(url.Substring(posInUrl));
  250. retval = new Tuple<UrlNode, List<object>>(curUrlNode, parameters);
  251. parameters = null;
  252. #endregion
  253. }
  254. else
  255. {
  256. #region Take this wildcard as parameter and proceed
  257. val = urlParts.First();
  258. newParams.Add(url.Substring(posInUrl, (val.Length == 0) ? 1 : val.Length));
  259. posInUrl += (val.Length == 0) ? 1 : val.Length + 1;
  260. #endregion
  261. }
  262. #endregion
  263. }
  264. #endregion
  265. }
  266. else
  267. {
  268. #region This part is still not valid, return null to proceed with the predecessor level
  269. return null;
  270. #endregion
  271. }
  272. #endregion
  273. }
  274. #endregion
  275. } while (retval == null);
  276. #region Are there any parameters to add to the result?
  277. if (!parameters.IsNullOrEmpty())
  278. {
  279. #region We need to swap the parameters due to recursive call
  280. parameters.AddRange(retval.Item2);
  281. retval = new Tuple<UrlNode, List<object>>(retval.Item1, parameters);
  282. #endregion
  283. }
  284. #endregion
  285. return retval;
  286. #endregion
  287. }
  288. else if (curUrlNode.Callback == null && !curUrlNode.ChildNodes.IsNullOrEmpty() && curUrlNode.ChildNodes.ContainsKey("{}"))
  289. {
  290. #region The current callback is null AND this is the last part of the url
  291. parameters.Add(url.Substring(posInUrl));
  292. curUrlNode = curUrlNode.ChildNodes["{}"];
  293. #endregion
  294. }
  295. else if (urlParts.CountIsGreater(1) && curUrlNode.ChildNodes.IsNullOrEmpty())
  296. {
  297. #region No childs but still some url parts
  298. return null;
  299. #endregion
  300. }
  301. else if (curUrlNode.Callback == null && !curUrlNode.ChildNodes.IsNullOrEmpty() && !curUrlNode.ChildNodes.ContainsKey("{}"))
  302. {
  303. #region There are childs but they have no placeholders ({}), so we have no valid definition
  304. return null;
  305. #endregion
  306. }
  307. return new Tuple<UrlNode, List<object>>(curUrlNode, parameters);
  308. }
  309. #endregion
  310. }
  311. }