Issues (51)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/PackagePrivate/Param.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace ParamProcessor\PackagePrivate;
4
5
use DataValues\DataValue;
6
use Exception;
7
use ParamProcessor\IParam;
8
use ParamProcessor\IParamDefinition;
9
use ParamProcessor\Options;
10
use ParamProcessor\ParamDefinition;
11
use ParamProcessor\ParamDefinitionFactory;
12
use ParamProcessor\ProcessingError;
13
use ValueParsers\NullParser;
14
use ValueParsers\ParseException;
15
use ValueParsers\ValueParser;
16
17
/**
18
 * Package private!
19
 *
20
 * Parameter class, representing the "instance" of a parameter.
21
 * Holds a ParamDefinition, user provided input (name & value) and processing state.
22
 *
23
 * @licence GNU GPL v2+
24
 * @author Jeroen De Dauw < [email protected] >
25
 */
26
class Param implements IParam {
27
28
	/**
29
	 * Indicates whether parameters not found in the criteria list
30
	 * should be stored in case they are not accepted. The default is false.
31
	 *
32
	 * @deprecated since 1.7
33
	 */
34
	public static $accumulateParameterErrors = false;
35
36
	/**
37
	 * The original parameter name as provided by the user. This can be the
38
	 * main name or an alias.
39
	 * @var string
40
	 */
41
	protected $originalName;
42
43
	/**
44
	 * The original value as provided by the user. This is mainly retained for
45
	 * usage in error messages when the parameter turns out to be invalid.
46
	 * @var string
47
	 */
48
	protected $originalValue;
49
50
	/**
51
	 * @var mixed
52
	 */
53
	protected $value;
54
55
	/**
56
	 * Keeps track of how many times the parameter has been set by the user.
57
	 * This is used to detect overrides and for figuring out a parameter is missing.
58
	 * @var integer
59
	 */
60
	protected $setCount = 0;
61
62
	/**
63
	 * @var ProcessingError[]
64
	 */
65
	protected $errors = [];
66
67
	/**
68
	 * Indicates if the parameter was set to it's default.
69
	 * @var boolean
70
	 */
71
	protected $defaulted = false;
72
73
	/**
74
	 * @var ParamDefinition
75
	 */
76
	protected $definition;
77
78 107
	public function __construct( IParamDefinition $definition ) {
79 107
		$this->definition = $definition;
0 ignored issues
show
Documentation Bug introduced by
$definition is of type object<ParamProcessor\IParamDefinition>, but the property $definition was declared to be of type object<ParamProcessor\ParamDefinition>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
80 107
	}
81
82
	/**
83
	 * Sets and cleans the original value and name.
84
	 */
85 105
	public function setUserValue( string $paramName, $paramValue, Options $options ): bool {
86 105
		if ( $this->setCount > 0 && !$options->acceptOverriding() ) {
87
			// TODO
88
			return false;
89
		}
90
91 105
		$this->originalName = $paramName;
92 105
		$this->originalValue = $paramValue;
93
94 105
		$this->cleanValue( $options );
95
96 105
		$this->setCount++;
97
98 105
		return true;
99
	}
100
101
	/**
102
	 * @param mixed $value
103
	 */
104 93
	public function setValue( $value ) {
105 93
		$this->value = $value;
106 93
	}
107
108
	/**
109
	 * Sets the $value to a cleaned value of $originalValue.
110
	 */
111 105
	protected function cleanValue( Options $options ) {
112 105
		if ( $this->definition->isList() ) {
113
			$this->value = explode( $this->definition->getDelimiter(), $this->originalValue );
114
		}
115
		else {
116 105
			$this->value = $this->originalValue;
117
		}
118
119 105
		if ( $this->shouldTrim( $options ) ) {
120 105
			$this->trimValue();
121
		}
122
123 105
		if ( $this->shouldLowercase( $options ) ) {
124
			$this->lowercaseValue();
125
		}
126 105
	}
127
128 105
	private function shouldTrim( Options $options ): bool {
129 105
		$trim = $this->definition->trimDuringClean();
130
131 105
		if ( $trim === true ) {
132
			return true;
133
		}
134
135 105
		return is_null( $trim ) && $options->trimValues();
136
	}
137
138 105
	private function trimValue() {
139 105
		if ( is_string( $this->value ) ) {
140 99
			$this->value = trim( $this->value );
141
		}
142 58
		elseif ( $this->definition->isList() ) {
143
			foreach ( $this->value as &$element ) {
144
				if ( is_string( $element ) ) {
145
					$element = trim( $element );
146
				}
147
			}
148
		}
149 105
	}
150
151 105
	private function shouldLowercase( Options $options ): bool {
152 105
		if ( $options->lowercaseValues() ) {
153
			return true;
154
		}
155
156 105
		$definitionOptions = $this->definition->getOptions();
157
158 105
		return array_key_exists( 'tolower', $definitionOptions ) && $definitionOptions['tolower'];
159
	}
160
161
	private function lowercaseValue() {
162
		if ( $this->definition->isList() ) {
163
			foreach ( $this->value as &$element ) {
164
				if ( is_string( $element ) ) {
165
					$element = strtolower( $element );
166
				}
167
			}
168
		}
169
		elseif ( is_string( $this->value ) ) {
170
			$this->value = strtolower( $this->value );
171
		}
172
	}
173
174
	/**
175
	 * Parameter processing entry point.
176
	 * Processes the parameter. This includes parsing, validation and additional formatting.
177
	 *
178
	 * @param ParamDefinition[] $definitions
179
	 * @param Param[] $params
180
	 * @param Options $options
181
	 *
182
	 * @throws Exception
183
	 */
184 105
	public function process( array &$definitions, array $params, Options $options ) {
185 105
		if ( $this->setCount == 0 ) {
186 1
			if ( $this->definition->isRequired() ) {
187
				// This should not occur, so throw an exception.
188
				throw new Exception( 'Attempted to validate a required parameter without first setting a value.' );
189
			}
190
			else {
191 1
				$this->setToDefault();
192
			}
193
		}
194
		else {
195 104
			$this->parseAndValidate( $options );
196
		}
197
198 105
		if ( !$this->hasFatalError() && ( $this->definition->shouldManipulateDefault() || !$this->wasSetToDefault() ) ) {
199 93
			$this->definition->format( $this, $definitions, $params );
200
		}
201 105
	}
202
203 104
	public function getValueParser( Options $options ): ValueParser {
204 104
		$parser = $this->definition->getValueParser();
205
206 104
		if ( !( $parser instanceof NullParser ) ) {
207
			return $parser;
208
		}
209
210
		// TODO: inject factory
211 104
		$type = ParamDefinitionFactory::singleton()->getType( $this->definition->getType() );
212
213 104
		$parserClass = $options->isStringlyTyped() ? $type->getStringParserClass() : $type->getTypedParserClass();
214
215 104
		return new $parserClass( new \ValueParsers\ParserOptions( $this->definition->getOptions() ) );
216
	}
217
218 104
	protected function parseAndValidate( Options $options ) {
219 104
		$parser = $this->getValueParser( $options );
220
221 104
		if ( $this->definition->isList() ) {
222
			$values = [];
223
224
			foreach ( $this->getValue() as $value ) {
225
				$parsedValue = $this->parseAndValidateValue( $parser, $value );
226
227
				if ( is_array( $parsedValue ) ) {
228
					$values[] = $parsedValue[0];
229
				}
230
			}
231
232
			$this->value = $values;
233
		}
234
		else {
235 104
			$parsedValue = $this->parseAndValidateValue( $parser, $this->getValue() );
236
237 104
			if ( is_array( $parsedValue ) ) {
238 99
				$this->value = $parsedValue[0];
239
			}
240
		}
241
242 104
		$this->setToDefaultIfNeeded();
243 104
	}
244
245
	/**
246
	 * Parses and validates the provided with with specified parser.
247
	 * The result is returned in an array on success. On fail, false is returned.
248
	 * The result is wrapped in an array since we need to be able to distinguish
249
	 * between the method returning false and the value being false.
250
	 *
251
	 * Parsing and validation errors get added to $this->errors.
252
	 *
253
	 * @since 1.0
254
	 *
255
	 * @param ValueParser $parser
256
	 * @param mixed $value
257
	 *
258
	 * @return array|bool
259
	 */
260 104
	protected function parseAndValidateValue( ValueParser $parser, $value ) {
261
		try {
262 104
			$value = $parser->parse( $value );
263
		}
264 13
		catch ( ParseException $parseException ) {
265 13
			$this->registerProcessingError( $parseException->getMessage() );
266 13
			return false;
267
		}
268
269 99
		if ( $value instanceof DataValue ) {
270 58
			$value = $value->getValue();
271
		}
272
273 99
		$this->validateValue( $value );
274
275 99
		return [ $value ];
276
	}
277
278 35
	protected function registerProcessingError( string $message ) {
279 35
		$this->errors[] = $this->newProcessingError( $message );
280 35
	}
281
282 35
	protected function newProcessingError( string $message ): ProcessingError {
283 35
		$severity = $this->isRequired() ? ProcessingError::SEVERITY_FATAL : ProcessingError::SEVERITY_NORMAL;
284 35
		return new ProcessingError( $message, $severity );
285
	}
286
287
	/**
288
	 * @param mixed $value
289
	 */
290 99
	protected function validateValue( $value ) {
291 99
		$validationCallback = $this->definition->getValidationCallback();
292
293 99
		if ( $validationCallback !== null && $validationCallback( $value ) !== true ) {
294 7
			$this->registerProcessingError( 'Validation callback failed' );
295
		}
296
		else {
297 99
			$validator = $this->definition->getValueValidator();
298 99
			if ( method_exists( $validator, 'setOptions' ) ) {
299 99
				$validator->setOptions( $this->definition->getOptions() );
300
			}
301 99
			$validationResult = $validator->validate( $value );
302
303 99
			if ( !$validationResult->isValid() ) {
304 24
				foreach ( $validationResult->getErrors() as $error ) {
305 24
					$this->registerProcessingError( $error->getText() );
306
				}
307
			}
308
		}
309 99
	}
310
311
	/**
312
	 * Sets the parameter value to the default if needed.
313
	 */
314 104
	protected function setToDefaultIfNeeded() {
315 104
		if ( $this->shouldSetToDefault() ) {
316 15
			$this->setToDefault();
317
		}
318 104
	}
319
320 104
	private function shouldSetToDefault(): bool {
321 104
		if ( $this->hasFatalError() ) {
322 20
			return false;
323
		}
324
325 92
		if ( $this->definition->isList() ) {
326
			return $this->errors !== [] && $this->value === [];
327
		}
328
329 92
		return $this->errors !== [];
330
	}
331
332
	/**
333
	 * Returns the original use-provided name.
334
	 *
335
	 * @throws Exception
336
	 * @return string
337
	 */
338 36
	public function getOriginalName(): string {
339 36
		if ( $this->setCount == 0 ) {
340
			throw new Exception( 'No user input set to the parameter yet, so the original name does not exist' );
341
		}
342 36
		return $this->originalName;
343
	}
344
345
	/**
346
	 * Returns the original use-provided value.
347
	 *
348
	 * @throws Exception
349
	 * @return mixed
350
	 */
351 36
	public function getOriginalValue() {
352 36
		if ( $this->setCount == 0 ) {
353
			throw new Exception( 'No user input set to the parameter yet, so the original value does not exist' );
354
		}
355 36
		return $this->originalValue;
356
	}
357
358
	/**
359
	 * Returns all validation errors that occurred so far.
360
	 *
361
	 * @return ProcessingError[]
362
	 */
363 95
	public function getErrors(): array {
364 95
		return $this->errors;
365
	}
366
367
	/**
368
	 * Sets the parameter value to the default.
369
	 */
370 16
	protected function setToDefault() {
371 16
		$this->defaulted = true;
372 16
		$this->value = $this->definition->getDefault();
373 16
	}
374
375 49
	public function wasSetToDefault(): bool {
376 49
		return $this->defaulted;
377
	}
378
379 105
	public function hasFatalError(): bool {
380 105
		foreach ( $this->errors as $error ) {
381 35
			if ( $error->isFatal() ) {
382 20
				return true;
383
			}
384
		}
385
386 93
		return false;
387
	}
388
389
	/**
390
	 * Returns the ParamDefinition this Param was constructed from.
391
	 */
392
	public function getDefinition(): ParamDefinition {
393
		return $this->definition;
394
	}
395
396
	/**
397
	 * @return mixed
398
	 */
399 105
	public function &getValue() {
400 105
		return $this->value;
401
	}
402
403 36
	public function isRequired(): bool {
404 36
		return $this->definition->isRequired();
405
	}
406
407 49
	public function getName(): string {
408 49
		return $this->definition->getName();
409
	}
410
411
	/**
412
	 * @return string[]
413
	 */
414
	public function getAliases(): array {
415
		return $this->definition->getAliases();
416
	}
417
418
}