PageRenderTime 22ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/4.0/Source/ClassLibrary/Filter.cs

#
C# | 636 lines | 340 code | 92 blank | 204 comment | 67 complexity | 941696ea0621cc47b7625cbc5894a80e MD5 | raw file
Possible License(s): CPL-1.0, GPL-2.0, CC-BY-SA-3.0, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. /*
  2. Copyright (c) 2012 Jakub Misek
  3. The use and distribution terms for this software are contained in the file named License.txt,
  4. which can be found in the root of the Phalanger distribution. By using this software
  5. in any fashion, you are agreeing to be bound by the terms of this license.
  6. You must not remove this notice from this software.
  7. */
  8. using System;
  9. using PHP.Core;
  10. using System.Text;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.Text.RegularExpressions;
  14. using System.ComponentModel;
  15. #if SILVERLIGHT
  16. using PHP.CoreCLR;
  17. using System.Windows.Browser;
  18. #else
  19. using System.Web;
  20. using System.Diagnostics;
  21. #endif
  22. namespace PHP.Library
  23. {
  24. #region Constants
  25. public enum FilterInput : int
  26. {
  27. [ImplementsConstant("INPUT_POST")]
  28. Post = 0,
  29. [ImplementsConstant("INPUT_GET")]
  30. Get = 1,
  31. [ImplementsConstant("INPUT_COOKIE")]
  32. Cookie = 2,
  33. [ImplementsConstant("INPUT_ENV")]
  34. Env = 4,
  35. [ImplementsConstant("INPUT_SERVER")]
  36. Server = 5,
  37. [ImplementsConstant("INPUT_SESSION")]
  38. Session = 6,
  39. [ImplementsConstant("INPUT_REQUEST")]
  40. Request = 99
  41. }
  42. /// <summary>
  43. /// Other filter ids.
  44. /// </summary>
  45. public enum FilterIds : int
  46. {
  47. /// <summary>
  48. /// Flag used to require scalar as input
  49. /// </summary>
  50. [ImplementsConstant("FILTER_REQUIRE_SCALAR")]
  51. FILTER_REQUIRE_SCALAR = 33554432,
  52. /// <summary>
  53. /// Require an array as input.
  54. /// </summary>
  55. [ImplementsConstant("FILTER_REQUIRE_ARRAY")]
  56. FILTER_REQUIRE_ARRAY = 16777216,
  57. /// <summary>
  58. /// Always returns an array.
  59. /// </summary>
  60. [ImplementsConstant("FILTER_FORCE_ARRAY")]
  61. FILTER_FORCE_ARRAY = 67108864,
  62. /// <summary>
  63. /// Use NULL instead of FALSE on failure.
  64. /// </summary>
  65. [ImplementsConstant("FILTER_NULL_ON_FAILURE")]
  66. FILTER_NULL_ON_FAILURE = 134217728,
  67. /// <summary>
  68. /// ID of "callback" filter.
  69. /// </summary>
  70. [ImplementsConstant("FILTER_CALLBACK")]
  71. FILTER_CALLBACK = 1024,
  72. }
  73. /// <summary>
  74. /// Validation filters.
  75. /// </summary>
  76. public enum FilterValidate : int
  77. {
  78. /// <summary>
  79. /// ID of "int" filter.
  80. /// </summary>
  81. [ImplementsConstant("FILTER_VALIDATE_INT")]
  82. INT = 257,
  83. /// <summary>
  84. /// ID of "boolean" filter.
  85. /// </summary>
  86. [ImplementsConstant("FILTER_VALIDATE_BOOLEAN")]
  87. BOOLEAN = 258,
  88. /// <summary>
  89. /// ID of "float" filter.
  90. /// </summary>
  91. [ImplementsConstant("FILTER_VALIDATE_FLOAT")]
  92. FLOAT = 259,
  93. /// <summary>
  94. /// ID of "validate_regexp" filter.
  95. /// </summary>
  96. [ImplementsConstant("FILTER_VALIDATE_REGEXP")]
  97. REGEXP = 272,
  98. /// <summary>
  99. /// ID of "validate_url" filter.
  100. /// </summary>
  101. [ImplementsConstant("FILTER_VALIDATE_URL")]
  102. URL = 273,
  103. /// <summary>
  104. /// ID of "validate_email" filter.
  105. /// </summary>
  106. [ImplementsConstant("FILTER_VALIDATE_EMAIL")]
  107. EMAIL = 274,
  108. /// <summary>
  109. /// ID of "validate_ip" filter.
  110. /// </summary>
  111. [ImplementsConstant("FILTER_VALIDATE_IP")]
  112. IP = 275,
  113. }
  114. /// <summary>
  115. /// Sanitize filters.
  116. /// </summary>
  117. public enum FilterSanitize : int
  118. {
  119. /// <summary>
  120. /// ID of "string" filter.
  121. /// </summary>
  122. [ImplementsConstant("FILTER_SANITIZE_STRING")]
  123. STRING = 513,
  124. /// <summary>
  125. /// ID of "stripped" filter.
  126. /// </summary>
  127. [ImplementsConstant("FILTER_SANITIZE_STRIPPED")]
  128. STRIPPED = STRING, // alias of FILTER_SANITIZE_STRING
  129. /// <summary>
  130. /// ID of "encoded" filter.
  131. /// </summary>
  132. [ImplementsConstant("FILTER_SANITIZE_ENCODED")]
  133. ENCODED = 514,
  134. /// <summary>
  135. /// ID of "special_chars" filter.
  136. /// </summary>
  137. [ImplementsConstant("FILTER_SANITIZE_SPECIAL_CHARS")]
  138. SPECIAL_CHARS = 515,
  139. /// <summary>
  140. /// ID of "unsafe_raw" filter.
  141. /// </summary>
  142. [ImplementsConstant("FILTER_UNSAFE_RAW")]
  143. FILTER_UNSAFE_RAW = 516,
  144. /// <summary>
  145. /// ID of default ("string") filter.
  146. /// </summary>
  147. [ImplementsConstant("FILTER_DEFAULT")]
  148. FILTER_DEFAULT = FILTER_UNSAFE_RAW, // alias of FILTER_UNSAFE_RAW
  149. /// <summary>
  150. /// ID of "email" filter.
  151. /// Remove all characters except letters, digits and !#$%&amp;'*+-/=?^_`{|}~@.[].
  152. /// </summary>
  153. [ImplementsConstant("FILTER_SANITIZE_EMAIL")]
  154. EMAIL = 517,
  155. /// <summary>
  156. /// ID of "url" filter.
  157. /// </summary>
  158. [ImplementsConstant("FILTER_SANITIZE_URL")]
  159. URL = 518,
  160. /// <summary>
  161. /// ID of "number_int" filter.
  162. /// </summary>
  163. [ImplementsConstant("FILTER_SANITIZE_NUMBER_INT")]
  164. NUMBER_INT = 519,
  165. /// <summary>
  166. /// ID of "number_float" filter.
  167. /// </summary>
  168. [ImplementsConstant("FILTER_SANITIZE_NUMBER_FLOAT")]
  169. NUMBER_FLOAT = 520,
  170. /// <summary>
  171. /// ID of "magic_quotes" filter.
  172. /// </summary>
  173. [ImplementsConstant("FILTER_SANITIZE_MAGIC_QUOTES")]
  174. MAGIC_QUOTES = 521,
  175. }
  176. [Flags]
  177. public enum FilterFlag : int
  178. {
  179. /// <summary>
  180. /// No flags.
  181. /// </summary>
  182. [ImplementsConstant("FILTER_FLAG_NONE")]
  183. NONE = 0,
  184. /// <summary>
  185. /// Allow octal notation (0[0-7]+) in "int" filter.
  186. /// </summary>
  187. [ImplementsConstant("FILTER_FLAG_ALLOW_OCTAL")]
  188. ALLOW_OCTAL = 1,
  189. /// <summary>
  190. /// Allow hex notation (0x[0-9a-fA-F]+) in "int" filter.
  191. /// </summary>
  192. [ImplementsConstant("FILTER_FLAG_ALLOW_HEX")]
  193. ALLOW_HEX = 2,
  194. /// <summary>
  195. /// Strip characters with ASCII value less than 32.
  196. /// </summary>
  197. [ImplementsConstant("FILTER_FLAG_STRIP_LOW")]
  198. STRIP_LOW = 4,
  199. /// <summary>
  200. /// Strip characters with ASCII value greater than 127.
  201. /// </summary>
  202. [ImplementsConstant("FILTER_FLAG_STRIP_HIGH")]
  203. STRIP_HIGH = 8,
  204. /// <summary>
  205. /// Encode characters with ASCII value less than 32.
  206. /// </summary>
  207. [ImplementsConstant("FILTER_FLAG_ENCODE_LOW")]
  208. ENCODE_LOW = 16,
  209. /// <summary>
  210. /// Encode characters with ASCII value greater than 127.
  211. /// </summary>
  212. [ImplementsConstant("FILTER_FLAG_ENCODE_HIGH")]
  213. ENCODE_HIGH = 32,
  214. /// <summary>
  215. /// Encode &amp;.
  216. /// </summary>
  217. [ImplementsConstant("FILTER_FLAG_ENCODE_AMP")]
  218. ENCODE_AMP = 64,
  219. /// <summary>
  220. /// Don't encode ' and ".
  221. /// </summary>
  222. [ImplementsConstant("FILTER_FLAG_NO_ENCODE_QUOTES")]
  223. NO_ENCODE_QUOTES = 128,
  224. /// <summary>
  225. /// ?
  226. /// </summary>
  227. [ImplementsConstant("FILTER_FLAG_EMPTY_STRING_NULL")]
  228. EMPTY_STRING_NULL = 256,
  229. /// <summary>
  230. /// Allow fractional part in "number_float" filter.
  231. /// </summary>
  232. [ImplementsConstant("FILTER_FLAG_ALLOW_FRACTION")]
  233. ALLOW_FRACTION = 4096,
  234. /// <summary>
  235. /// Allow thousand separator (,) in "number_float" filter.
  236. /// </summary>
  237. [ImplementsConstant("FILTER_FLAG_ALLOW_THOUSAND")]
  238. ALLOW_THOUSAND = 8192,
  239. /// <summary>
  240. /// Allow scientific notation (e, E) in "number_float" filter.
  241. /// </summary>
  242. [ImplementsConstant("FILTER_FLAG_ALLOW_SCIENTIFIC")]
  243. ALLOW_SCIENTIFIC = 16384,
  244. /// <summary>
  245. /// Require path in "validate_url" filter.
  246. /// </summary>
  247. [ImplementsConstant("FILTER_FLAG_PATH_REQUIRED")]
  248. PATH_REQUIRED = 262144,
  249. /// <summary>
  250. /// Require query in "validate_url" filter.
  251. /// </summary>
  252. [ImplementsConstant("FILTER_FLAG_QUERY_REQUIRED")]
  253. QUERY_REQUIRED = 524288,
  254. /// <summary>
  255. /// Allow only IPv4 address in "validate_ip" filter.
  256. /// </summary>
  257. [ImplementsConstant("FILTER_FLAG_IPV4")]
  258. IPV4 = 1048576,
  259. /// <summary>
  260. /// Allow only IPv6 address in "validate_ip" filter.
  261. /// </summary>
  262. [ImplementsConstant("FILTER_FLAG_IPV6")]
  263. IPV6 = 2097152,
  264. /// <summary>
  265. /// Deny reserved addresses in "validate_ip" filter.
  266. /// </summary>
  267. [ImplementsConstant("FILTER_FLAG_NO_RES_RANGE")]
  268. NO_RES_RANGE = 4194304,
  269. /// <summary>
  270. /// Deny private addresses in "validate_ip" filter.
  271. /// </summary>
  272. [ImplementsConstant("FILTER_FLAG_NO_PRIV_RANGE")]
  273. NO_PRIV_RANGE = 8388608
  274. }
  275. #endregion
  276. [ImplementsExtension("filter")]
  277. public static class PhpFiltering
  278. {
  279. #region (NS) filter_input_array, filter_var_array, filter_id, filter_list
  280. [ImplementsFunction("filter_input_array", FunctionImplOptions.NotSupported)]
  281. public static object filter_input_array(int type)
  282. {
  283. return filter_input_array(type, null);
  284. }
  285. /// <summary>
  286. /// Gets external variables and optionally filters them.
  287. /// </summary>
  288. [ImplementsFunction("filter_input_array", FunctionImplOptions.NotSupported)]
  289. public static object filter_input_array(int type, object definition)
  290. {
  291. return false;
  292. }
  293. /// <summary>
  294. /// Returns the filter ID belonging to a named filter.
  295. /// </summary>
  296. [ImplementsFunction("filter_id", FunctionImplOptions.NotSupported)]
  297. [return: CastToFalse]
  298. public static int filter_id(string filtername)
  299. {
  300. return -1;
  301. }
  302. /// <summary>
  303. /// Returns a list of all supported filters.
  304. /// </summary>
  305. [ImplementsFunction("filter_list", FunctionImplOptions.NotSupported)]
  306. public static PhpArray/*!*/filter_list()
  307. {
  308. return new PhpArray();
  309. }
  310. [ImplementsFunction("filter_var_array", FunctionImplOptions.NotSupported)]
  311. public static object filter_var_array(PhpArray data)
  312. {
  313. return filter_var_array(data, null);
  314. }
  315. /// <summary>
  316. /// Gets multiple variables and optionally filters them.
  317. /// </summary>
  318. /// <returns></returns>
  319. [ImplementsFunction("filter_var_array", FunctionImplOptions.NotSupported)]
  320. public static object filter_var_array(PhpArray data, object definition)
  321. {
  322. return null;
  323. }
  324. #endregion
  325. #region filter_input
  326. [ImplementsFunction("filter_input")]
  327. public static object filter_input(ScriptContext/*!*/context, FilterInput type, string variable_name)
  328. {
  329. return filter_input(context, type, variable_name, (int)FilterSanitize.FILTER_DEFAULT, null);
  330. }
  331. [ImplementsFunction("filter_input")]
  332. public static object filter_input(ScriptContext/*!*/context, FilterInput type, string variable_name, int filter)
  333. {
  334. return filter_input(context, type, variable_name, filter, null);
  335. }
  336. /// <summary>
  337. /// Gets a specific external variable by name and optionally filters it.
  338. /// </summary>
  339. [ImplementsFunction("filter_input")]
  340. public static object filter_input(ScriptContext/*!*/context, FilterInput type, string variable_name, int filter /*= FILTER_DEFAULT*/ , object options)
  341. {
  342. var arrayobj = GetArrayByInput(context, type);
  343. object value;
  344. if (arrayobj == null || !arrayobj.TryGetValue(variable_name, out value))
  345. return null;
  346. return filter_var(value, filter, options);
  347. }
  348. #endregion
  349. #region filter_var, filter_has_var
  350. /// <summary>
  351. /// Checks if variable of specified type exists
  352. /// </summary>
  353. [ImplementsFunction("filter_has_var")]
  354. public static bool filter_has_var(ScriptContext/*!*/context, FilterInput type, string variable_name)
  355. {
  356. var arrayobj = GetArrayByInput(context, type);
  357. if (arrayobj != null)
  358. return arrayobj.ContainsKey(variable_name);
  359. else
  360. return false;
  361. }
  362. /// <summary>
  363. /// Returns <see cref="PhpArray"/> containing required input.
  364. /// </summary>
  365. /// <param name="context">CUrrent <see cref="ScriptContext"/>.</param>
  366. /// <param name="type"><see cref="FilterInput"/> value.</param>
  367. /// <returns>An instance of <see cref="PhpArray"/> or <c>null</c> if there is no such input.</returns>
  368. private static PhpArray GetArrayByInput(ScriptContext/*!*/context, FilterInput type)
  369. {
  370. object arrayobj = null;
  371. switch (type)
  372. {
  373. case FilterInput.Get:
  374. arrayobj = context.AutoGlobals.Get.Value; break;
  375. case FilterInput.Post:
  376. arrayobj = context.AutoGlobals.Post.Value; break;
  377. case FilterInput.Server:
  378. arrayobj = context.AutoGlobals.Server.Value; break;
  379. case FilterInput.Request:
  380. arrayobj = context.AutoGlobals.Request.Value; break;
  381. case FilterInput.Env:
  382. arrayobj = context.AutoGlobals.Env.Value; break;
  383. case FilterInput.Cookie:
  384. arrayobj = context.AutoGlobals.Cookie.Value; break;
  385. case FilterInput.Session:
  386. arrayobj = context.AutoGlobals.Session.Value; break;
  387. default:
  388. return null;
  389. }
  390. // cast arrayobj to PhpArray if possible:
  391. return PhpArray.AsPhpArray(arrayobj);
  392. }
  393. [ImplementsFunction("filter_var")]
  394. public static object filter_var(object variable)
  395. {
  396. return filter_var(variable, (int)FilterSanitize.FILTER_DEFAULT, null);
  397. }
  398. [ImplementsFunction("filter_var")]
  399. public static object filter_var(object variable, int filter)
  400. {
  401. return filter_var(variable, filter, null);
  402. }
  403. /// <summary>
  404. /// Filters a variable with a specified filter.
  405. /// </summary>
  406. /// <param name="variable">Value to filter.</param>
  407. /// <param name="filter">The ID of the filter to apply.</param>
  408. /// <param name="options">Associative array of options or bitwise disjunction of flags. If filter accepts options, flags can be provided in "flags" field of array. For the "callback" filter, callback type should be passed. The callback must accept one argument, the value to be filtered, and return the value after filtering/sanitizing it.</param>
  409. /// <returns>Returns the filtered data, or <c>false</c> if the filter fails.</returns>
  410. [ImplementsFunction("filter_var")]
  411. public static object filter_var(object variable, int filter /*= FILTER_DEFAULT*/ , object options)
  412. {
  413. switch (filter)
  414. {
  415. //
  416. // SANITIZE
  417. //
  418. case (int)FilterSanitize.FILTER_DEFAULT:
  419. return Core.Convert.ObjectToString(variable);
  420. case (int)FilterSanitize.EMAIL:
  421. // Remove all characters except letters, digits and !#$%&'*+-/=?^_`{|}~@.[].
  422. return FilterSanitizeString(PHP.Core.Convert.ObjectToString(variable), (c) =>
  423. (int)c <= 0x7f && (Char.IsLetterOrDigit(c) ||
  424. c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' ||
  425. c == '*' || c == '+' || c == '-' || c == '/' || c == '=' || c == '!' ||
  426. c == '?' || c == '^' || c == '_' || c == '`' || c == '{' || c == '|' ||
  427. c == '}' || c == '~' || c == '@' || c == '.' || c == '[' || c == ']'));
  428. //
  429. // VALIDATE
  430. //
  431. case (int)FilterValidate.EMAIL:
  432. {
  433. var str = PHP.Core.Convert.ObjectToString(variable);
  434. return RegexUtilities.IsValidEmail(str) ? str : (object)false;
  435. }
  436. case (int)FilterValidate.INT:
  437. {
  438. int result;
  439. if (int.TryParse((PhpVariable.AsString(variable) ?? string.Empty).Trim(), out result))
  440. {
  441. if (options != null) PhpException.ArgumentValueNotSupported("options", "!null");
  442. return result; // TODO: options: min_range, max_range
  443. }
  444. else
  445. return false;
  446. }
  447. case (int)FilterValidate.REGEXP:
  448. {
  449. PhpArray optarray;
  450. // options = options['options']['regexp']
  451. if ((optarray = PhpArray.AsPhpArray(options)) != null &&
  452. optarray.TryGetValue("options", out options) && (optarray = PhpArray.AsPhpArray(options)) != null &&
  453. optarray.TryGetValue("regexp", out options))
  454. {
  455. if (PerlRegExp.Match(options, variable) > 0)
  456. return variable;
  457. }
  458. else
  459. PhpException.InvalidArgument("options", LibResources.GetString("option_missing", "regexp"));
  460. return false;
  461. }
  462. default:
  463. PhpException.ArgumentValueNotSupported("filter", filter);
  464. break;
  465. }
  466. return false;
  467. }
  468. #endregion
  469. #region Helper filter methods
  470. private static class RegexUtilities
  471. {
  472. private static readonly Regex ValidEmailRegex = new Regex(
  473. @"^(?("")(""[^""]+?""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
  474. @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$",
  475. RegexOptions.IgnoreCase | RegexOptions.Compiled);
  476. public static bool IsValidEmail(string strIn)
  477. {
  478. if (String.IsNullOrEmpty(strIn) || strIn.Length > 320)
  479. return false;
  480. // Use IdnMapping class to convert Unicode domain names.
  481. try
  482. {
  483. strIn = Regex.Replace(strIn, @"(@)(.+)$", DomainMapper);
  484. }
  485. catch (ArgumentException)
  486. {
  487. return false;
  488. }
  489. // Return true if strIn is in valid e-mail format.
  490. return ValidEmailRegex.IsMatch(strIn);
  491. }
  492. private static string DomainMapper(Match match)
  493. {
  494. // IdnMapping class with default property values.
  495. var idn = new System.Globalization.IdnMapping();
  496. string domainName = match.Groups[2].Value;
  497. //try
  498. //{
  499. domainName = idn.GetAscii(domainName);
  500. //}
  501. //catch (ArgumentException)
  502. //{
  503. // invalid = true;
  504. //}
  505. return match.Groups[1].Value + domainName;
  506. }
  507. }
  508. /// <summary>
  509. /// Remove all characters not valid by given <paramref name="predicate"/>.
  510. /// </summary>
  511. private static string FilterSanitizeString(string str, Predicate<char>/*!*/predicate)
  512. {
  513. Debug.Assert(predicate != null);
  514. // nothing to sanitize:
  515. if (string.IsNullOrEmpty(str)) return string.Empty;
  516. // check if all the characters are valid first:
  517. bool allvalid = true;
  518. foreach (var c in str)
  519. if (!predicate(c))
  520. {
  521. allvalid = false;
  522. break;
  523. }
  524. if (allvalid)
  525. {
  526. return str;
  527. }
  528. else
  529. {
  530. // remove not allowed characters:
  531. StringBuilder newstr = new StringBuilder(str.Length);
  532. foreach (char c in str)
  533. if (predicate(c))
  534. newstr.Append(c);
  535. return newstr.ToString();
  536. }
  537. }
  538. #endregion
  539. }
  540. }