View file File name : sqli.php Content :<?php if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) { require_once dirname(__FILE__) . '/lexer.php'; class wfWAFSQLiParser extends wfWAFBaseParser { const FLAG_PARSE_MYSQL_PORTABLE_COMMENTS = wfWAFSQLiLexer::FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS; /** * @param string $param * @return bool */ public static function testForSQLi($param) { static $instance; static $tests; if (!$instance) { $instance = new self(new wfWAFSQLiLexer()); } if (!$tests) { // SQL statement and token count for lexer $tests = array( array('%s', 1), array('SELECT * FROM t WHERE i = %s ', 7), array("SELECT * FROM t WHERE i = '%s' ", 7), array('SELECT * FROM t WHERE i = "%s" ', 7), array('SELECT * FROM t WHERE i = (%s) ', 9), array("SELECT * FROM t WHERE i = ('%s') ", 9), array('SELECT * FROM t WHERE i = ("%s") ', 9), array('SELECT * FROM t WHERE i = ((%s)) ', 11), array("SELECT * FROM t WHERE i = (('%s')) ", 11), array('SELECT * FROM t WHERE i = (("%s")) ', 11), array('SELECT * FROM t WHERE i = (((%s))) ', 13), array("SELECT * FROM t WHERE i = ((('%s'))) ", 13), array('SELECT * FROM t WHERE i = ((("%s"))) ', 13), array('SELECT * FROM t WHERE i = %s and j = (1 ) ', 12), array("SELECT * FROM t WHERE i = '%s' and j = (1 ) ", 12), array('SELECT * FROM t WHERE i = "%s" and j = (1 ) ', 12), array('SELECT MATCH(t) AGAINST (%s) from t ', 10), array("SELECT MATCH(t) AGAINST ('%s') from t ", 10), array('SELECT MATCH(t) AGAINST ("%s") from t ', 10), // array('SELECT CASE WHEN %s THEN 1 ELSE 0 END from t ', 8), // array("SELECT CASE WHEN '%s' THEN 1 ELSE 0 END from t ", 8), // array('SELECT CASE WHEN "%s" THEN 1 ELSE 0 END from t ', 8), // // array('SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END) from t ', 12), // array("SELECT (CASE WHEN ('%s') THEN 1 ELSE 0 END) from t ", 12), // array('SELECT (CASE WHEN ("%s") THEN 1 ELSE 0 END) from t ', 12), array('SELECT * FROM (select %s) ', 6), array("SELECT * FROM (select '%s') ", 6), array('SELECT * FROM (select "%s") ', 6), array('SELECT * FROM (select (%s)) ', 8), array("SELECT * FROM (select ('%s')) ", 8), array('SELECT * FROM (select ("%s")) ', 8), array('SELECT * FROM (select ((%s))) ', 10), array("SELECT * FROM (select (('%s'))) ", 10), array('SELECT * FROM (select (("%s"))) ', 10), // // array('SELECT * FROM t JOIN t2 on i = %s ', 9), // array("SELECT * FROM t JOIN t2 on i = '%s' ", 9), // array('SELECT * FROM t JOIN t2 on i = "%s" ', 9), // array('SELECT * FROM t JOIN t2 on i = (%s) ', 11), // array("SELECT * FROM t JOIN t2 on i = ('%s') ", 11), // array('SELECT * FROM t JOIN t2 on i = ("%s") ', 11), // array('SELECT * FROM t JOIN t2 on i = ((%s)) ', 13), // array("SELECT * FROM t JOIN t2 on i = (('%s')) ", 13), // array('SELECT * FROM t JOIN t2 on i = (("%s")) ', 13), // array('SELECT * FROM t JOIN t2 on i = (((%s))) ', 15), // array("SELECT * FROM t JOIN t2 on i = ((('%s'))) ", 15), // array('SELECT * FROM t JOIN t2 on i = ((("%s"))) ', 15), array('SELECT * FROM %s ', 3), array('INSERT INTO t (col) VALUES (%s) ', 9), array("INSERT INTO t (col) VALUES ('%s') ", 9), array('INSERT INTO t (col) VALUES ("%s") ', 9), array('UPDATE t1 SET col1 = %s ', 5), array('UPDATE t1 SET col1 = \'%s\' ', 5), ); } $lexerFlags = array(0, wfWAFSQLiLexer::FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS); foreach ($lexerFlags as $flags) { foreach ($tests as $test) { // $startTime = microtime(true); list($sql, $expectedTokenCount) = $test; try { $instance->setFlags($flags); $instance->setSubject(sprintf($sql, $param)); if (($instance->hasMoreThanNumTokens($expectedTokenCount) && $instance->evaluate()) || $instance->hasMultiplePortableCommentVersions()) { // printf("%s took %f seconds\n", $sql, microtime(true) - $startTime); return true; } // printf("%s took %f seconds\n", $sql, microtime(true) - $startTime); } catch (wfWAFParserSyntaxError $e) { } } } return false; } private $subject; /** * @var int */ private $flags; /** @var wfWAFSQLiLexer */ protected $lexer; private $portableCommentVersions = array(); private $intervalUnits = array( 'SECOND', 'MINUTE', 'HOUR', 'DAY_SYM', 'WEEK', 'MONTH', 'QUARTER', 'YEAR', 'SECOND_MICROSECOND', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'HOUR_MICROSECOND', 'HOUR_SECOND', 'HOUR_MINUTE', 'DAY_MICROSECOND', 'DAY_SECOND', 'DAY_MINUTE', 'DAY_HOUR', 'YEAR_MONTH', ); private $reservedWords = array( "_FILENAME", "ACCESSIBLE", "ADD", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ASENSITIVE", "BEFORE", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOTH", "BY", "CALL", "CASCADE", "CASE", "CHANGE", "CHAR", "CHARACTER", "CHECK", "COLLATE", "COLUMN", "CONDITION", "CONSTRAINT", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DATABASES", "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", "DAY_SECOND", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELAYED", "DELETE", "DESC", "DESCRIBE", "DETERMINISTIC", "DISTINCT", "DISTINCTROW", "DIV", "DOUBLE", "DROP", // "DUAL", // works as a table name ??? "EACH", "ELSE", "ELSEIF", "ENCLOSED", "ESCAPED", "EXISTS", "EXIT", "EXPLAIN", "FALSE", "FETCH", "FLOAT", "FLOAT4", "FLOAT8", "FOR", "FORCE", "FOREIGN", "FROM", "FULLTEXT", "GRANT", "GROUP", "HAVING", "HIGH_PRIORITY", "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", "IF", "IGNORE", "IN", "INDEX", "INFILE", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INT1", "INT2", "INT3", "INT4", "INT8", "INTEGER", "INTERVAL", "INTO", "IS", "ITERATE", "JOIN", "KEY", "KEYS", "KILL", "LEADING", "LEAVE", "LEFT", "LIKE", "LIMIT", "LINEAR", "LINES", "LOAD", "LOCALTIME", "LOCALTIMESTAMP", "LOCK", "LONG", "LONGBLOB", "LONGTEXT", "LOOP", "LOW_PRIORITY", "MASTER_SSL_VERIFY_SERVER_CERT", "MATCH", "MAXVALUE", "MEDIUMBLOB", "MEDIUMINT", "MEDIUMTEXT", "MIDDLEINT", "MINUTE_MICROSECOND", "MINUTE_SECOND", "MOD", "MODIFIES", "NATURAL", "NOT", "NO_WRITE_TO_BINLOG", "NULL", "NUMERIC", "ON", "OPTIMIZE", "OPTION", "OPTIONALLY", "OR", "ORDER", "OUT", "OUTER", "OUTFILE", "PRECISION", "PRIMARY", "PROCEDURE", "PURGE", "RANGE", "READ", "READS", "READ_WRITE", "REAL", "REFERENCES", "REGEXP", "RELEASE", "RENAME", "REPEAT", "REPLACE", "REQUIRE", "RESIGNAL", "RESTRICT", "RETURN", "REVOKE", "RIGHT", "RLIKE", "SCHEMA", "SCHEMAS", "SECOND_MICROSECOND", "SELECT", "SENSITIVE", "SEPARATOR", "SET", "SHOW", "SIGNAL", "SMALLINT", "SPATIAL", "SPECIFIC", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQL_BIG_RESULT", "SQL_CALC_FOUND_ROWS", "SQL_SMALL_RESULT", "SSL", "STARTING", "STRAIGHT_JOIN", "TABLE", "TERMINATED", "THEN", "TINYBLOB", "TINYINT", "TINYTEXT", "TO", "TRAILING", "TRIGGER", "TRUE", "UNDO", "UNION", "UNIQUE", "UNLOCK", "UNSIGNED", "UPDATE", "USAGE", "USE", "USING", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", "VALUES", "VARBINARY", "VARCHAR", "VARCHARACTER", "VARYING", "WHEN", "WHERE", "WHILE", "WITH", "WRITE", "XOR", "YEAR_MONTH", "ZEROFILL", ); private $keywords = array( "ACCESSIBLE", "ACTION", "ADD", "AFTER", "AGAINST", "AGGREGATE", "ALGORITHM", "ALL", "ALTER", "ANALYZE", "AND", "ANY", "AS", "ASC", "ASCII", "ASENSITIVE", "AT", "AUTHORS", "AUTOEXTEND_SIZE", "AUTO_INCREMENT", "AVG", "AVG_ROW_LENGTH", "BACKUP", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BINLOG", "BIT", "BLOB", "BLOCK", "BOOL", "BOOLEAN", "BOTH", "BTREE", "BY", "BYTE", "CACHE", "CALL", "CASCADE", "CASCADED", "CASE", "CATALOG_NAME", "CHAIN", "CHANGE", "CHANGED", "CHAR", "CHARACTER", "CHARSET", "CHECK", "CHECKSUM", "CIPHER", "CLASS_ORIGIN", "CLIENT", "CLOSE", "COALESCE", "CODE", "COLLATE", "COLLATION", "COLUMN", "COLUMNS", "COLUMN_NAME", "COMMENT", "COMMIT", "COMMITTED", "COMPACT", "COMPLETION", "COMPRESSED", "CONCURRENT", "CONDITION", "CONNECTION", "CONSISTENT", "CONSTRAINT", "CONSTRAINT_CATALOG", "CONSTRAINT_NAME", "CONSTRAINT_SCHEMA", "CONTAINS", "CONTEXT", "CONTINUE", "CONTRIBUTORS", "CONVERT", "CPU", "CREATE", "CROSS", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "CURSOR_NAME", "DATA", "DATABASE", "DATABASES", "DATAFILE", "DATE", "DATETIME", "DAY", "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", "DAY_SECOND", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFINER", "DELAYED", "DELAY_KEY_WRITE", "DELETE", "DESC", "DESCRIBE", "DES_KEY_FILE", "DETERMINISTIC", "DIRECTORY", "DISABLE", "DISCARD", "DISK", "DISTINCT", "DISTINCTROW", "DIV", "DO", "DOUBLE", "DROP", "DUAL", "DUMPFILE", "DUPLICATE", "DYNAMIC", "EACH", "ELSE", "ELSEIF", "ENABLE", "ENCLOSED", "END", "ENDS", "ENGINE", "ENGINES", "ENUM", "ERROR", "ERRORS", "ESCAPE", "ESCAPED", "EVENT", "EVENTS", "EVERY", "EXECUTE", "EXISTS", "EXIT", "EXPANSION", "EXPLAIN", "EXTENDED", "EXTENT_SIZE", "FALSE", "FAST", "FAULTS", "FETCH", "FIELDS", "FILE", "FIRST", "FIXED", "FLOAT", "FLOAT4", "FLOAT8", "FLUSH", "FOR", "FORCE", "FOREIGN", "FOUND", "FRAC_SECOND", "FROM", "FULL", "FULLTEXT", "FUNCTION", "GENERAL", "GEOMETRY", "GEOMETRYCOLLECTION", "GET_FORMAT", "GLOBAL", "GRANT", "GRANTS", "GROUP", "HANDLER", "HASH", "HAVING", "HELP", "HIGH_PRIORITY", "HOST", "HOSTS", "HOUR", "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", "IDENTIFIED", "IF", "IGNORE", "IGNORE_SERVER_IDS", "IMPORT", "IN", "INDEX", "INDEXES", "INFILE", "INITIAL_SIZE", "INNER", "INNOBASE", "INNODB", "INOUT", "INSENSITIVE", "INSERT", "INSERT_METHOD", "INSTALL", "INT", "INT1", "INT2", "INT3", "INT4", "INT8", "INTEGER", "INTERVAL", "INTO", "INVOKER", "IO", "IO_THREAD", "IPC", "IS", "ISOLATION", "ISSUER", "ITERATE", "JOIN", "KEY", "KEYS", "KEY_BLOCK_SIZE", "KILL", "LANGUAGE", "LAST", "LEADING", "LEAVE", "LEAVES", "LEFT", "LESS", "LEVEL", "LIKE", "LIMIT", "LINEAR", "LINES", "LINESTRING", "LIST", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCK", "LOCKS", "LOGFILE", "LOGS", "LONG", "LONGBLOB", "LONGTEXT", "LOOP", "LOW_PRIORITY", "MASTER", "MASTER_CONNECT_RETRY", "MASTER_HEARTBEAT_PERIOD", "MASTER_HOST", "MASTER_LOG_FILE", "MASTER_LOG_POS", "MASTER_PASSWORD", "MASTER_PORT", "MASTER_SERVER_ID", "MASTER_SSL", "MASTER_SSL_CA", "MASTER_SSL_CAPATH", "MASTER_SSL_CERT", "MASTER_SSL_CIPHER", "MASTER_SSL_KEY", "MASTER_SSL_VERIFY_SERVER_CERT", "MASTER_USER", "MATCH", "MAXVALUE", "MAX_CONNECTIONS_PER_HOUR", "MAX_QUERIES_PER_HOUR", "MAX_ROWS", "MAX_SIZE", "MAX_UPDATES_PER_HOUR", "MAX_USER_CONNECTIONS", "MEDIUM", "MEDIUMBLOB", "MEDIUMINT", "MEDIUMTEXT", "MEMORY", "MERGE", "MESSAGE_TEXT", "MICROSECOND", "MIDDLEINT", "MIGRATE", "MINUTE", "MINUTE_MICROSECOND", "MINUTE_SECOND", "MIN_ROWS", "MOD", "MODE", "MODIFIES", "MODIFY", "MONTH", "MULTILINESTRING", "MULTIPOINT", "MULTIPOLYGON", "MUTEX", "MYSQL_ERRNO", "NAME", "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NDB", "NDBCLUSTER", "NEW", "NEXT", "NO", "NODEGROUP", "NONE", "NOT", "NO_WAIT", "NO_WRITE_TO_BINLOG", "NULL", "NUMERIC", "NVARCHAR", "OFFSET", "OLD_PASSWORD", "ON", "ONE", "ONE_SHOT", "OPEN", "OPTIMIZE", "OPTION", "OPTIONALLY", "OPTIONS", "OR", "ORDER", "OUT", "OUTER", "OUTFILE", "OWNER", "PACK_KEYS", "PAGE", "PARSER", "PARTIAL", "PARTITION", "PARTITIONING", "PARTITIONS", "PASSWORD", "PHASE", "PLUGIN", "PLUGINS", "POINT", "POLYGON", "PORT", "PRECISION", "PREPARE", "PRESERVE", "PREV", "PRIMARY", "PRIVILEGES", "PROCEDURE", "PROCESSLIST", "PROFILE", "PROFILES", "PROXY", "PURGE", "QUARTER", "QUERY", "QUICK", "RANGE", "READ", "READS", "READ_ONLY", "READ_WRITE", "REAL", "REBUILD", "RECOVER", "REDOFILE", "REDO_BUFFER_SIZE", "REDUNDANT", "REFERENCES", "REGEXP", "RELAY", "RELAYLOG", "RELAY_LOG_FILE", "RELAY_LOG_POS", "RELAY_THREAD", "RELEASE", "RELOAD", "REMOVE", "RENAME", "REORGANIZE", "REPAIR", "REPEAT", "REPEATABLE", "REPLACE", "REPLICATION", "REQUIRE", "RESET", "RESIGNAL", "RESTORE", "RESTRICT", "RESUME", "RETURN", "RETURNS", "REVOKE", "RIGHT", "RLIKE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWS", "ROW_FORMAT", "RTREE", "SAVEPOINT", "SCHEDULE", "SCHEMA", "SCHEMAS", "SCHEMA_NAME", "SECOND", "SECOND_MICROSECOND", "SECURITY", "SELECT", "SENSITIVE", "SEPARATOR", "SERIAL", "SERIALIZABLE", "SERVER", "SESSION", "SET", "SHARE", "SHOW", "SHUTDOWN", "SIGNAL", "SIGNED", "SIMPLE", "SLAVE", "SLOW", "SMALLINT", "SNAPSHOT", "SOCKET", "SOME", "SONAME", "SOUNDS", "SOURCE", "SPATIAL", "SPECIFIC", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQL_BIG_RESULT", "SQL_BUFFER_RESULT", "SQL_CACHE", "SQL_CALC_FOUND_ROWS", "SQL_NO_CACHE", "SQL_SMALL_RESULT", "SQL_THREAD", "SQL_TSI_DAY", "SQL_TSI_FRAC_SECOND", "SQL_TSI_HOUR", "SQL_TSI_MINUTE", "SQL_TSI_MONTH", "SQL_TSI_QUARTER", "SQL_TSI_SECOND", "SQL_TSI_WEEK", "SQL_TSI_YEAR", "SSL", "START", "STARTING", "STARTS", "STATUS", "STOP", "STORAGE", "STRAIGHT_JOIN", "STRING", "SUBCLASS_ORIGIN", "SUBJECT", "SUBPARTITION", "SUBPARTITIONS", "SUPER", "SUSPEND", "SWAPS", "SWITCHES", "TABLE", "TABLES", "TABLESPACE", "TABLE_CHECKSUM", "TABLE_NAME", "TEMPORARY", "TEMPTABLE", "TERMINATED", "TEXT", "THAN", "THEN", "TIME", "TIMESTAMP", "TIMESTAMPADD", "TIMESTAMPDIFF", "TINYBLOB", "TINYINT", "TINYTEXT", "TO", "TRAILING", "TRANSACTION", "TRIGGER", "TRIGGERS", "TRUE", "TRUNCATE", "TYPE", "TYPES", "UNCOMMITTED", "UNDEFINED", "UNDO", "UNDOFILE", "UNDO_BUFFER_SIZE", "UNICODE", "UNINSTALL", "UNION", "UNIQUE", "UNKNOWN", "UNLOCK", "UNSIGNED", "UNTIL", "UPDATE", "UPGRADE", "USAGE", "USE", "USER", "USER_RESOURCES", "USE_FRM", "USING", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", "VALUE", "VALUES", "VARBINARY", "VARCHAR", "VARCHARACTER", "VARIABLES", "VARYING", "VIEW", "WAIT", "WARNINGS", "WEEK", "WHEN", "WHERE", "WHILE", "WITH", "WORK", "WRAPPER", "WRITE", "X509", "XA", "XML", "XOR", "YEAR", "YEAR_MONTH", "ZEROFILL", ); private $numberFunctions = array( 'ABS', 'ACOS', 'ASIN', 'ATAN2', 'ATAN', 'CEIL', 'CEILING', 'CONV', 'COS', 'COT', 'CRC32', 'DEGREES', 'EXP', 'FLOOR', 'LN', 'LOG10', 'LOG2', 'LOG', 'MOD', 'PI', 'POW', 'POWER', 'RADIANS', 'RAND', 'ROUND', 'SIGN', 'SIN', 'SQRT', 'TAN', 'TRUNCATE', ); private $charFunctions = array( 'ASCII_SYM', 'BIN', 'BIT_LENGTH', 'CHAR_LENGTH', 'CHAR', 'CONCAT_WS', 'CONCAT', 'ELT', 'EXPORT_SET', 'FIELD', 'FIND_IN_SET', 'FORMAT', 'FROM_BASE64', 'HEX', 'INSERT', 'INSTR', 'LEFT', 'LENGTH', 'LOAD_FILE', 'LOCATE', 'LOWER', 'LPAD', 'LTRIM', 'MAKE_SET', 'MID', 'OCT', 'ORD', 'QUOTE', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'RPAD', 'RTRIM', 'SOUNDEX', 'SPACE', 'STRCMP', 'SUBSTRING_INDEX', 'SUBSTRING', 'TO_BASE64', 'TRIM', 'UNHEX', 'UPPER', 'WEIGHT_STRING', ); private $timeFunctions = array( 'ADDDATE', 'ADDTIME', 'CONVERT_TZ', 'CURDATE', 'CURTIME', 'DATE_ADD', 'DATE_FORMAT', 'DATE_SUB', 'DATE_SYM', 'DATEDIFF', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 'EXTRACT', 'FROM_DAYS', 'FROM_UNIXTIME', 'GET_FORMAT', 'HOUR', 'LAST_DAY ', 'MAKEDATE', 'MAKETIME ', 'MICROSECOND', 'MINUTE', 'MONTH', 'MONTHNAME', 'NOW', 'PERIOD_ADD', 'PERIOD_DIFF', 'QUARTER', 'SEC_TO_TIME', 'SECOND', 'STR_TO_DATE', 'SUBTIME', 'SYSDATE', 'TIME_FORMAT', 'TIME_TO_SEC', 'TIME_SYM', 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TO_DAYS', 'TO_SECONDS', 'UNIX_TIMESTAMP', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'YEAR', 'YEARWEEK', ); private $otherFunctions = array( 'MAKE_SET', 'LOAD_FILE', 'IF', 'IFNULL', 'AES_ENCRYPT', 'AES_DECRYPT', 'DECODE', 'ENCODE', 'DES_DECRYPT', 'DES_ENCRYPT', 'ENCRYPT', 'MD5', 'OLD_PASSWORD', 'PASSWORD', 'BENCHMARK', 'CHARSET', 'COERCIBILITY', 'COLLATION', 'CONNECTION_ID', 'CURRENT_USER', 'DATABASE', 'SCHEMA', 'USER', 'SESSION_USER', 'SYSTEM_USER', 'VERSION_SYM', 'FOUND_ROWS', 'LAST_INSERT_ID', 'DEFAULT', 'GET_LOCK', 'RELEASE_LOCK', 'IS_FREE_LOCK', 'IS_USED_LOCK', 'MASTER_POS_WAIT', 'INET_ATON', 'INET_NTOA', 'NAME_CONST', 'SLEEP', 'UUID', 'VALUES', ); private $groupFunctions = array( 'AVG', 'COUNT', 'MAX_SYM', 'MIN_SYM', 'SUM', 'BIT_AND', 'BIT_OR', 'BIT_XOR', 'GROUP_CONCAT', 'STD', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 'VAR_POP', 'VAR_SAMP', 'VARIANCE', ); /** * @param wfWAFSQLiLexer $lexer * @param string $subject * @param int $flags */ public function __construct($lexer, $subject = null, $flags = 0) { parent::__construct($lexer); $this->setSubject($subject); $this->setFlags($flags); } protected function _init() { $this->portableCommentVersions = array(); $this->index = -1; } /** * @param int $num * @return bool */ public function hasMoreThanNumTokens($num) { $this->_init(); $savePoint = $this->index; for ($i = 0; $i <= $num;) { $token=$this->nextToken(); if($token){ if(!$this->lexer->isValueLiteral($token->getType())) $i++; } else{ $this->index = $savePoint; return false; } } $this->index = $savePoint; return true; } /** * @return bool */ public function evaluate() { try { $this->parse(); return true; } catch (wfWAFParserSyntaxError $e) { return false; } } public function parse() { $this->_init(); if ( $this->parseSelectStatement() || $this->parseInsertStatement() || $this->parseUpdateStatement() // || $this->parseDeleteStatement() // || $this->parseReplaceStatement() ) { $token = $this->nextToken(); if ($token && !$this->isTokenOfType($token, wfWAFSQLiLexer::SEMICOLON)) { $this->triggerSyntaxError($this->currentToken()); } } else { $this->triggerSyntaxError($this->expectNextToken()); } } /** * @param int $index * @return bool */ protected function getToken($index) { if (array_key_exists($index, $this->tokens)) { return $this->tokens[$index]; } while ($token = $this->getLexer()->nextToken()) { if (!$this->isCommentToken($token)) { $this->tokens[$index] = $token; return $this->tokens[$index]; } } return false; } /** * @param wfWAFLexerToken $token * @return bool */ public function isCommentToken($token) { if ($this->isTokenOfType($token, wfWAFSQLiLexer::MYSQL_PORTABLE_COMMENT_START)) { $this->portableCommentVersions[(int) preg_replace('/[^\d]/', '', $token->getValue())] = 1; } return $this->isTokenOfType($token, array( wfWAFSQLiLexer::SINGLE_LINE_COMMENT, wfWAFSQLiLexer::MULTI_LINE_COMMENT, wfWAFSQLiLexer::MYSQL_PORTABLE_COMMENT_START, wfWAFSQLiLexer::MYSQL_PORTABLE_COMMENT_END, )); } public function hasMultiplePortableCommentVersions() { return count($this->portableCommentVersions) > 1; } /** * Expects the next token to be an identifier with the supplied case-insensitive value * * @param $keyword * @return wfWAFLexerToken * @throws wfWAFParserSyntaxError */ protected function expectNextIdentifierEquals($keyword) { $nextToken = $this->expectNextToken(); $this->expectTokenTypeEquals($nextToken, wfWAFSQLiLexer::UNQUOTED_IDENTIFIER); if ($nextToken->getLowerCaseValue() !== wfWAFUtils::strtolower($keyword)) { $this->triggerSyntaxError($nextToken); } return $nextToken; } private function parseSelectStatement() { $startIndex = $this->index; $hasSelect = false; while ($this->parseSelectExpression()) { $hasSelect = true; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'union')) { $hasSelect = false; if (!$this->isIdentifierWithValue($this->nextToken(), 'all')) { $this->index--; } continue; } $this->index = $savePoint; break; } if ($hasSelect) { return true; } $this->index = $startIndex; return false; } private function parseSelectExpression() { $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parseSelectExpression() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; if ($this->parseSelect()) { if ($this->parseFrom()) { $this->parseWhere(); $this->parseProcedure(); $this->parseGroupBy(); $this->parseHaving(); } $this->parseOrderBy(); $this->parseLimit(); return true; } return false; } /** * @throws wfWAFParserSyntaxError */ private function parseSelect() { $startPoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'select')) { $optionalSelectParamsRegex = '/ALL|DISTINCT(?:ROW)?|HIGH_PRIORITY|MAX_STATEMENT_TIME|STRAIGHT_JOIN|SQL_SMALL_RESULT|SQL_BIG_RESULT|SQL_BUFFER_RESULT|SQL_CACHE|SQL_NO_CACHE|SQL_CALC_FOUND_ROWS/i'; while (true) { $savePoint = $this->index; $token = $this->nextToken(); if ($token) { $value = $token->getLowerCaseValue(); if (preg_match($optionalSelectParamsRegex, $value)) { if ($value == 'max_statement_time') { $this->expectTokenTypeEquals($this->expectNextToken(), wfWAFSQLiLexer::EQUALS_SYMBOL); $this->expectTokenTypeInArray($this->expectNextToken(), array( wfWAFSQLiLexer::INTEGER_LITERAL, wfWAFSQLiLexer::BINARY_NUMBER_LITERAL, wfWAFSQLiLexer::HEX_NUMBER_LITERAL, wfWAFSQLiLexer::BINARY_NUMBER_LITERAL, )); } continue; } } $this->index = $savePoint; break; } return $this->parseSelectList(); } $this->index = $startPoint; return false; } /** * @throws wfWAFParserSyntaxError */ private function parseSelectList() { $startPoint = $this->index; $hasSelects = false; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::ASTERISK)) { $hasSelects = true; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { // Just SELECT * [FROM ...] $this->index--; return true; } } else { $this->index = $startPoint; } while ($this->parseDisplayedColumn()) { $hasSelects = true; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { continue; } $this->index--; break; } if ($hasSelects) { return true; } $this->index = $startPoint; return false; } private function parseDisplayedColumn() { /* ( table_spec DOT ASTERISK ) | ( column_spec (alias)? ) | ( bit_expr (alias)? ) */ $savePoint = $this->index; if ($this->parseTableSpec() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::DOT) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::ASTERISK) ) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->parseExpression()) { $this->parseAlias(); return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->parseColumnSpec()) { $this->parseAlias(); return true; } $this->index = $savePoint; return false; } /** * @return bool */ private function parseExpressionList() { $startIndex = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) { $hasExpressions = false; while ($this->parseExpression()) { $hasExpressions = true; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { continue; } $this->index--; break; } if ($hasExpressions && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $startIndex; return false; } private function parseExpression() { // Combines these: // exp_factor3 ( AND_SYM exp_factor3 )* ; // expression: exp_factor1 ( OR_SYM exp_factor1 )* ; // exp_factor1: exp_factor2 ( XOR exp_factor2 )* ; // exp_factor2: exp_factor3 ( AND_SYM exp_factor3 )* ; $savePoint = $this->index; $hasExpression = false; while ($this->parseExpressionFactor3()) { $hasExpression = true; $savePoint2 = $this->index; $token = $this->nextToken(); if ($this->isOrToken($token) || $this->isAndToken($token) || $this->isIdentifierWithValue($token, 'xor')) { continue; } $this->index = $savePoint2; break; } if ($hasExpression) { return true; } $this->index = $savePoint; return false; } private function parseExpressionFactor3() { // (NOT_SYM)? exp_factor4 ; $savePoint = $this->index; if (!$this->isNotSymbolToken($this->nextToken())) { $this->index--; } if ($this->parseExpressionFactor4()) { return true; } $this->index = $savePoint; return false; } private function parseExpressionFactor4() { // bool_primary ( IS_SYM (NOT_SYM)? (boolean_literal|NULL_SYM) )? ; $savePoint = $this->index; if ($this->parseBoolPrimary()) { $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'is')) { if (!$this->isNotSymbolToken($this->nextToken())) { $this->index--; } if ($this->isIdentifierWithValue($this->nextToken(), array( 'true', 'false', 'null', )) ) { return true; } } $this->index = $savePoint; return true; } $this->index = $savePoint; return false; } /** * @return bool */ private function parseBoolPrimary() { $startIndex = $this->index; $token = $this->nextToken(); if ($token) { $hasNot = false; if ($this->isNotSymbolToken($token)) { $hasNot = true; $token = $this->nextToken(); } $val = $token->getLowerCaseValue(); if ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) { if ($val === 'exists' && $this->parseSubquery()) { return true; } else if ($hasNot) { $this->index = $startIndex; return false; } } if (!$hasNot) { $this->index = $startIndex; } } if ($this->parsePredicate()) { $savePoint = $this->index; $opToken = $this->nextToken(); if ($opToken) { switch ($opToken->getType()) { case wfWAFSQLiLexer::EQUALS_SYMBOL: case wfWAFSQLiLexer::LESS_THAN: case wfWAFSQLiLexer::GREATER_THAN: case wfWAFSQLiLexer::LESS_THAN_EQUAL_TO: case wfWAFSQLiLexer::GREATER_THAN_EQUAL_TO: case wfWAFSQLiLexer::NOT_EQUALS: case wfWAFSQLiLexer::SET_VAR: $savePoint2 = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), array( 'any', 'all' )) && $this->parseSubquery() ) { return true; } $this->index = $savePoint2; $savePoint2 = $this->index; if ($this->testForSubquery() && $this->parseSubquery()) { return true; } $this->index = $savePoint2; if ($this->parsePredicate()) { return true; } $this->index = $startIndex; return false; } } $this->index = $savePoint; return true; } $this->index = $startIndex; return false; } private function parsePredicate() { $startIndex = $this->index; if ($this->parseBitExpression()) { $savePoint = $this->index; $token = $this->nextToken(); if ($token) { if ($hasNot = $this->isNotSymbolToken($token)) { $token = $this->nextToken(); if (!$token) { $this->index = $startIndex; return false; } } $val = $token->getLowerCaseValue(); if ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) { switch ($val) { case 'in': if ($this->parseSubquery() || $this->parseExpressionList()) { return true; } break; case 'between': if ($this->parseBitExpression() && $this->isIdentifierWithValue($this->nextToken(), 'and') && $this->parsePredicate() ) { return true; } break; case 'sounds': if ($this->isIdentifierWithValue($this->nextToken(), 'like') && $this->parseBitExpression() ) { return true; } break; case 'like': case 'rlike': if ($this->parseSimpleExpression()) { // We've got a LIKE statement at this point $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'escape') && $this->parseSimpleExpression() ) { return true; } $this->index = $savePoint; return true; } break; case 'regexp': if ($this->parseBitExpression()) { return true; } break; default: if ($hasNot) { $this->index = $startIndex; return false; } break; } } } $this->index = $savePoint; return true; } $this->index = $startIndex; return false; } /** * @return bool */ private function parseBitExpression() { // factor1 ( VERTBAR factor1 )? ; $savePoint = $this->index; if ($this->parseBitExprFactor5()) { $savePoint = $this->index; $token = $this->nextToken(); if (($this->isTokenOfType($token, array( wfWAFSQLiLexer::BIT_OR, wfWAFSQLiLexer::BIT_AND, wfWAFSQLiLexer::BIT_XOR, wfWAFSQLiLexer::BIT_LEFT_SHIFT, wfWAFSQLiLexer::BIT_RIGHT_SHIFT, wfWAFSQLiLexer::BIT_INVERSION, wfWAFSQLiLexer::PLUS, wfWAFSQLiLexer::MINUS, wfWAFSQLiLexer::ASTERISK, wfWAFSQLiLexer::DIVISION, wfWAFSQLiLexer::MOD, )) || $this->isIdentifierWithValue($token, array( 'div', 'mod' ))) && $this->parseBitExpression() ) { return true; } $this->index = $savePoint; return true; } $this->index = $savePoint; return false; } private function parseBitExprFactor5() { // factor6 ( (PLUS|MINUS) interval_expr )? ; $savePoint = $this->index; if ($this->parseBitExprFactor6()) { $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), array( wfWAFSQLiLexer::PLUS, wfWAFSQLiLexer::MINUS, )) && $this->parseIntervalExpression() ) { return true; } $this->index = $savePoint; return true; } $this->index = $savePoint; return false; } private function parseBitExprFactor6() { // (PLUS | MINUS | NEGATION | BINARY) simple_expr // | simple_expr ; $startPoint = $this->index; $savePoint = $this->index; while ( ($token = $this->nextToken()) && ( $this->isTokenOfType($token, array( wfWAFSQLiLexer::PLUS, wfWAFSQLiLexer::MINUS, )) || ($this->isTokenOfType($token, wfWAFSQLiLexer::BIT_INVERSION)) || ($this->isIdentifierWithValue($token, 'BINARY')) ) ) { $savePoint = $this->index; } $this->index = $savePoint; if ($this->parseSimpleExpression()) { return true; } $this->index = $startPoint; return false; } /** * literal_value * | column_spec * | function_call * | USER_VAR * | expression_list * | (ROW_SYM expression_list) * | subquery * | EXISTS subquery * | {identifier expr} * | match_against_statement * | case_when_statement * | interval_expr * * @return bool */ private function parseSimpleExpression() { $startPoint = $this->index; $simple = ($parseLiteral = $this->parseLiteral()) || ($parseMatchAgainst = $this->parseMatchAgainst()) || ($parseFunctionCall = $this->parseFunctionCall()) || ($parseVariable = $this->parseVariable()) || ($parseExpressionList = $this->parseExpressionList()) || ($parseSubquery = $this->parseSubquery()) || ($parseExistsSubquery = $this->parseExistsSubquery()) || ($parseCaseWhen = $this->parseCaseWhen()) || ($parseODBCExpression = $this->parseODBCExpression()) || ($parseIntervalExpression = $this->parseIntervalExpression()) || ($parseColumnSpec = $this->parseColumnSpec()); if ($simple) { $token = $this->nextToken(); if ($token && $token->getLowerCaseValue() == 'collate') { $savePoint = $this->index; if ($this->parseCollationName()) { return true; } $this->index = $savePoint; } else { $this->index--; } return true; } $this->index = $startPoint; return false; } /** * @return bool */ private function parseLiteral() { $startIndex = $this->index; $savePoint = $this->index; while ($this->isTokenOfType($this->nextToken(), array( wfWAFSQLiLexer::PLUS, wfWAFSQLiLexer::MINUS, ))) { $savePoint = $this->index; } $this->index = $savePoint; // Check if this is a Character Set Introducer $nextToken = $this->nextToken(); $hasCharacterSetIntroducer = $this->isTokenOfType($nextToken, wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) && substr($nextToken->getValue(), 0, 1) === '_'; if (!$hasCharacterSetIntroducer) { $this->index--; } $validLiteral = false; $nextToken = $this->nextToken(); if ($nextToken) { switch ($nextToken->getType()) { case wfWAFSQLiLexer::INTEGER_LITERAL: case wfWAFSQLiLexer::BINARY_NUMBER_LITERAL: case wfWAFSQLiLexer::HEX_NUMBER_LITERAL: case wfWAFSQLiLexer::REAL_NUMBER_LITERAL: $validLiteral = true; break; // Allow concatenation: 'test' 'test' is valid case wfWAFSQLiLexer::DOUBLE_STRING_LITERAL: case wfWAFSQLiLexer::SINGLE_STRING_LITERAL: $savePoint = $this->index; while ($this->isTokenOfType($this->nextToken(), array( wfWAFSQLiLexer::DOUBLE_STRING_LITERAL, wfWAFSQLiLexer::SINGLE_STRING_LITERAL ))) { $savePoint = $this->index; } $this->index = $savePoint; $validLiteral = true; break; case wfWAFSQLiLexer::UNQUOTED_IDENTIFIER: if ($nextToken->getLowerCaseValue() === 'null') { $validLiteral = true; } break; } } if ($validLiteral) { if ($hasCharacterSetIntroducer) { // Check for and parse collation $savePoint = $this->index; $hasCollation = $this->isIdentifierWithValue($this->nextToken(), 'collation') && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::UNQUOTED_IDENTIFIER); if (!$hasCollation) { $this->index = $savePoint; } } return true; } $this->index = $startIndex; return false; } /** * @return bool */ private function parseColumnSpec() { $savePoint = $this->index; if ($this->parseTableSpec()) { $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::DOT)) { $nextToken = $this->nextToken(); if ($nextToken && ($nextToken->getType() == wfWAFSQLiLexer::UNQUOTED_IDENTIFIER || $nextToken->getType() == wfWAFSQLiLexer::QUOTED_IDENTIFIER) ) { return true; } $this->index = $savePoint; return false; } $this->index = $savePoint; return true; } $this->index = $savePoint; return false; } /** * CAST_SYM LPAREN expression AS_SYM cast_data_type RPAREN ) * | ( CONVERT_SYM LPAREN expression COMMA cast_data_type RPAREN ) * | ( CONVERT_SYM LPAREN expression USING_SYM transcoding_name RPAREN ) * | ( group_functions LPAREN ( ASTERISK | ALL | DISTINCT )? bit_expr RPAREN ) * * @return bool */ private function parseFunctionCall() { $startPoint = $this->index; $functionToken = $this->nextToken(); if ($functionToken && $functionToken->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) { if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) { switch ($functionToken->getLowerCaseValue()) { case 'cast': if ($this->parseExpression() && $this->isIdentifierWithValue($this->nextToken(), 'as') && $this->parseCastDataType() && $this->parseOptionalCharacterSet() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } break; case 'convert': if ($this->parseExpression()) { $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) && $this->parseCastDataType() && $this->parseOptionalCharacterSet() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'using') && $this->parseTranscodingName() && $this->parseOptionalCharacterSet() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; } break; case 'trim': if (!$this->isIdentifierWithValue($this->nextToken(), array( 'leading', 'both', 'trailing', ))) { $this->index--; } while ($this->parseExpression()) { $nextToken = $this->nextToken(); if ( $this->isTokenOfType($nextToken, wfWAFSQLiLexer::COMMA) || $this->isIdentifierWithValue($nextToken, array( 'from', 'for', 'in', )) ) { continue; } $this->index--; break; } if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } break; case 'weight_string': if ($this->parseExpression()) { $savePoint = $this->index; if (!( $this->isIdentifierWithValue($this->nextToken(), 'as') && $this->parseCastDataType() && $this->parseOptionalCharacterSet() )) { $this->index = $savePoint; } if ($this->isIdentifierWithValue($this->nextToken(), 'level')) { while ($this->parseExpression()) { $nextToken = $this->nextToken(); if ( $this->isTokenOfType($nextToken, wfWAFSQLiLexer::COMMA) || $this->isTokenOfType($nextToken, wfWAFSQLiLexer::MINUS) ) { continue; } $this->index--; break; } while ($this->isIdentifierWithValue($this->nextToken(), array( 'asc', 'desc', 'reverse', ))) { continue; } $this->index--; } else { $this->index--; } } if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } break; default: $savePoint = $this->index; if (in_array($functionToken->getUpperCaseValue(), $this->groupFunctions)) { $token = $this->nextToken(); if (!$this->isIdentifierWithValue($token, array( 'all', 'distinct', )) && !$this->isTokenOfType($token, wfWAFSQLiLexer::ASTERISK) ) { $this->index--; } $this->parseBitExpression(); if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $savePoint; while ($this->parseExpression()) { $nextToken = $this->nextToken(); if ( $this->isTokenOfType($nextToken, wfWAFSQLiLexer::COMMA) || $this->isIdentifierWithValue($nextToken, array( 'from', 'for', 'in', )) ) { continue; } $this->index--; break; } if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } break; } } } $this->index = $startPoint; return false; } /** * BINARY (INTEGER_NUM)? * | CHAR (INTEGER_NUM)? * | DATE_SYM * | DATETIME * | DECIMAL_SYM ( INTEGER_NUM (COMMA INTEGER_NUM)? )? * | SIGNED_SYM (INTEGER_SYM)? * | TIME_SYM * | UNSIGNED_SYM (INTEGER_SYM)? * * @return bool */ private function parseCastDataType() { $startPoint = $this->index; $token = $this->nextToken(); if ($this->isKeywordToken($token)) { switch ($token->getLowerCaseValue()) { case 'binary': case 'char': case 'nchar': case 'varchar': case 'character': $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; return true; case 'date': case 'datetime': case 'time': return true; case 'signed': case 'unsigned': if (!$this->isIdentifierWithValue($this->nextToken(), array( 'int', 'integer', ))) { $this->index--; } return true; case 'decimal': $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL)) { $savePoint2 = $this->index; if (!($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL) )) { $this->index = $savePoint2; } if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $savePoint; return true; } } $this->index = $startPoint; return false; } private function parseTranscodingName() { $savePoint = $this->index; $token = $this->nextToken(); if ($token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) { return true; } $this->index = $savePoint; return false; } private function parseOptionalCharacterSet() { $savePoint = $this->index; if (!( $this->nextToken()->getLowerCaseValue() === 'character' && $this->nextToken()->getLowerCaseValue() === 'set' && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) )) { $this->index = $savePoint; } return true; } private function parseVariable() { $nextToken = $this->nextToken(); if ($nextToken && $nextToken->getType() === wfWAFSQLiLexer::VARIABLE) { return true; } $this->index--; return false; } /** * @return bool */ private function parseSubquery() { $startIndex = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parseSelectStatement() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $startIndex; return false; } private function testForSubquery() { $startIndex = $this->index; $nextToken = $this->nextToken(); if ($nextToken && $nextToken->getType() === wfWAFSQLiLexer::OPEN_PARENTHESIS) { $selectToken = $this->nextToken(); if ($this->isIdentifierWithValue($selectToken, 'select')) { $this->index = $startIndex; return true; } } $this->index = $startIndex; return false; } /** * * * @return bool */ private function parseExistsSubquery() { $startIndex = $this->index; $existsToken = $this->nextToken(); if ($this->isIdentifierWithValue($existsToken, 'exists')) { if ($this->parseSubquery()) { return true; } } $this->index = $startIndex; return false; } /** * MATCH (column_spec (COMMA column_spec)* ) AGAINST (expression (search_modifier)? ) * * @return bool */ private function parseMatchAgainst() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'match')) { $savePoint = $this->index; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) { $this->index = $savePoint; } $hasColumns = false; while ($this->parseColumnSpec()) { $hasColumns = true; $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { continue; } $this->index = $savePoint; break; } $savePoint = $this->index; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { $this->index = $savePoint; } if ($hasColumns && $this->isIdentifierWithValue($this->nextToken(), 'against') && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parseExpression() && ($this->parseSearchModifier() || true) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } } $this->index = $startIndex; return false; } /** * Used in match/against * * @link https://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html * @return bool */ private function parseSearchModifier() { $startIndex = $this->index; $startToken = $this->nextToken(); if ($this->isIdentifierWithValue($startToken, 'in')) { $next = $this->nextToken(); if ($this->isIdentifierWithValue($next, 'natural') && $this->isIdentifierWithValue($this->nextToken(), 'language') && $this->isIdentifierWithValue($this->nextToken(), 'mode') ) { $saveIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'with') && $this->isIdentifierWithValue($this->nextToken(), 'query') && $this->isIdentifierWithValue($this->nextToken(), 'expansion') ) { return true; } $this->index = $saveIndex; return true; } else if ($this->isIdentifierWithValue($next, 'boolean') && $this->isIdentifierWithValue($this->nextToken(), 'mode') ) { return true; } } else if ($this->isIdentifierWithValue($startToken, 'with')) { if ($this->isIdentifierWithValue($this->nextToken(), 'query') && $this->isIdentifierWithValue($this->nextToken(), 'expansion') ) { return true; } } $this->index = $startIndex; return false; } /** * @return bool */ private function parseCaseWhen() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'case')) { $hasWhen = false; while (true) { if (!$this->isIdentifierWithValue($this->nextToken(), 'when')) { $this->index--; break; } if ($this->parseExpression()) { if ($this->isIdentifierWithValue($this->nextToken(), 'then') && $this->parseBitExpression()) { $hasWhen = true; continue; } $this->index--; } $this->index--; break; } if ($hasWhen) { $endToken = $this->nextToken(); if ($this->isIdentifierWithValue($endToken, 'else')) { if (!$this->parseBitExpression()) { $this->index = $startIndex; return false; } $endToken = $this->nextToken(); } if ($this->isIdentifierWithValue($endToken, 'end')) { return true; } } } $this->index = $startIndex; return false; } /** * @return bool */ private function parseODBCExpression() { $startIndex = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_BRACKET) && $this->isIdentifier($this->nextToken()) && $this->parseExpression() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_BRACKET)) { return true; } $this->index = $startIndex; return false; } /** * @return bool */ private function parseIntervalExpression() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'interval') && $this->parseExpression()) { $intervalUnitToken = $this->nextToken(); if ($intervalUnitToken && in_array($intervalUnitToken->getType(), $this->intervalUnits)) { return true; } } $this->index = $startIndex; return false; } /** * @return bool */ public function parseCollationName() { $startIndex = $this->index; $token = $this->nextToken(); if ($token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) { return true; } $this->index = $startIndex; return false; } /** * @return bool */ private function parseFrom() { $startIndex = $this->index; $token = $this->nextToken(); if ($this->isIdentifierWithValue($token, 'from')) { return $this->parseTableReferences(); } $this->index = $startIndex; return false; } /** * @link http://dev.mysql.com/doc/refman/5.6/en/join.html * @return bool */ private function parseTableReferences() { $startPoint = $this->index; $hasReferences = false; while ($this->parseEscapedTableReference()) { $hasReferences = true; $savePoint = $this->index; $token = $this->nextToken(); if ($this->isTokenOfType($token, wfWAFSQLiLexer::COMMA)) { continue; } $this->index = $savePoint; break; } if ($hasReferences) { return true; } $this->index = $startPoint; return false; } /** * @return bool */ private function parseEscapedTableReference() { $startPoint = $this->index; if ($this->parseTableReference() || ( $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_BRACKET) && $this->isIdentifierWithValue($this->nextToken(), 'oj') && $this->parseTableReference() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_BRACKET) ) ) { return true; } $this->index = $startPoint; return false; } /** * @return bool */ private function parseTableReference() { $savePoint = $this->index; $hasTables = false; if ($this->parseTableFactor()) { $hasTables = true; while ($this->parseJoinTable()) { } } if ($hasTables) { return true; } $this->index = $savePoint; return false; } /** * table_factor: * tbl_name [PARTITION (partition_names)] [[AS] alias] [index_hint_list] * | table_subquery [AS] alias * | ( table_references ) */ private function parseTableFactor() { $savePoint = $this->index; if ($this->parseTableSpec()) { $savePoint2 = $this->index; if (!$this->parsePartitionClause()) { $this->index = $savePoint2; } $this->parseAlias(); $this->parseIndexHintList(); return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->parseSubquery() && $this->parseAlias()) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parseTableReferences() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; return false; } /** * PARTITION (partition_names) * * @return bool */ private function parsePartitionClause() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'partition') && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parsePartitionNames() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $startIndex; return false; } /** * @return bool */ private function parsePartitionNames() { $startPoint = $this->index; $hasPartition = false; while ($this->parsePartitionName()) { $hasPartition = true; $savePoint = $this->index; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $this->index = $savePoint; break; } } if ($hasPartition) { return true; } $this->index = $startPoint; return false; } /** * @return bool */ private function parsePartitionName() { $startPoint = $this->index; $token = $this->nextToken(); if ($this->isValidNonReservedWordIdentifier($token)) { return true; } $this->index = $startPoint; return false; } /** * join_table: * table_reference [INNER | CROSS] JOIN table_factor [join_condition] * | table_reference STRAIGHT_JOIN table_factor * | table_reference STRAIGHT_JOIN table_factor ON conditional_expr * | table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_condition * | table_reference NATURAL [{LEFT|RIGHT} [OUTER]] JOIN table_factor * * @return bool */ private function parseJoinTable() { $savePoint = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), array( 'inner', 'cross', )) ) { $this->index = $savePoint; } if ($this->isIdentifierWithValue($this->nextToken(), 'join') && $this->parseTableFactor()) { $this->parseJoinCondition(); return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'straight_join') && $this->parseTableFactor() ) { $savePoint = $this->index; if (!($this->isIdentifierWithValue($this->nextToken(), 'on') && $this->parseExpression())) { $this->index = $savePoint; } return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), array( 'left', 'right', )) ) { $savePoint2 = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), array( 'outer', )) ) { $this->index = $savePoint2; } } else { $this->index = $savePoint; } if ($this->isIdentifierWithValue($this->nextToken(), 'join') && $this->parseTableReference() && $this->parseJoinCondition() ) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), array( 'natural', )) ) { if ($this->isIdentifierWithValue($this->nextToken(), array( 'left', 'right', )) ) { $savePoint2 = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), array( 'outer', )) ) { $this->index = $savePoint2; } } else { $this->index = $savePoint; } if ($this->isIdentifierWithValue($this->nextToken(), 'join') && $this->parseTableFactor() ) { return true; } } $this->index = $savePoint; return false; } /** * (ON expression) | (USING_SYM column_list) * * @return bool */ private function parseJoinCondition() { $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'on') && $this->parseExpression()) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'using') && $this->parseColumnList()) { return true; } $this->index = $savePoint; return false; } /** * @return bool */ private function parseTableSpec() { $savePoint = $this->index; $lastComponent = $savePoint; $components = 0; while (($token = $this->nextToken()) !== false) { if ($this->isValidIdentifier($token)) { $lastComponent = $this->index; $next = $this->nextToken(); if ($this->isTokenOfType($next, wfWAFSQLiLexer::DOT)) { $components++; } elseif ($this->isTokenOfType($next, wfWAFSQLiLexer::REAL_NUMBER_LITERAL)) { $next = $this->nextToken(); if ($this->isTokenOfType($next, wfWAFSQLiLexer::UNQUOTED_IDENTIFIER) && in_array($next->getValue(), array('e', 'E')) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::DOT)) { $components++; } else { break; } } elseif ($components > 0 || $this->isValidNonReservedWordIdentifier($token)) { $this->index = $lastComponent; return true; } else { break; } } else if ($this->isTokenOfType($token, wfWAFSQLiLexer::ASTERISK)) { $this->index = $lastComponent; return true; } else { break; } } $this->index = $savePoint; return false; } /** * @return bool */ private function parseAlias() { $savePoint = $this->index; $token = $this->nextToken(); if ($this->isIdentifierWithValue($token, 'as')) { $token = $this->nextToken(); } if ($this->isValidNonReservedWordIdentifier($token)) { return true; } $this->index = $savePoint; return false; } /** * @return bool */ private function parseIndexHintList() { $startPoint = $this->index; $hasHints = false; while ($this->parseIndexHint()) { $hasHints = true; $savePoint = $this->index; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $this->index = $savePoint; break; } } if ($hasHints) { return true; } $this->index = $startPoint; return false; } /** * @return bool */ private function parseIndexHint() { // USE_SYM index_options LPAREN (index_list)? RPAREN $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'use') && $this->parseIndexOptions() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) ) { $this->parseIndexList(); if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $savePoint; // IGNORE_SYM index_options LPAREN index_list RPAREN $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'ignore') && $this->parseIndexOptions() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parseIndexList() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; // FORCE_SYM index_options LPAREN index_list RPAREN $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'force') && $this->parseIndexOptions() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) && $this->parseIndexList() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; return false; } /** * (INDEX_SYM | KEY_SYM) ( FOR_SYM ((JOIN_SYM) | (ORDER_SYM BY_SYM) | (GROUP_SYM BY_SYM)) )? * * @return bool */ private function parseIndexOptions() { $savePoint = $this->index; $token = $this->nextToken(); if ($this->isIdentifierWithValue($token, 'index') || $this->isIdentifierWithValue($token, 'key') ) { $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'for')) { $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'join')) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'order') && $this->isIdentifierWithValue($this->nextToken(), 'by') ) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'group') && $this->isIdentifierWithValue($this->nextToken(), 'by') ) { return true; } $this->index = $savePoint; return true; } $this->index = $savePoint; return true; } $this->index = $savePoint; return false; } private function parseIndexList() { $startPoint = $this->index; $hasIndex = false; while ($this->parseIndexName()) { $hasIndex = true; $savePoint = $this->index; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $this->index = $savePoint; break; } } if ($hasIndex) { return true; } $this->index = $startPoint; return false; } private function parseIndexName() { $startPoint = $this->index; $token = $this->nextToken(); if ($this->isValidNonReservedWordIdentifier($token)) { return true; } $this->index = $startPoint; return false; } /** * LPAREN column_spec (COMMA column_spec)* RPAREN * * @return bool */ private function parseColumnList() { $startPoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) { $hasColumn = false; while ($this->parseColumnSpec()) { $hasColumn = true; $savePoint = $this->index; if (!$this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $this->index = $savePoint; break; } } if ($hasColumn && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $startPoint; return false; } private function parseWhere() { $startIndex = $this->index; $token = $this->nextToken(); if ($this->isIdentifierWithValue($token, 'where')) { if ($this->parseExpression()) { return true; } } $this->index = $startIndex; return false; } private function parseProcedure() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'PROCEDURE') && $this->isIdentifierWithValue($this->nextToken(), 'ANALYSE') && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS) ) { $savePoint = $this->index; if ($this->parseExpression()) { $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) && $this->parseExpression() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS) ) { return true; } $this->index = $savePoint; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $savePoint; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $startIndex; return false; } /** * GROUP_SYM BY_SYM groupby_item (COMMA groupby_item)* (WITH ROLLUP_SYM)? * * @return bool */ private function parseGroupBy() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'group') && $this->isIdentifierWithValue($this->nextToken(), 'by') ) { $hasItems = false; while ($this->parseGroupByItem()) { $hasItems = true; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { continue; } $this->index--; break; } if ($hasItems) { $savePoint = $this->index; if (!($this->isIdentifierWithValue($this->nextToken(), 'with') && $this->isIdentifierWithValue($this->nextToken(), 'rollup')) ) { $this->index = $savePoint; } return true; } } $this->index = $startIndex; return false; } /** * column_spec | INTEGER_NUM | bit_expr ; * * @return bool */ private function parseGroupByItem() { $startIndex = $this->index; if ($this->parseBitExpression()) { return true; } $this->index = $startIndex; return false; } /** * HAVING expression * * @return bool */ private function parseHaving() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'having') && $this->parseExpression() ) { return true; } $this->index = $startIndex; return false; } /** * ORDER_SYM BY_SYM orderby_item (COMMA orderby_item)* * * @return bool */ private function parseOrderBy() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'order') && $this->isIdentifierWithValue($this->nextToken(), 'by') ) { $hasItems = false; while ($this->parseOrderByItem()) { $hasItems = true; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { continue; } $this->index--; break; } if ($hasItems) { return true; } } $this->index = $startIndex; return false; } /** * groupby_item (ASC | DESC)? ; * * @return bool */ private function parseOrderByItem() { $startIndex = $this->index; if ($this->parseGroupByItem()) { $savePoint = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), array( 'asc', 'desc', )) ) { $this->index = $savePoint; } return true; } $this->index = $startIndex; return false; } private function parseLimit() { // LIMIT ((offset COMMA)? row_count) | (row_count OFFSET_SYM offset) $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'limit') && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL) ) { $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA) && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL) ) { return true; } $this->index = $savePoint; $savePoint = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'offset') && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::INTEGER_LITERAL) ) { return true; } $this->index = $savePoint; return true; } $this->index = $startIndex; return false; } /** * @link http://dev.mysql.com/doc/refman/5.6/en/insert.html * @return bool */ private function parseInsertStatement() { $startIndex = $this->index; if ($this->parseInsertStatement1() || $this->parseInsertStatement2() || $this->parseInsertStatement3() ) { return true; } $this->index = $startIndex; return false; } /** * insert_header * (column_list)? * value_list_clause * ( insert_subfix )? * * @return bool */ private function parseInsertStatement1() { $startIndex = $this->index; if ($this->parseInsertHeader()) { $this->parseColumnList(); if ($this->parseValueListClause()) { $this->parseInsertSubfix(); return true; } } $this->index = $startIndex; return false; } /** * insert_header * set_columns_cluase * ( insert_subfix )? * * @return bool */ private function parseInsertStatement2() { $startIndex = $this->index; if ($this->parseInsertHeader() && $this->parseSetColumnsClause()) { $this->parseInsertSubfix(); return true; } $this->index = $startIndex; return false; } /** * insert_header * (column_list)? * select_expression * ( insert_subfix )? * * @return bool */ private function parseInsertStatement3() { $startIndex = $this->index; if ($this->parseInsertHeader()) { $this->parseColumnList(); if ($this->parseSelectStatement()) { $this->parseInsertSubfix(); return true; } } $this->index = $startIndex; return false; } /** * INSERT (LOW_PRIORITY | HIGH_PRIORITY)? (IGNORE_SYM)? * (INTO)? table_spec * (partition_clause)? * * @return bool */ private function parseInsertHeader() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'insert')) { $savePoint = $this->index; // (LOW_PRIORITY | HIGH_PRIORITY)? if (!$this->isIdentifierWithValue($this->nextToken(), array( 'LOW_PRIORITY', 'HIGH_PRIORITY' )) ) { $this->index = $savePoint; } // (IGNORE_SYM)? $savePoint = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), array( 'IGNORE' )) ) { $this->index = $savePoint; } // (INTO)? $savePoint = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), array( 'into' )) ) { $this->index = $savePoint; } // table_spec if ($this->parseTableSpec()) { $savePoint = $this->index; // (partition_clause)? if (!$this->parsePartitionClause()) { $this->index = $savePoint; } return true; } } $this->index = $startIndex; return false; } /** * (VALUES | VALUE_SYM) column_value_list (COMMA column_value_list)*; * * @return bool */ private function parseValueListClause() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), array( 'value', 'values', )) ) { $hasValues = false; while ($this->parseColumnValueList()) { $hasValues = true; $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $hasValues = false; continue; } $this->index = $savePoint; break; } if ($hasValues) { return true; } } $this->index = $startIndex; return false; } /** * LPAREN (bit_expr|DEFAULT) (COMMA (bit_expr|DEFAULT) )* RPAREN ; * * @return bool */ private function parseColumnValueList() { $startIndex = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::OPEN_PARENTHESIS)) { $hasValues = false; while (true) { $savePoint = $this->index; if (!$this->parseBitExpression() && !$this->isIdentifierWithValue($this->nextToken(), 'DEFAULT')) { $this->index = $savePoint; break; } $hasValues = true; $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $hasValues = false; continue; } $this->index = $savePoint; break; } if ($hasValues && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::CLOSE_PARENTHESIS)) { return true; } } $this->index = $startIndex; return false; } private function parseInsertSubfix() { // ON DUPLICATE_SYM KEY_SYM UPDATE column_spec EQ_SYM expression (COMMA column_spec EQ_SYM expression)* $startIndex = $this->index; if ( $this->isIdentifierWithValue($this->nextToken(), 'on') && $this->isIdentifierWithValue($this->nextToken(), 'duplicate') && $this->isIdentifierWithValue($this->nextToken(), 'key') && $this->isIdentifierWithValue($this->nextToken(), 'update') ) { $hasValues = false; while (true) { $savePoint = $this->index; if ($this->parseColumnSpec() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::EQUALS_SYMBOL) && $this->parseExpression() ) { $hasValues = true; $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $hasValues = false; continue; } $this->index = $savePoint; break; } $this->index = $savePoint; break; } if ($hasValues) { return true; } } $this->index = $startIndex; return false; } /** * SET_SYM set_column_cluase ( COMMA set_column_cluase )*; * * @return bool */ private function parseSetColumnsClause() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'set')) { $hasValues = true; while ($this->parseSetColumnClause()) { $hasValues = true; $savePoint = $this->index; if ($this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::COMMA)) { $hasValues = false; continue; } $this->index = $savePoint; break; } if ($hasValues) { return true; } } $this->index = $startIndex; return false; } /** * column_spec EQ_SYM (expression|DEFAULT) ; * * @return bool */ private function parseSetColumnClause() { $startIndex = $this->index; if ($this->parseColumnSpec() && $this->isTokenOfType($this->nextToken(), wfWAFSQLiLexer::EQUALS_SYMBOL) && ($this->parseExpression() || $this->isIdentifierWithValue($this->nextToken(), 'default')) ) { return true; } $this->index = $startIndex; return false; } /** * UPDATE (LOW_PRIORITY)? (IGNORE_SYM)? table_reference * set_columns_cluase * (where_clause)? * (orderby_clause)? * (limit_clause)? * | * UPDATE (LOW_PRIORITY)? (IGNORE_SYM)? table_references * set_columns_cluase * (where_clause)? * * @return bool */ private function parseUpdateStatement() { $startIndex = $this->index; if ($this->isIdentifierWithValue($this->nextToken(), 'update')) { $savePoint = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), 'LOW_PRIORITY')) { $this->index = $savePoint; } $savePoint = $this->index; if (!$this->isIdentifierWithValue($this->nextToken(), 'ignore')) { $this->index = $savePoint; } if ($this->parseTableReferences() && $this->parseSetColumnsClause()) { $this->parseWhere(); $this->parseOrderBy(); $this->parseLimit(); return true; } } $this->index = $startIndex; return false; } /** * @param wfWAFLexerToken $token * @return bool */ private function isIdentifier($token) { return $token && ($token->getType() === wfWAFSQLiLexer::QUOTED_IDENTIFIER || $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER); } /** * @param wfWAFLexerToken $token * @param string|array $value * @return bool */ private function isIdentifierWithValue($token, $value) { return $token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && (is_array($value) ? in_array($token->getLowerCaseValue(), array_map('wfWAFUtils::strtolower', $value)) : $token->getLowerCaseValue() === wfWAFUtils::strtolower($value)); } /** * @param wfWAFLexerToken $token * @param mixed $type * @return bool */ protected function isTokenOfType($token, $type) { if (is_array($type)) { return $token && in_array($token->getType(), $type); } return $token && $token->getType() === $type; } /** * @param wfWAFLexerToken $token * @return bool */ private function isNotSymbolToken($token) { return $token && ( ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && $token->getLowerCaseValue() === 'not') || ($token->getType() === wfWAFSQLiLexer::EXPR_NOT) ); } /** * @param wfWAFLexerToken $token * @return bool */ private function isKeywordToken($token) { return $token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && in_array($token->getUpperCaseValue(), $this->keywords); } /** * @param wfWAFLexerToken $token * @return bool */ private function isReservedWordToken($token) { return $token && $token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && in_array($token->getUpperCaseValue(), $this->reservedWords); } /** * @param wfWAFLexerToken $token * @return bool */ private function isValidNonKeywordIdentifier($token) { return $token && ( $token->getType() === wfWAFSQLiLexer::QUOTED_IDENTIFIER || ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && !$this->isKeywordToken($token)) ); } /** * @param wfWAFLexerToken $token * @return bool */ private function isValidNonReservedWordIdentifier($token) { return $token && ( $token->getType() === wfWAFSQLiLexer::QUOTED_IDENTIFIER || ($token->getType() === wfWAFSQLiLexer::UNQUOTED_IDENTIFIER && !$this->isReservedWordToken($token)) ); } /** * @param wfWAFLexerToken $token * @return bool */ private function isValidIdentifier($token) { return $this->isTokenOfType($token, array( wfWAFSQLiLexer::QUOTED_IDENTIFIER, wfWAFSQLiLexer::UNQUOTED_IDENTIFIER )); } /** * @param wfWAFLexerToken $token * @return bool */ private function isOrToken($token) { return $token && ($this->isIdentifierWithValue($token, 'or') || $this->isTokenOfType($token, wfWAFSQLiLexer::EXPR_OR)); } /** * @param wfWAFLexerToken $token * @return bool */ private function isAndToken($token) { return $token && ($this->isIdentifierWithValue($token, 'and') || $this->isTokenOfType($token, wfWAFSQLiLexer::EXPR_AND)); } /** * @return string */ public function getSubject() { return $this->subject; } /** * @param string $subject */ public function setSubject($subject) { $this->subject = $subject; $this->setTokens(array()); $this->lexer->setSQL($this->subject); } /** * @return int */ public function getFlags() { return $this->flags; } /** * @param int $flags */ public function setFlags($flags) { $this->flags = $flags; $this->lexer->setFlags($this->flags); } } class wfWAFSQLiLexer implements wfWAFLexerInterface { const FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS = 0x1; const UNQUOTED_IDENTIFIER = 'UNQUOTED_IDENTIFIER'; const VARIABLE = 'VARIABLE'; const QUOTED_IDENTIFIER = 'QUOTED_IDENTIFIER'; const DOUBLE_STRING_LITERAL = 'DOUBLE_STRING_LITERAL'; const SINGLE_STRING_LITERAL = 'SINGLE_STRING_LITERAL'; const INTEGER_LITERAL = 'INTEGER_LITERAL'; const REAL_NUMBER_LITERAL = 'REAL_NUMBER_LITERAL'; const BINARY_NUMBER_LITERAL = 'BINARY_NUMBER_LITERAL'; const HEX_NUMBER_LITERAL = 'HEX_NUMBER_LITERAL'; const DOT = 'DOT'; const OPEN_PARENTHESIS = 'OPEN_PARENTHESIS'; const CLOSE_PARENTHESIS = 'CLOSE_PARENTHESIS'; const OPEN_BRACKET = 'OPEN_BRACKET'; const CLOSE_BRACKET = 'CLOSE_BRACKET'; const COMMA = 'COMMA'; const EXPR_OR = 'EXPR_OR'; const EXPR_AND = 'EXPR_AND'; const EXPR_NOT = 'EXPR_NOT'; const BIT_AND = 'BIT_AND'; const BIT_LEFT_SHIFT = 'BIT_LEFT_SHIFT'; const BIT_RIGHT_SHIFT = 'BIT_RIGHT_SHIFT'; const BIT_XOR = 'BIT_XOR'; const BIT_INVERSION = 'BIT_INVERSION'; const BIT_OR = 'BIT_OR'; const PLUS = 'PLUS'; const MINUS = 'MINUS'; const ASTERISK = 'ASTERISK'; const DIVISION = 'DIVISION'; const MOD = 'MOD'; const ARROW = 'ARROW'; const EQUALS_SYMBOL = 'EQUALS_SYMBOL'; const NOT_EQUALS = 'NOT_EQUALS'; const LESS_THAN = 'LESS_THAN'; const GREATER_THAN = 'GREATER_THAN'; const LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO'; const GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO'; const SET_VAR = 'SET_VAR'; const RIGHT_BRACKET = 'RIGHT_BRACKET'; const LEFT_BRACKET = 'LEFT_BRACKET'; const SEMICOLON = 'SEMICOLON'; const COLON = 'COLON'; const MYSQL_PORTABLE_COMMENT_START = 'MYSQL_PORTABLE_COMMENT_START'; const MYSQL_PORTABLE_COMMENT_END = 'MYSQL_PORTABLE_COMMENT_END'; const SINGLE_LINE_COMMENT = 'SINGLE_LINE_COMMENT'; const MULTI_LINE_COMMENT = 'MULTI_LINE_COMMENT'; /** * @var int */ private $flags; private $tokenMatchers; private $hasPortableCommentStart = false; public static function getLexerTokenMatchers() { static $tokenMatchers; if ($tokenMatchers === null) { $tokenMatchers = array( new wfWAFLexerTokenMatcher(self::REAL_NUMBER_LITERAL, '/^(?:[0-9]+\\.[0-9]+|[0-9]+\\.|\\.[0-9]+|[Ee][\\+\\-][0-9]+)/'), new wfWAFLexerTokenMatcher(self::BINARY_NUMBER_LITERAL, '/^(?:0b[01]+|[bB]\'[01]+\')/', true), new wfWAFLexerTokenMatcher(self::HEX_NUMBER_LITERAL, '/^(?:0x[0-9a-fA-F]+|[xX]\'[0-9a-fA-F]+\')/', true), new wfWAFLexerTokenMatcher(self::INTEGER_LITERAL, '/^[0-9]+/', true), new wfWAFLexerTokenMatcher(self::VARIABLE, '/^(?:@(?:`(?:[^`]*(?:``[^`]*)*)`| "([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"| \'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'| [a-zA-Z_\\$\\.]+| @[a-zA-Z_\\$][a-zA-Z_\\$0-9]{0,256}){0,1}) /Asx'), new wfWAFLexerTokenMatcher(self::QUOTED_IDENTIFIER, '/^`(?:[^`]*(?:``[^`]*)*)`/As'), new wfWAFLexerTokenMatcher(self::DOUBLE_STRING_LITERAL, '/^(?:[nN]|_[0-9a-zA-Z\\$_]{0,256})?"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"/As'), new wfWAFLexerTokenMatcher(self::SINGLE_STRING_LITERAL, '/^(?:[nN]|_[0-9a-zA-Z\\$_]{0,256})?\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'), // U+0080 .. U+FFFF new wfWAFLexerTokenMatcher(self::UNQUOTED_IDENTIFIER, '/^[0-9a-zA-Z\\$_\\x{0080}-\\x{FFFF}]{1,256}/u'), new wfWAFLexerTokenMatcher(self::MYSQL_PORTABLE_COMMENT_START, '/^\\/\\*\\![0-9]{0,5}/s'), new wfWAFLexerTokenMatcher(self::MYSQL_PORTABLE_COMMENT_END, '/^\\*\\//s'), new wfWAFLexerTokenMatcher(self::SINGLE_LINE_COMMENT, '/^(?:#[^\n]*|--(?:[ \t\r][^\n]*|[\n]))/'), new wfWAFLexerTokenMatcher(self::MULTI_LINE_COMMENT, '/^\\/\\*.*?\\*\\//s'), new wfWAFLexerTokenMatcher(self::DOT, '/^\\./'), new wfWAFLexerTokenMatcher(self::OPEN_PARENTHESIS, '/^\\(/'), new wfWAFLexerTokenMatcher(self::CLOSE_PARENTHESIS, '/^\\)/'), new wfWAFLexerTokenMatcher(self::OPEN_BRACKET, '/^\\{/'), new wfWAFLexerTokenMatcher(self::CLOSE_BRACKET, '/^\\}/'), new wfWAFLexerTokenMatcher(self::COMMA, '/^,/'), new wfWAFLexerTokenMatcher(self::EXPR_OR, '/^\\|\\|/'), new wfWAFLexerTokenMatcher(self::EXPR_AND, '/^&&/'), new wfWAFLexerTokenMatcher(self::BIT_LEFT_SHIFT, '/^\\<\\</'), new wfWAFLexerTokenMatcher(self::BIT_RIGHT_SHIFT, '/^\\>\\>/'), new wfWAFLexerTokenMatcher(self::EQUALS_SYMBOL, '/^(?:\\=|\\<\\=\\>)/'), new wfWAFLexerTokenMatcher(self::ARROW, '/^\\=\\>/'), new wfWAFLexerTokenMatcher(self::LESS_THAN_EQUAL_TO, '/^\\<\\=/'), new wfWAFLexerTokenMatcher(self::GREATER_THAN_EQUAL_TO, '/^\\>\\=/'), new wfWAFLexerTokenMatcher(self::NOT_EQUALS, '/^(?:\\<\\>|(?:\\!|\\~|\\^)\\=)/'), new wfWAFLexerTokenMatcher(self::LESS_THAN, '/^\\</'), new wfWAFLexerTokenMatcher(self::GREATER_THAN, '/^\\>/'), new wfWAFLexerTokenMatcher(self::SET_VAR, '/^:\\=/'), new wfWAFLexerTokenMatcher(self::BIT_XOR, '/^\\^/'), new wfWAFLexerTokenMatcher(self::BIT_INVERSION, '/^\\~/'), new wfWAFLexerTokenMatcher(self::BIT_OR, '/^\\|/'), new wfWAFLexerTokenMatcher(self::PLUS, '/^\\+/'), new wfWAFLexerTokenMatcher(self::MINUS, '/^\\-/'), new wfWAFLexerTokenMatcher(self::ASTERISK, '/^\\*/'), new wfWAFLexerTokenMatcher(self::DIVISION, '/^\\//'), new wfWAFLexerTokenMatcher(self::MOD, '/^%/'), new wfWAFLexerTokenMatcher(self::EXPR_NOT, '/^\\!/'), new wfWAFLexerTokenMatcher(self::BIT_AND, '/^&/'), new wfWAFLexerTokenMatcher(self::RIGHT_BRACKET, '/^\\]/'), new wfWAFLexerTokenMatcher(self::LEFT_BRACKET, '/^\\[/'), new wfWAFLexerTokenMatcher(self::SEMICOLON, '/^;/'), new wfWAFLexerTokenMatcher(self::COLON, '/^:/'), ); } return $tokenMatchers; } /** * @var string */ private $sql; /** * @var wfWAFStringScanner */ private $scanner; /** * wfWAFRuleLexer constructor. * @param $sql * @param int $flags */ public function __construct($sql = null, $flags = 0) { $this->scanner = new wfWAFStringScanner(); $this->tokenMatchers = self::getLexerTokenMatchers(); $this->setSQL($sql); $this->setFlags($flags); } /** * @return array * @throws wfWAFParserSyntaxError */ public function tokenize() { $tokens = array(); while ($token = $this->nextToken()) { $tokens[] = $token; } return $tokens; } /** * @return bool|wfWAFLexerToken * @throws wfWAFParserSyntaxError */ public function nextToken() { if (!$this->scanner->eos()) { /** @var wfWAFLexerTokenMatcher $tokenMatcher */ foreach ($this->tokenMatchers as $tokenMatcher) { $this->scanner->skip('/^\s+/s'); if ($this->scanner->eos()) { return false; } if (($this->flags & self::FLAG_TOKENIZE_MYSQL_PORTABLE_COMMENTS) === 0 && ($tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_START || $tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_END) ) { continue; } if (!$this->hasPortableCommentStart && $tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_END) { continue; } if ($tokenMatcher->useMaximalMunch() && ($match = $this->scanner->check($tokenMatcher->getMatch())) !== null) { $biggestToken = $this->createToken($tokenMatcher->getTokenID(), $match); /** @var wfWAFLexerTokenMatcher $tokenMatcher2 */ foreach ($this->tokenMatchers as $tokenMatcher2) { if ($tokenMatcher === $tokenMatcher2) { continue; } if (($match2 = $this->scanner->check($tokenMatcher2->getMatch())) !== null) { $biggestToken2 = $this->createToken($tokenMatcher2->getTokenID(), $match2); if (wfWAFUtils::strlen($biggestToken2->getValue()) > wfWAFUtils::strlen($biggestToken->getValue())) { $biggestToken = $biggestToken2; } } } $this->scanner->advancePointer(wfWAFUtils::strlen($biggestToken->getValue())); return $biggestToken; } else if (($match = $this->scanner->scan($tokenMatcher->getMatch())) !== null) { $token = $this->createToken($tokenMatcher->getTokenID(), $match); if ($tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_START) { $this->hasPortableCommentStart = true; } else if ($tokenMatcher->getTokenID() === self::MYSQL_PORTABLE_COMMENT_END) { $this->hasPortableCommentStart = false; } return $token; } } $char = $this->scanner->scanChar(); $e = new wfWAFParserSyntaxError(sprintf('Invalid character "%s" (\x%02x) found on line %d, column %d', $char, ord($char), $this->scanner->getLine(), $this->scanner->getColumn())); $e->setParseLine($this->scanner->getLine()); $e->setParseColumn($this->scanner->getColumn()); throw $e; } return false; } public function hasMoreTokens() { } /** * @param $type * @param $value * @return wfWAFLexerToken */ protected function createToken($type, $value) { return new wfWAFLexerToken($type, $value, $this->scanner->getLine(), $this->scanner->getColumn()); } /** * @return string */ public function getSQL() { return $this->sql; } /** * @param string $sql */ public function setSQL($sql) { if (is_string($sql)) { $this->scanner->setString($sql); } $this->sql = $sql; } /** * @return int */ public function getFlags() { return $this->flags; } /** * @param int $flags */ public function setFlags($flags) { $this->flags = $flags; } /** * Check if the given token type represents a literal value * @param $type the token type * @return bool true if the provided type is a value literal, false otherwise */ public function isValueLiteral($type){ switch($type){ case self::DOUBLE_STRING_LITERAL: case self::SINGLE_STRING_LITERAL: case self::INTEGER_LITERAL: case self::REAL_NUMBER_LITERAL: case self::BINARY_NUMBER_LITERAL: case self::HEX_NUMBER_LITERAL: case self::COMMA: return true; default: return false; } } } class wfWAFLexerTokenMatcher { /** * @var mixed */ private $tokenID; /** * @var string */ private $match; /** * @var bool */ private $useMaximalMunch; /** * @param mixed $tokenID * @param string $match * @param bool $useMaximalMunch */ public function __construct($tokenID, $match, $useMaximalMunch = false) { $this->tokenID = $tokenID; $this->match = $match; $this->useMaximalMunch = $useMaximalMunch; } /** * @return bool */ public function useMaximalMunch() { return $this->useMaximalMunch; } /** * @return mixed */ public function getTokenID() { return $this->tokenID; } /** * @param mixed $tokenID */ public function setTokenID($tokenID) { $this->tokenID = $tokenID; } /** * @return string */ public function getMatch() { return $this->match; } /** * @param string $match */ public function setMatch($match) { $this->match = $match; } } }