PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.6
ShopBuilder – WooCommerce Builder For Elementor v3.2.6
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / vendor / codesvault / howdy-qb / src / Validation / IdentifierValidator.php

IdentifierValidator.php in ShopBuilder – WooCommerce Builder For Elementor 3.2.6, at vendor/codesvault/howdy-qb/src/Validation/IdentifierValidator.php

175 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace CodesVault\Howdyqb\Validation;
4
5 use CodesVault\Howdyqb\Utilities;
6 use InvalidArgumentException;
7
8 class IdentifierValidator
9 {
10 private const VALID_IDENTIFIER_PATTERN = '/^[a-zA-Z_][a-zA-Z0-9_]*$/';
11
12 private const MAX_IDENTIFIER_LENGTH = 64;
13
14 private const VALID_OPERATORS = [
15 '=', '!=', '<>', '<', '>', '<=', '>=',
16 'LIKE', 'NOT LIKE',
17 'IN', 'NOT IN',
18 'BETWEEN', 'NOT BETWEEN',
19 'IS', 'IS NOT',
20 'REGEXP', 'NOT REGEXP',
21 'EXISTS', 'NOT EXISTS',
22 ];
23
24 public static function validateTableName(string $tableName): string
25 {
26 if (strlen($tableName) > self::MAX_IDENTIFIER_LENGTH) {
27 throw new \InvalidArgumentException(
28 sprintf('Table name exceeds maximum length of %d characters.', self::MAX_IDENTIFIER_LENGTH)
29 );
30 }
31
32 if (!self::isValidIdentifier($tableName)) {
33 throw new \InvalidArgumentException(
34 sprintf('Invalid table name: "%s". Table names must start with a letter or underscore and contain only alphanumeric characters and underscores.', $tableName)
35 );
36 }
37 return self::escapeIdentifier($tableName);
38 }
39
40 public static function validateColumnName(string $columnName): string
41 {
42 $columnName = trim($columnName);
43
44 if (empty($columnName)) {
45 throw new InvalidArgumentException('Column name cannot be empty.');
46 }
47
48 // Allow wildcard selector
49 if ($columnName === '*') {
50 return '*';
51 }
52
53 // Handle table.column syntax
54 if (strpos($columnName, '.') !== false) {
55 $parts = explode('.', $columnName);
56 if (count($parts) !== 2) {
57 throw new InvalidArgumentException(
58 sprintf('Invalid column name format: "%s". Use "table.column" or just "column".', $columnName)
59 );
60 }
61
62 if (!self::isValidIdentifier($parts[0]) || !self::isValidIdentifier($parts[1])) {
63 throw new InvalidArgumentException(
64 sprintf('Invalid column name: "%s".', $columnName)
65 );
66 }
67
68 return self::escapeIdentifier($parts[0]) . '.' . self::escapeIdentifier($parts[1]);
69 }
70
71 if (strlen($columnName) > self::MAX_IDENTIFIER_LENGTH) {
72 throw new InvalidArgumentException(
73 sprintf('Column name exceeds maximum length of %d characters.', self::MAX_IDENTIFIER_LENGTH)
74 );
75 }
76
77 if (!self::isValidIdentifier($columnName)) {
78 throw new InvalidArgumentException(
79 sprintf('Invalid column name: "%s". Column names must start with a letter or underscore and contain only alphanumeric characters and underscores.', $columnName)
80 );
81 }
82
83 return self::escapeIdentifier($columnName);
84 }
85
86 public static function validateColumnNames(array $columnNames): array
87 {
88 $validatedColumns = [];
89 foreach ($columnNames as $columnName) {
90 $validatedColumns[] = self::validateColumnName($columnName);
91 }
92 return $validatedColumns;
93 }
94
95 private static function isValidIdentifier(string $identifier): bool
96 {
97 if (self::containsSqlInjectionPatterns($identifier)) {
98 return false;
99 }
100
101 return preg_match(self::VALID_IDENTIFIER_PATTERN, $identifier) === 1;
102 }
103
104 private static function containsSqlInjectionPatterns(string $identifier): bool
105 {
106 $dangerousPatterns = [
107 '/[;\'"\\\\]/', // Semicolons, quotes, backslashes
108 '/--/', // SQL comments
109 '/\/\*/', // Block comment start
110 '/\*\//', // Block comment end
111 '/\bOR\b/i', // OR keyword
112 '/\bAND\b/i', // AND keyword
113 '/\bUNION\b/i', // UNION keyword
114 '/\bSELECT\b/i', // SELECT keyword
115 '/\bDROP\b/i', // DROP keyword
116 '/\bDELETE\b/i', // DELETE keyword
117 '/\bINSERT\b/i', // INSERT keyword
118 '/\bUPDATE\b/i', // UPDATE keyword
119 '/\bEXEC\b/i', // EXEC keyword
120 '/0x[0-9a-fA-F]+/', // Hex values
121 ];
122
123 foreach ($dangerousPatterns as $pattern) {
124 if (preg_match($pattern, $identifier)) {
125 return true;
126 }
127 }
128
129 return false;
130 }
131
132 public static function escapeIdentifier(string $identifier): string
133 {
134 $identifier = str_replace('`', '', $identifier);
135 return '`' . $identifier . '`';
136 }
137
138 public static function validateTableNameWithAlias(string $tableName): string
139 {
140 $tableName = trim($tableName);
141
142 if (empty($tableName)) {
143 throw new InvalidArgumentException('Table name cannot be empty.');
144 }
145
146 // Handle table AS alias or table alias syntax
147 $parts = explode(' ', $tableName);
148 if (count($parts) === 2) {
149 $tablePart = self::validateTableName(Utilities::get_db_configs()->prefix . $parts[0]);
150 $aliasPart = self::validateTableName($parts[1]);
151
152 return $tablePart . ' ' . $aliasPart;
153 }
154
155 return self::validateTableName($tableName);
156 }
157
158 public static function validateOperator(string $operator): string
159 {
160 $operator = trim(strtoupper($operator));
161
162 if (empty($operator)) {
163 throw new InvalidArgumentException('Operator cannot be empty.');
164 }
165
166 if (!in_array($operator, self::VALID_OPERATORS, true)) {
167 throw new InvalidArgumentException(
168 sprintf('Invalid operator: "%s".', $operator)
169 );
170 }
171
172 return $operator;
173 }
174 }
175