PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / vendor / league / uri / src / UriTemplate / Expression.php

Expression.php in Defender Security – Malware Scanner, Login Security & Firewall 5.7.2, at vendor/league/uri/src/UriTemplate/Expression.php

330 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * League.Uri (https://uri.thephpleague.com)
5 *
6 * (c) Ignace Nyamagana Butera <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 declare(strict_types=1);
13
14 namespace League\Uri\UriTemplate;
15
16 use League\Uri\Exceptions\SyntaxError;
17 use League\Uri\Exceptions\TemplateCanNotBeExpanded;
18 use function array_filter;
19 use function array_keys;
20 use function array_map;
21 use function array_unique;
22 use function explode;
23 use function implode;
24 use function preg_match;
25 use function rawurlencode;
26 use function str_replace;
27 use function strpos;
28 use function substr;
29
30 final class Expression
31 {
32 /**
33 * Expression regular expression pattern.
34 *
35 * @link https://tools.ietf.org/html/rfc6570#section-2.2
36 */
37 private const REGEXP_EXPRESSION = '/^\{
38 (?:
39 (?<operator>[\.\/;\?&\=,\!@\|\+#])?
40 (?<variables>[^\}]*)
41 )
42 \}$/x';
43
44 /**
45 * Reserved Operator characters.
46 *
47 * @link https://tools.ietf.org/html/rfc6570#section-2.2
48 */
49 private const RESERVED_OPERATOR = '=,!@|';
50
51 /**
52 * Processing behavior according to the expression type operator.
53 *
54 * @link https://tools.ietf.org/html/rfc6570#appendix-A
55 */
56 private const OPERATOR_HASH_LOOKUP = [
57 '' => ['prefix' => '', 'joiner' => ',', 'query' => false],
58 '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
59 '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
60 '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
61 '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
62 ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
63 '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
64 '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true],
65 ];
66
67 private string $operator;
68 /** @var array<VarSpecifier> */
69 private array $varSpecifiers;
70 private string $joiner;
71 /** @var array<string> */
72 private array $variableNames;
73 private string $expressionString;
74
75 private function __construct(string $operator, VarSpecifier ...$varSpecifiers)
76 {
77 $this->operator = $operator;
78 $this->varSpecifiers = $varSpecifiers;
79 $this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner'];
80 $this->variableNames = $this->setVariableNames();
81 $this->expressionString = $this->setExpressionString();
82 }
83
84 /**
85 * @return array<string>
86 */
87 private function setVariableNames(): array
88 {
89 return array_unique(array_map(
90 static fn (VarSpecifier $varSpecifier): string => $varSpecifier->name(),
91 $this->varSpecifiers
92 ));
93 }
94
95 private function setExpressionString(): string
96 {
97 $varSpecifierString = implode(',', array_map(
98 static fn (VarSpecifier $variable): string => $variable->toString(),
99 $this->varSpecifiers
100 ));
101
102 return '{'.$this->operator.$varSpecifierString.'}';
103 }
104
105 /**
106 * {@inheritDoc}
107 */
108 public static function __set_state(array $properties): self
109 {
110 return new self($properties['operator'], ...$properties['varSpecifiers']);
111 }
112
113 /**
114 * @throws SyntaxError if the expression is invalid
115 * @throws SyntaxError if the operator used in the expression is invalid
116 * @throws SyntaxError if the variable specifiers is invalid
117 */
118 public static function createFromString(string $expression): self
119 {
120 if (1 !== preg_match(self::REGEXP_EXPRESSION, $expression, $parts)) {
121 throw new SyntaxError('The expression "'.$expression.'" is invalid.');
122 }
123
124 /** @var array{operator:string, variables:string} $parts */
125 $parts = $parts + ['operator' => ''];
126 if ('' !== $parts['operator'] && false !== strpos(self::RESERVED_OPERATOR, $parts['operator'])) {
127 throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.');
128 }
129
130 return new Expression($parts['operator'], ...array_map(
131 static fn (string $varSpec): VarSpecifier => VarSpecifier::createFromString($varSpec),
132 explode(',', $parts['variables'])
133 ));
134 }
135
136 /**
137 * Returns the expression string representation.
138 *
139 */
140 public function toString(): string
141 {
142 return $this->expressionString;
143 }
144
145 /**
146 * @return array<string>
147 */
148 public function variableNames(): array
149 {
150 return $this->variableNames;
151 }
152
153 public function expand(VariableBag $variables): string
154 {
155 $parts = [];
156 foreach ($this->varSpecifiers as $varSpecifier) {
157 $parts[] = $this->replace($varSpecifier, $variables);
158 }
159
160 $expanded = implode($this->joiner, array_filter($parts, static fn ($value): bool => '' !== $value));
161 if ('' === $expanded) {
162 return $expanded;
163 }
164
165 $prefix = self::OPERATOR_HASH_LOOKUP[$this->operator]['prefix'];
166 if ('' === $prefix) {
167 return $expanded;
168 }
169
170 return $prefix.$expanded;
171 }
172
173 /**
174 * Replaces an expression with the given variables.
175 *
176 * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
177 * @throws TemplateCanNotBeExpanded if the variables contains nested array values
178 */
179 private function replace(VarSpecifier $varSpec, VariableBag $variables): string
180 {
181 $value = $variables->fetch($varSpec->name());
182 if (null === $value) {
183 return '';
184 }
185
186 $useQuery = self::OPERATOR_HASH_LOOKUP[$this->operator]['query'];
187 [$expanded, $actualQuery] = $this->inject($value, $varSpec, $useQuery);
188 if (!$actualQuery) {
189 return $expanded;
190 }
191
192 if ('&' !== $this->joiner && '' === $expanded) {
193 return $varSpec->name();
194 }
195
196 return $varSpec->name().'='.$expanded;
197 }
198
199 /**
200 * @param string|array<string> $value
201 *
202 * @return array{0:string, 1:bool}
203 */
204 private function inject($value, VarSpecifier $varSpec, bool $useQuery): array
205 {
206 if (is_string($value)) {
207 return $this->replaceString($value, $varSpec, $useQuery);
208 }
209
210 return $this->replaceList($value, $varSpec, $useQuery);
211 }
212
213 /**
214 * Expands an expression using a string value.
215 *
216 * @return array{0:string, 1:bool}
217 */
218 private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array
219 {
220 if (':' === $varSpec->modifier()) {
221 $value = substr($value, 0, $varSpec->position());
222 }
223
224 $expanded = rawurlencode($value);
225 if ('+' === $this->operator || '#' === $this->operator) {
226 return [$this->decodeReserved($expanded), $useQuery];
227 }
228
229 return [$expanded, $useQuery];
230 }
231
232 /**
233 * Expands an expression using a list of values.
234 *
235 * @param array<string> $value
236 *
237 * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
238 *
239 * @return array{0:string, 1:bool}
240 */
241 private function replaceList(array $value, VarSpecifier $varSpec, bool $useQuery): array
242 {
243 if ([] === $value) {
244 return ['', false];
245 }
246
247 if (':' === $varSpec->modifier()) {
248 throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name());
249 }
250
251 $pairs = [];
252 $isAssoc = $this->isAssoc($value);
253 foreach ($value as $key => $var) {
254 if ($isAssoc) {
255 $key = rawurlencode((string) $key);
256 }
257
258 $var = rawurlencode($var);
259 if ('+' === $this->operator || '#' === $this->operator) {
260 $var = $this->decodeReserved($var);
261 }
262
263 if ('*' === $varSpec->modifier()) {
264 if ($isAssoc) {
265 $var = $key.'='.$var;
266 } elseif ($key > 0 && $useQuery) {
267 $var = $varSpec->name().'='.$var;
268 }
269 }
270
271 $pairs[$key] = $var;
272 }
273
274 if ('*' === $varSpec->modifier()) {
275 if ($isAssoc) {
276 // Don't prepend the value name when using the explode
277 // modifier with an associative array.
278 $useQuery = false;
279 }
280
281 return [implode($this->joiner, $pairs), $useQuery];
282 }
283
284 if ($isAssoc) {
285 // When an associative array is encountered and the
286 // explode modifier is not set, then the result must be
287 // a comma separated list of keys followed by their
288 // respective values.
289 foreach ($pairs as $offset => &$data) {
290 $data = $offset.','.$data;
291 }
292
293 unset($data);
294 }
295
296 return [implode(',', $pairs), $useQuery];
297 }
298
299 /**
300 * Determines if an array is associative.
301 *
302 * This makes the assumption that input arrays are sequences or hashes.
303 * This assumption is a trade-off for accuracy in favor of speed, but it
304 * should work in almost every case where input is supplied for a URI
305 * template.
306 */
307 private function isAssoc(array $array): bool
308 {
309 return [] !== $array && 0 !== array_keys($array)[0];
310 }
311
312 /**
313 * Removes percent encoding on reserved characters (used with + and # modifiers).
314 */
315 private function decodeReserved(string $str): string
316 {
317 static $delimiters = [
318 ':', '/', '?', '#', '[', ']', '@', '!', '$',
319 '&', '\'', '(', ')', '*', '+', ',', ';', '=',
320 ];
321
322 static $delimitersEncoded = [
323 '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24',
324 '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D',
325 ];
326
327 return str_replace($delimitersEncoded, $delimiters, $str);
328 }
329 }
330