HttpInput.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace OSHttpServer
  5. {
  6. /// <summary>
  7. /// Contains some kind of input from the browser/client.
  8. /// can be QueryString, form data or any other request body content.
  9. /// </summary>
  10. public class HttpInput : IHttpInput
  11. {
  12. /// <summary> Representation of a non-initialized class instance </summary>
  13. public static readonly HttpInput Empty = new("Empty", true);
  14. private readonly Dictionary<string, HttpInputItem> _items = new();
  15. private string _name;
  16. /// <summary> Variable telling the class that it is non-initialized <see cref="Empty"/> </summary>
  17. protected readonly bool _ignoreChanges;
  18. /// <summary>
  19. /// Initializes a new instance of the <see cref="HttpInput"/> class.
  20. /// </summary>
  21. /// <param name="name">form name.</param>
  22. public HttpInput(string name)
  23. {
  24. Name = name;
  25. }
  26. /// <summary>
  27. /// Initializes a new instance of the <see cref="HttpInput"/> class.
  28. /// </summary>
  29. /// <param name="name">form name.</param>
  30. /// <param name="ignoreChanges">if set to <c>true</c> all changes will be ignored. </param>
  31. /// <remarks>this constructor should only be used by Empty</remarks>
  32. protected HttpInput(string name, bool ignoreChanges)
  33. {
  34. _name = name;
  35. _ignoreChanges = ignoreChanges;
  36. }
  37. /// <summary>Creates a deep copy of the HttpInput class</summary>
  38. /// <param name="input">The object to copy</param>
  39. /// <remarks>The function makes a deep copy of quite a lot which can be slow</remarks>
  40. protected HttpInput(HttpInput input)
  41. {
  42. foreach (HttpInputItem item in input)
  43. _items.Add(item.Name, new HttpInputItem(item));
  44. _name = input._name;
  45. _ignoreChanges = input._ignoreChanges;
  46. }
  47. /// <summary>
  48. /// Form name as lower case
  49. /// </summary>
  50. public string Name
  51. {
  52. get { return _name; }
  53. set { _name = value; }
  54. }
  55. /// <summary>
  56. /// Add a new element. Form array elements are parsed
  57. /// and added in a correct hierarchy.
  58. /// </summary>
  59. /// <param name="name">Name is converted to lower case.</param>
  60. /// <param name="value"></param>
  61. /// <exception cref="ArgumentNullException"><c>name</c> is null.</exception>
  62. /// <exception cref="InvalidOperationException">Cannot add stuff to <see cref="HttpInput.Empty"/>.</exception>
  63. public void Add(string name, string value)
  64. {
  65. if (name is null)
  66. throw new ArgumentNullException("name");
  67. if (_ignoreChanges)
  68. throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty.");
  69. // Check if it's a sub item.
  70. // we can have multiple levels of sub items as in user[extension[id]] => user -> extension -> id
  71. int pos = name.IndexOf('[');
  72. if (pos != -1)
  73. {
  74. string name1 = name[..pos];
  75. string name2 = ExtractOne(name);
  76. if (!_items.TryGetValue(name1, out HttpInputItem it))
  77. {
  78. it = new HttpInputItem(name1, null);
  79. _items.Add(name1, it);
  80. }
  81. it.Add(name2, value);
  82. }
  83. else
  84. {
  85. if (_items.TryGetValue(name, out HttpInputItem it))
  86. it.Add(value);
  87. else
  88. _items.Add(name, new HttpInputItem(name, value));
  89. }
  90. }
  91. /// <summary>
  92. /// Get a form item.
  93. /// </summary>
  94. /// <param name="name"></param>
  95. /// <returns>Returns <see cref="HttpInputItem.Empty"/> if item was not found.</returns>
  96. public HttpInputItem this[string name]
  97. {
  98. get
  99. {
  100. return _items.ContainsKey(name) ? _items[name] : HttpInputItem.Empty;
  101. }
  102. }
  103. /// <summary>
  104. /// Returns true if the class contains a <see cref="HttpInput"/> with the corresponding name.
  105. /// </summary>
  106. /// <param name="name">The field/query string name</param>
  107. /// <returns>True if the value exists</returns>
  108. public bool Contains(string name)
  109. {
  110. return _items.TryGetValue(name, out HttpInputItem it) && it.Value is not null;
  111. }
  112. /// <summary>
  113. /// Parses an item and returns it.
  114. /// This function is primarily used to parse array items as in user[name].
  115. /// </summary>
  116. /// <param name="name"></param>
  117. /// <param name="value"></param>
  118. /// <returns></returns>
  119. public static HttpInputItem ParseItem(string name, string value)
  120. {
  121. HttpInputItem item;
  122. // Check if it's a sub item.
  123. // we can have multiple levels of sub items as in user[extension[id]]] => user -> extension -> id
  124. int pos = name.IndexOf('[');
  125. if (pos != -1)
  126. {
  127. string name1 = name[..pos];
  128. string name2 = ExtractOne(name);
  129. item = new HttpInputItem(name1, null)
  130. {
  131. { name2, value }
  132. };
  133. }
  134. else
  135. item = new HttpInputItem(name, value);
  136. return item;
  137. }
  138. /// <summary> Outputs the instance representing all its values joined together </summary>
  139. /// <returns></returns>
  140. public override string ToString()
  141. {
  142. string temp = string.Empty;
  143. foreach (KeyValuePair<string, HttpInputItem> item in _items)
  144. temp += item.Value.ToString(Name);
  145. return temp;
  146. }
  147. /// <summary>Returns all items as an unescaped query string.</summary>
  148. /// <returns></returns>
  149. public string ToString(bool asQueryString)
  150. {
  151. if (!asQueryString)
  152. return ToString();
  153. string temp = string.Empty;
  154. foreach (KeyValuePair<string, HttpInputItem> item in _items)
  155. temp += item.Value.ToString(null, true) + '&';
  156. return temp.Length > 0 ? temp[..^1] : string.Empty;
  157. }
  158. /// <summary>
  159. /// Extracts one parameter from an array
  160. /// </summary>
  161. /// <param name="value">Containing the string array</param>
  162. /// <returns>All but the first value</returns>
  163. /// <example>
  164. /// string test1 = ExtractOne("system[user][extension][id]");
  165. /// string test2 = ExtractOne(test1);
  166. /// string test3 = ExtractOne(test2);
  167. /// // test1 = user[extension][id]
  168. /// // test2 = extension[id]
  169. /// // test3 = id
  170. /// </example>
  171. public static string ExtractOne(string value)
  172. {
  173. int pos = value.IndexOf('[');
  174. if (pos != -1)
  175. {
  176. ++pos;
  177. int gotMore = value.IndexOf('[', pos + 1);
  178. if (gotMore != -1)
  179. value = string.Concat(value.AsSpan(pos, gotMore - pos - 1), value.AsSpan(gotMore));
  180. else
  181. value = value.Substring(pos, value.Length - pos - 1);
  182. }
  183. return value;
  184. }
  185. /// <summary>Resets all data contained by class</summary>
  186. virtual public void Clear()
  187. {
  188. _name = string.Empty;
  189. _items.Clear();
  190. }
  191. ///<summary>
  192. ///Returns an enumerator that iterates through the collection.
  193. ///</summary>
  194. ///
  195. ///<returns>
  196. ///A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
  197. ///</returns>
  198. ///<filterpriority>1</filterpriority>
  199. IEnumerator<HttpInputItem> IEnumerable<HttpInputItem>.GetEnumerator()
  200. {
  201. return _items.Values.GetEnumerator();
  202. }
  203. ///<summary>
  204. ///Returns an enumerator that iterates through a collection.
  205. ///</summary>
  206. ///
  207. ///<returns>
  208. ///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
  209. ///</returns>
  210. ///<filterpriority>2</filterpriority>
  211. public IEnumerator GetEnumerator()
  212. {
  213. return _items.Values.GetEnumerator();
  214. }
  215. }
  216. /// <summary>
  217. /// Base class for request data containers
  218. /// </summary>
  219. public interface IHttpInput : IEnumerable<HttpInputItem>
  220. {
  221. /// <summary>
  222. /// Adds a parameter mapped to the presented name
  223. /// </summary>
  224. /// <param name="name">The name to map the parameter to</param>
  225. /// <param name="value">The parameter value</param>
  226. void Add(string name, string value);
  227. /// <summary>
  228. /// Returns a request parameter
  229. /// </summary>
  230. /// <param name="name">The name associated with the parameter</param>
  231. /// <returns></returns>
  232. HttpInputItem this[string name]
  233. { get; }
  234. /// <summary>
  235. /// Returns true if the container contains the requested parameter
  236. /// </summary>
  237. /// <param name="name">Parameter id</param>
  238. /// <returns>True if parameter exists</returns>
  239. bool Contains(string name);
  240. }
  241. }