PageRenderTime 67ms CodeModel.GetById 40ms RepoModel.GetById 0ms app.codeStats 0ms

/htrouter/Classes/Module/Rewrite/Condition.php

http://github.com/jaytaph/HTRouter
PHP | 281 lines | 191 code | 48 blank | 42 comment | 33 complexity | 53d7bac7dc46ea7e084df4e1fbe6314b MD5 | raw file
  1. <?php
  2. namespace HTRouter\Module\Rewrite;
  3. class Condition {
  4. // // TestString types
  5. // const TYPE_UNKNOWN = 0;
  6. // const TYPE_RULE_BACKREF = 1;
  7. // const TYPE_COND_BACKREF = 2;
  8. // const TYPE_SERVER = 3;
  9. // const TYPE_SPECIAL = 4;
  10. // Conditional Pattern types
  11. const COND_UNKNOWN = 0;
  12. const COND_REGEX = 1;
  13. const COND_LEXICAL_PRE = 2;
  14. const COND_LEXICAL_POST = 3;
  15. const COND_LEXICAL_EQ = 4;
  16. const COND_TEST_DIR = 5;
  17. const COND_TEST_FILE = 6;
  18. const COND_TEST_SIZE = 7;
  19. const COND_TEST_SYMLINK = 8;
  20. const COND_TEST_EXECUTE = 9;
  21. const COND_TEST_FILE_SUBREQ = 10;
  22. const COND_TEST_URL_SUBREQ = 11;
  23. protected $_specialTypes = array('IS_SUBREQ', 'API_VERSION', 'THE_REQUEST', 'REQUEST_URI', 'REQUEST_FILENAME', 'HTTPS');
  24. protected $_specialServer = array('HTTP_USER_AGENT','HTTP_REFERER','HTTP_COOKIE','HTTP_FORWARDED','HTTP_HOST','HTTP_PROXY_CONNECTION','HTTP_ACCEPT',
  25. 'REMOTE_ADDR','REMOTE_HOST','REMOTE_PORT','REMOTE_USER','REMOTE_IDENT','REQUEST_METHOD','SCRIPT_FILENAME','PATH_INFO','QUERY_STRING','AUTH_TYPE',
  26. 'DOCUMENT_ROOT','SERVER_ADMIN','SERVER_NAME','SERVER_ADDR','SERVER_PORT','SERVER_PROTOCOL','SERVER_SOFTWARE',
  27. 'TIME_YEAR','TIME_MON','TIME_DAY','TIME_HOUR','TIME_MIN','TIME_SEC','TIME_WDAY','TIME');
  28. protected $_testString;
  29. protected $_condPattern;
  30. protected $_condPatternType;
  31. protected $_condPatternNegate;
  32. protected $_matches = array(); // The last matches found when parsing the condition
  33. /**
  34. * @var null|\HTRouter\Module\Rewrite\Rule
  35. */
  36. protected $_rule = null;
  37. protected $_match = null; // True when the condition has matched before already.
  38. function __construct($testString, $condPattern, $flags = '') {
  39. // Set default values
  40. $this->_testString = $testString;
  41. $this->_condPattern = $condPattern;
  42. $this->_condPatternType = self::COND_UNKNOWN;
  43. $this->_condPatternNegate = false;
  44. $this->_condPatternNocase = false;
  45. $this->_condPatternOr = false;
  46. $this->_flags = array();
  47. // Parse string and condition (throws error on fault)
  48. $this->_parseCondPattern($condPattern);
  49. $this->_parseFlags($flags);
  50. }
  51. function __toString() {
  52. $ret = $this->_testString." ".($this->_condPatternNegate?"!":"").$this->_condPattern;
  53. if (count($this->_flags)) $ret .= " [".join(", ", $this->_flags)."]";
  54. return $ret;
  55. }
  56. protected function _parseCondPattern($condPattern) {
  57. if (empty($condPattern)) {
  58. throw new \InvalidArgumentException("CondPattern must not be empty!");
  59. }
  60. // Check if its a negative condition
  61. if ($condPattern[0] == "!") {
  62. // CondPattern argument must be modified as well!
  63. $condPattern = $this->_condPattern = substr($condPattern, 1);
  64. $this->_condPatternNegate = true;
  65. }
  66. // It's a regex unless we decide otherwise
  67. $this->_condPatternType = self::COND_REGEX;
  68. if ($condPattern[0] == "<" && strlen($condPattern) > 1) {
  69. $this->_condPattern = substr($condPattern, 1);
  70. $this->_condPatternType = self::COND_LEXICAL_PRE;
  71. } elseif ($condPattern[0] == ">" and strlen($condPattern) > 1) {
  72. $this->_condPattern = substr($condPattern, 1);
  73. $this->_condPatternType = self::COND_LEXICAL_POST;
  74. } elseif ($condPattern[0] == "=" and strlen($condPattern) > 1) {
  75. $this->_condPattern = substr($condPattern, 1);
  76. $this->_condPatternType = self::COND_LEXICAL_EQ;
  77. } elseif ($condPattern[0] == "-" and strlen($condPattern) == 2) {
  78. switch ($condPattern) {
  79. case "-d" :
  80. $this->_condPatternType = self::COND_TEST_DIR;
  81. break;
  82. case "-f" :
  83. $this->_condPatternType = self::COND_TEST_FILE;
  84. break;
  85. case "-s" :
  86. $this->_condPatternType = self::COND_TEST_SIZE;
  87. break;
  88. case "-l" :
  89. $this->_condPatternType = self::COND_TEST_SYMLINK;
  90. break;
  91. case "-x" :
  92. $this->_condPatternType = self::COND_TEST_EXECUTE;
  93. break;
  94. case "-F" :
  95. $this->_condPatternType = self::COND_TEST_FILE_SUBREQ;
  96. break;
  97. case "-U" :
  98. $this->_condPatternType = self::COND_TEST_URL_SUBREQ;
  99. break;
  100. }
  101. }
  102. }
  103. protected function _parseFlags($flags) {
  104. if (empty($flags)) return;
  105. // Check for brackets
  106. if ($flags[0] != '[' && $flags[strlen($flags)-1] != ']') {
  107. throw new \InvalidArgumentException("Flags must be bracketed");
  108. }
  109. // Remove brackets
  110. $flags = substr($flags, 1, -1);
  111. foreach (explode(",",$flags) as $flag) {
  112. $flag = trim($flag);
  113. switch (strtolower($flag)) {
  114. case "nocase" :
  115. case "nc" :
  116. $this->_flags[] = new Flag(Flag::TYPE_NOCASE, null, null);
  117. break;
  118. case "ornext" :
  119. case "or" :
  120. $this->_flags[] = new Flag(Flag::TYPE_ORNEXT, null, null);
  121. break;
  122. case "novary" :
  123. case "nv" :
  124. $this->_flags[] = new Flag(Flag::TYPE_NOVARY, null, null);
  125. break;
  126. default :
  127. throw new \InvalidArgumentException("Unknown condition flag: $flag");
  128. break;
  129. }
  130. }
  131. }
  132. function hasFlag($type) {
  133. return ($this->getFlag($type) != null);
  134. }
  135. /**
  136. * @return array \HTRouter\Module\Rewrite\Flag
  137. */
  138. function getFlags() {
  139. return $this->_flags;
  140. }
  141. function getFlag($type) {
  142. foreach ($this->getFlags() as $flag) {
  143. /**
  144. * @var $flag \HTRouter\Module\Rewrite\Flag
  145. */
  146. if ($flag->getType() == $type) {
  147. return $flag;
  148. }
  149. }
  150. return null;
  151. }
  152. /**
  153. * Returns true if the condition matches, false otherwise. We don't mind non-deterministic conditions like TIME_*
  154. *
  155. * @return bool
  156. */
  157. public function matches($request) {
  158. if ($this->_match == null) {
  159. // Cache it
  160. $this->_match = $this->_checkMatch($request);
  161. }
  162. return $this->_match;
  163. }
  164. function getLastMatches() {
  165. return $this->_matches;
  166. }
  167. /**
  168. * Actual workload of condition matching
  169. * @return bool
  170. */
  171. protected function _checkMatch($request) {
  172. $expanded = Rule::expandSubstitutions($this->_testString, $request);
  173. $match = false;
  174. // Check expanded string against conditional Pattern
  175. switch ($this->_condPatternType) {
  176. case self::COND_REGEX :
  177. $regex = '|'.$this->_condPattern.'|'; // Don't separate with / since it will be used a path delimiter
  178. // Case independent if needed
  179. if ($this->hasFlag(Flag::TYPE_NOCASE)) {
  180. $regex .= "i";
  181. }
  182. // Check regex
  183. $match = (preg_match($regex, $expanded, $matches) >= 1);
  184. // Store matches in case we need to do back references %N inside rules
  185. $this->_matches = $matches;
  186. break;
  187. case self::COND_LEXICAL_PRE :
  188. // PRE and POST lexical match does not follow the nocase fields!
  189. $sub = substr($expanded, 0, strlen($this->_condPattern));
  190. $match = (strcmp($sub, $this->_condPattern) == 0);
  191. break;
  192. case self::COND_LEXICAL_POST :
  193. // PRE and POST lexical match does not follow the nocase fields!
  194. $sub = substr($expanded, 0-strlen($this->_condPattern));
  195. $match = (strcmp($sub, $this->_condPattern) == 0);
  196. break;
  197. case self::COND_LEXICAL_EQ :
  198. if ($this->hasFlag(Flag::TYPE_NOCASE)) {
  199. $match = (strcasecmp($this->_condPattern, $expanded) == 0);
  200. } else {
  201. $match = (strcmp($this->_condPattern, $expanded) == 0);
  202. }
  203. break;
  204. case self::COND_TEST_DIR :
  205. $match = is_dir($expanded);
  206. break;
  207. case self::COND_TEST_FILE :
  208. $match = is_file($expanded);
  209. break;
  210. case self::COND_TEST_SIZE :
  211. $match = (is_file($expanded) && filesize($expanded) > 0);
  212. break;
  213. case self::COND_TEST_SYMLINK :
  214. $match = is_link($expanded);
  215. break;
  216. case self::COND_TEST_EXECUTE :
  217. $match = is_executable($expanded);
  218. break;
  219. case self::COND_TEST_FILE_SUBREQ :
  220. // @TODO: What to do?
  221. break;
  222. case self::COND_TEST_URL_SUBREQ :
  223. // @TODO: What to do?
  224. break;
  225. }
  226. // If the match must be negated, make it so
  227. if ($this->_condPatternNegate) {
  228. $match = ! $match;
  229. }
  230. \HTRouter::getInstance()->getLogger()->log(\HTRouter\Logger::ERRORLEVEL_DEBUG, "Conditional match of ".(string)$this." : ".($match ? "yes" : "no"));
  231. return $match;
  232. }
  233. }