Issues (2010)

Security Analysis    not enabled

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.

wp-includes/rest-api/class-wp-rest-request.php (2 issues)

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
 * REST API: WP_REST_Request class
4
 *
5
 * @package WordPress
6
 * @subpackage REST_API
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used to implement a REST request object.
12
 *
13
 * Contains data from the request, to be passed to the callback.
14
 *
15
 * Note: This implements ArrayAccess, and acts as an array of parameters when
16
 * used in that manner. It does not use ArrayObject (as we cannot rely on SPL),
17
 * so be aware it may have non-array behaviour in some cases.
18
 *
19
 * Note: When using features provided by ArrayAccess, be aware that WordPress deliberately
20
 * does not distinguish between arguments of the same name for different request methods.
21
 * For instance, in a request with `GET id=1` and `POST id=2`, `$request['id']` will equal
22
 * 2 (`POST`) not 1 (`GET`). For more precision between request methods, use
23
 * WP_REST_Request::get_body_params(), WP_REST_Request::get_url_params(), etc.
24
 *
25
 * @since 4.4.0
26
 *
27
 * @see ArrayAccess
28
 */
29
class WP_REST_Request implements ArrayAccess {
30
31
	/**
32
	 * HTTP method.
33
	 *
34
	 * @since 4.4.0
35
	 * @access protected
36
	 * @var string
37
	 */
38
	protected $method = '';
39
40
	/**
41
	 * Parameters passed to the request.
42
	 *
43
	 * These typically come from the `$_GET`, `$_POST` and `$_FILES`
44
	 * superglobals when being created from the global scope.
45
	 *
46
	 * @since 4.4.0
47
	 * @access protected
48
	 * @var array Contains GET, POST and FILES keys mapping to arrays of data.
49
	 */
50
	protected $params;
51
52
	/**
53
	 * HTTP headers for the request.
54
	 *
55
	 * @since 4.4.0
56
	 * @access protected
57
	 * @var array Map of key to value. Key is always lowercase, as per HTTP specification.
58
	 */
59
	protected $headers = array();
60
61
	/**
62
	 * Body data.
63
	 *
64
	 * @since 4.4.0
65
	 * @access protected
66
	 * @var string Binary data from the request.
67
	 */
68
	protected $body = null;
69
70
	/**
71
	 * Route matched for the request.
72
	 *
73
	 * @since 4.4.0
74
	 * @access protected
75
	 * @var string
76
	 */
77
	protected $route;
78
79
	/**
80
	 * Attributes (options) for the route that was matched.
81
	 *
82
	 * This is the options array used when the route was registered, typically
83
	 * containing the callback as well as the valid methods for the route.
84
	 *
85
	 * @since 4.4.0
86
	 * @access protected
87
	 * @var array Attributes for the request.
88
	 */
89
	protected $attributes = array();
90
91
	/**
92
	 * Used to determine if the JSON data has been parsed yet.
93
	 *
94
	 * Allows lazy-parsing of JSON data where possible.
95
	 *
96
	 * @since 4.4.0
97
	 * @access protected
98
	 * @var bool
99
	 */
100
	protected $parsed_json = false;
101
102
	/**
103
	 * Used to determine if the body data has been parsed yet.
104
	 *
105
	 * @since 4.4.0
106
	 * @access protected
107
	 * @var bool
108
	 */
109
	protected $parsed_body = false;
110
111
	/**
112
	 * Constructor.
113
	 *
114
	 * @since 4.4.0
115
	 * @access public
116
	 *
117
	 * @param string $method     Optional. Request method. Default empty.
118
	 * @param string $route      Optional. Request route. Default empty.
119
	 * @param array  $attributes Optional. Request attributes. Default empty array.
120
	 */
121
	public function __construct( $method = '', $route = '', $attributes = array() ) {
122
		$this->params = array(
123
			'URL'   => array(),
124
			'GET'   => array(),
125
			'POST'  => array(),
126
			'FILES' => array(),
127
128
			// See parse_json_params.
129
			'JSON'  => null,
130
131
			'defaults' => array(),
132
		);
133
134
		$this->set_method( $method );
135
		$this->set_route( $route );
136
		$this->set_attributes( $attributes );
137
	}
138
139
	/**
140
	 * Retrieves the HTTP method for the request.
141
	 *
142
	 * @since 4.4.0
143
	 * @access public
144
	 *
145
	 * @return string HTTP method.
146
	 */
147
	public function get_method() {
148
		return $this->method;
149
	}
150
151
	/**
152
	 * Sets HTTP method for the request.
153
	 *
154
	 * @since 4.4.0
155
	 * @access public
156
	 *
157
	 * @param string $method HTTP method.
158
	 */
159
	public function set_method( $method ) {
160
		$this->method = strtoupper( $method );
161
	}
162
163
	/**
164
	 * Retrieves all headers from the request.
165
	 *
166
	 * @since 4.4.0
167
	 * @access public
168
	 *
169
	 * @return array Map of key to value. Key is always lowercase, as per HTTP specification.
170
	 */
171
	public function get_headers() {
172
		return $this->headers;
173
	}
174
175
	/**
176
	 * Canonicalizes the header name.
177
	 *
178
	 * Ensures that header names are always treated the same regardless of
179
	 * source. Header names are always case insensitive.
180
	 *
181
	 * Note that we treat `-` (dashes) and `_` (underscores) as the same
182
	 * character, as per header parsing rules in both Apache and nginx.
183
	 *
184
	 * @link http://stackoverflow.com/q/18185366
185
	 * @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers
186
	 * @link https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
187
	 *
188
	 * @since 4.4.0
189
	 * @access public
190
	 * @static
191
	 *
192
	 * @param string $key Header name.
193
	 * @return string Canonicalized name.
194
	 */
195
	public static function canonicalize_header_name( $key ) {
196
		$key = strtolower( $key );
197
		$key = str_replace( '-', '_', $key );
198
199
		return $key;
200
	}
201
202
	/**
203
	 * Retrieves the given header from the request.
204
	 *
205
	 * If the header has multiple values, they will be concatenated with a comma
206
	 * as per the HTTP specification. Be aware that some non-compliant headers
207
	 * (notably cookie headers) cannot be joined this way.
208
	 *
209
	 * @since 4.4.0
210
	 * @access public
211
	 *
212
	 * @param string $key Header name, will be canonicalized to lowercase.
213
	 * @return string|null String value if set, null otherwise.
214
	 */
215 View Code Duplication
	public function get_header( $key ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
		$key = $this->canonicalize_header_name( $key );
217
218
		if ( ! isset( $this->headers[ $key ] ) ) {
219
			return null;
220
		}
221
222
		return implode( ',', $this->headers[ $key ] );
223
	}
224
225
	/**
226
	 * Retrieves header values from the request.
227
	 *
228
	 * @since 4.4.0
229
	 * @access public
230
	 *
231
	 * @param string $key Header name, will be canonicalized to lowercase.
232
	 * @return array|null List of string values if set, null otherwise.
233
	 */
234 View Code Duplication
	public function get_header_as_array( $key ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
235
		$key = $this->canonicalize_header_name( $key );
236
237
		if ( ! isset( $this->headers[ $key ] ) ) {
238
			return null;
239
		}
240
241
		return $this->headers[ $key ];
242
	}
243
244
	/**
245
	 * Sets the header on request.
246
	 *
247
	 * @since 4.4.0
248
	 * @access public
249
	 *
250
	 * @param string $key   Header name.
251
	 * @param string $value Header value, or list of values.
252
	 */
253
	public function set_header( $key, $value ) {
254
		$key = $this->canonicalize_header_name( $key );
255
		$value = (array) $value;
256
257
		$this->headers[ $key ] = $value;
258
	}
259
260
	/**
261
	 * Appends a header value for the given header.
262
	 *
263
	 * @since 4.4.0
264
	 * @access public
265
	 *
266
	 * @param string $key   Header name.
267
	 * @param string $value Header value, or list of values.
268
	 */
269
	public function add_header( $key, $value ) {
270
		$key = $this->canonicalize_header_name( $key );
271
		$value = (array) $value;
272
273
		if ( ! isset( $this->headers[ $key ] ) ) {
274
			$this->headers[ $key ] = array();
275
		}
276
277
		$this->headers[ $key ] = array_merge( $this->headers[ $key ], $value );
278
	}
279
280
	/**
281
	 * Removes all values for a header.
282
	 *
283
	 * @since 4.4.0
284
	 * @access public
285
	 *
286
	 * @param string $key Header name.
287
	 */
288
	public function remove_header( $key ) {
289
		unset( $this->headers[ $key ] );
290
	}
291
292
	/**
293
	 * Sets headers on the request.
294
	 *
295
	 * @since 4.4.0
296
	 * @access public
297
	 *
298
	 * @param array $headers  Map of header name to value.
299
	 * @param bool  $override If true, replace the request's headers. Otherwise, merge with existing.
300
	 */
301
	public function set_headers( $headers, $override = true ) {
302
		if ( true === $override ) {
303
			$this->headers = array();
304
		}
305
306
		foreach ( $headers as $key => $value ) {
307
			$this->set_header( $key, $value );
308
		}
309
	}
310
311
	/**
312
	 * Retrieves the content-type of the request.
313
	 *
314
	 * @since 4.4.0
315
	 * @access public
316
	 *
317
	 * @return array Map containing 'value' and 'parameters' keys.
318
	 */
319
	public function get_content_type() {
320
		$value = $this->get_header( 'content-type' );
321
		if ( empty( $value ) ) {
322
			return null;
323
		}
324
325
		$parameters = '';
326
		if ( strpos( $value, ';' ) ) {
327
			list( $value, $parameters ) = explode( ';', $value, 2 );
328
		}
329
330
		$value = strtolower( $value );
331
		if ( strpos( $value, '/' ) === false ) {
332
			return null;
333
		}
334
335
		// Parse type and subtype out.
336
		list( $type, $subtype ) = explode( '/', $value, 2 );
337
338
		$data = compact( 'value', 'type', 'subtype', 'parameters' );
339
		$data = array_map( 'trim', $data );
340
341
		return $data;
342
	}
343
344
	/**
345
	 * Retrieves the parameter priority order.
346
	 *
347
	 * Used when checking parameters in get_param().
348
	 *
349
	 * @since 4.4.0
350
	 * @access protected
351
	 *
352
	 * @return array List of types to check, in order of priority.
353
	 */
354
	protected function get_parameter_order() {
355
		$order = array();
356
		$order[] = 'JSON';
357
358
		$this->parse_json_params();
359
360
		// Ensure we parse the body data.
361
		$body = $this->get_body();
362
		if ( $this->method !== 'POST' && ! empty( $body ) ) {
363
			$this->parse_body_params();
364
		}
365
366
		$accepts_body_data = array( 'POST', 'PUT', 'PATCH' );
367
		if ( in_array( $this->method, $accepts_body_data ) ) {
368
			$order[] = 'POST';
369
		}
370
371
		$order[] = 'GET';
372
		$order[] = 'URL';
373
		$order[] = 'defaults';
374
375
		/**
376
		 * Filters the parameter order.
377
		 *
378
		 * The order affects which parameters are checked when using get_param() and family.
379
		 * This acts similarly to PHP's `request_order` setting.
380
		 *
381
		 * @since 4.4.0
382
		 *
383
		 * @param array           $order {
384
		 *    An array of types to check, in order of priority.
385
		 *
386
		 *    @param string $type The type to check.
387
		 * }
388
		 * @param WP_REST_Request $this The request object.
389
		 */
390
		return apply_filters( 'rest_request_parameter_order', $order, $this );
391
	}
392
393
	/**
394
	 * Retrieves a parameter from the request.
395
	 *
396
	 * @since 4.4.0
397
	 * @access public
398
	 *
399
	 * @param string $key Parameter name.
400
	 * @return mixed|null Value if set, null otherwise.
401
	 */
402
	public function get_param( $key ) {
403
		$order = $this->get_parameter_order();
404
405
		foreach ( $order as $type ) {
406
			// Determine if we have the parameter for this type.
407
			if ( isset( $this->params[ $type ][ $key ] ) ) {
408
				return $this->params[ $type ][ $key ];
409
			}
410
		}
411
412
		return null;
413
	}
414
415
	/**
416
	 * Sets a parameter on the request.
417
	 *
418
	 * @since 4.4.0
419
	 * @access public
420
	 *
421
	 * @param string $key   Parameter name.
422
	 * @param mixed  $value Parameter value.
423
	 */
424
	public function set_param( $key, $value ) {
425
		switch ( $this->method ) {
426
			case 'POST':
427
				$this->params['POST'][ $key ] = $value;
428
				break;
429
430
			default:
431
				$this->params['GET'][ $key ] = $value;
432
				break;
433
		}
434
	}
435
436
	/**
437
	 * Retrieves merged parameters from the request.
438
	 *
439
	 * The equivalent of get_param(), but returns all parameters for the request.
440
	 * Handles merging all the available values into a single array.
441
	 *
442
	 * @since 4.4.0
443
	 * @access public
444
	 *
445
	 * @return array Map of key to value.
446
	 */
447
	public function get_params() {
448
		$order = $this->get_parameter_order();
449
		$order = array_reverse( $order, true );
450
451
		$params = array();
452
		foreach ( $order as $type ) {
453
			$params = array_merge( $params, (array) $this->params[ $type ] );
454
		}
455
456
		return $params;
457
	}
458
459
	/**
460
	 * Retrieves parameters from the route itself.
461
	 *
462
	 * These are parsed from the URL using the regex.
463
	 *
464
	 * @since 4.4.0
465
	 * @access public
466
	 *
467
	 * @return array Parameter map of key to value.
468
	 */
469
	public function get_url_params() {
470
		return $this->params['URL'];
471
	}
472
473
	/**
474
	 * Sets parameters from the route.
475
	 *
476
	 * Typically, this is set after parsing the URL.
477
	 *
478
	 * @since 4.4.0
479
	 * @access public
480
	 *
481
	 * @param array $params Parameter map of key to value.
482
	 */
483
	public function set_url_params( $params ) {
484
		$this->params['URL'] = $params;
485
	}
486
487
	/**
488
	 * Retrieves parameters from the query string.
489
	 *
490
	 * These are the parameters you'd typically find in `$_GET`.
491
	 *
492
	 * @since 4.4.0
493
	 * @access public
494
	 *
495
	 * @return array Parameter map of key to value
496
	 */
497
	public function get_query_params() {
498
		return $this->params['GET'];
499
	}
500
501
	/**
502
	 * Sets parameters from the query string.
503
	 *
504
	 * Typically, this is set from `$_GET`.
505
	 *
506
	 * @since 4.4.0
507
	 * @access public
508
	 *
509
	 * @param array $params Parameter map of key to value.
510
	 */
511
	public function set_query_params( $params ) {
512
		$this->params['GET'] = $params;
513
	}
514
515
	/**
516
	 * Retrieves parameters from the body.
517
	 *
518
	 * These are the parameters you'd typically find in `$_POST`.
519
	 *
520
	 * @since 4.4.0
521
	 * @access public
522
	 *
523
	 * @return array Parameter map of key to value.
524
	 */
525
	public function get_body_params() {
526
		return $this->params['POST'];
527
	}
528
529
	/**
530
	 * Sets parameters from the body.
531
	 *
532
	 * Typically, this is set from `$_POST`.
533
	 *
534
	 * @since 4.4.0
535
	 * @access public
536
	 *
537
	 * @param array $params Parameter map of key to value.
538
	 */
539
	public function set_body_params( $params ) {
540
		$this->params['POST'] = $params;
541
	}
542
543
	/**
544
	 * Retrieves multipart file parameters from the body.
545
	 *
546
	 * These are the parameters you'd typically find in `$_FILES`.
547
	 *
548
	 * @since 4.4.0
549
	 * @access public
550
	 *
551
	 * @return array Parameter map of key to value
552
	 */
553
	public function get_file_params() {
554
		return $this->params['FILES'];
555
	}
556
557
	/**
558
	 * Sets multipart file parameters from the body.
559
	 *
560
	 * Typically, this is set from `$_FILES`.
561
	 *
562
	 * @since 4.4.0
563
	 * @access public
564
	 *
565
	 * @param array $params Parameter map of key to value.
566
	 */
567
	public function set_file_params( $params ) {
568
		$this->params['FILES'] = $params;
569
	}
570
571
	/**
572
	 * Retrieves the default parameters.
573
	 *
574
	 * These are the parameters set in the route registration.
575
	 *
576
	 * @since 4.4.0
577
	 * @access public
578
	 *
579
	 * @return array Parameter map of key to value
580
	 */
581
	public function get_default_params() {
582
		return $this->params['defaults'];
583
	}
584
585
	/**
586
	 * Sets default parameters.
587
	 *
588
	 * These are the parameters set in the route registration.
589
	 *
590
	 * @since 4.4.0
591
	 * @access public
592
	 *
593
	 * @param array $params Parameter map of key to value.
594
	 */
595
	public function set_default_params( $params ) {
596
		$this->params['defaults'] = $params;
597
	}
598
599
	/**
600
	 * Retrieves the request body content.
601
	 *
602
	 * @since 4.4.0
603
	 * @access public
604
	 *
605
	 * @return string Binary data from the request body.
606
	 */
607
	public function get_body() {
608
		return $this->body;
609
	}
610
611
	/**
612
	 * Sets body content.
613
	 *
614
	 * @since 4.4.0
615
	 * @access public
616
	 *
617
	 * @param string $data Binary data from the request body.
618
	 */
619
	public function set_body( $data ) {
620
		$this->body = $data;
621
622
		// Enable lazy parsing.
623
		$this->parsed_json = false;
624
		$this->parsed_body = false;
625
		$this->params['JSON'] = null;
626
	}
627
628
	/**
629
	 * Retrieves the parameters from a JSON-formatted body.
630
	 *
631
	 * @since 4.4.0
632
	 * @access public
633
	 *
634
	 * @return array Parameter map of key to value.
635
	 */
636
	public function get_json_params() {
637
		// Ensure the parameters have been parsed out.
638
		$this->parse_json_params();
639
640
		return $this->params['JSON'];
641
	}
642
643
	/**
644
	 * Parses the JSON parameters.
645
	 *
646
	 * Avoids parsing the JSON data until we need to access it.
647
	 *
648
	 * @since 4.4.0
649
	 * @access protected
650
	 */
651
	protected function parse_json_params() {
652
		if ( $this->parsed_json ) {
653
			return;
654
		}
655
656
		$this->parsed_json = true;
657
658
		// Check that we actually got JSON.
659
		$content_type = $this->get_content_type();
660
661
		if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
662
			return;
663
		}
664
665
		$params = json_decode( $this->get_body(), true );
666
667
		/*
668
		 * Check for a parsing error.
669
		 *
670
		 * Note that due to WP's JSON compatibility functions, json_last_error
671
		 * might not be defined: https://core.trac.wordpress.org/ticket/27799
672
		 */
673
		if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
674
			return;
675
		}
676
677
		$this->params['JSON'] = $params;
678
	}
679
680
	/**
681
	 * Parses the request body parameters.
682
	 *
683
	 * Parses out URL-encoded bodies for request methods that aren't supported
684
	 * natively by PHP. In PHP 5.x, only POST has these parsed automatically.
685
	 *
686
	 * @since 4.4.0
687
	 * @access protected
688
	 */
689
	protected function parse_body_params() {
690
		if ( $this->parsed_body ) {
691
			return;
692
		}
693
694
		$this->parsed_body = true;
695
696
		/*
697
		 * Check that we got URL-encoded. Treat a missing content-type as
698
		 * URL-encoded for maximum compatibility.
699
		 */
700
		$content_type = $this->get_content_type();
701
702
		if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
703
			return;
704
		}
705
706
		parse_str( $this->get_body(), $params );
707
708
		/*
709
		 * Amazingly, parse_str follows magic quote rules. Sigh.
710
		 *
711
		 * NOTE: Do not refactor to use `wp_unslash`.
712
		 */
713
		if ( get_magic_quotes_gpc() ) {
714
			$params = stripslashes_deep( $params );
715
		}
716
717
		/*
718
		 * Add to the POST parameters stored internally. If a user has already
719
		 * set these manually (via `set_body_params`), don't override them.
720
		 */
721
		$this->params['POST'] = array_merge( $params, $this->params['POST'] );
722
	}
723
724
	/**
725
	 * Retrieves the route that matched the request.
726
	 *
727
	 * @since 4.4.0
728
	 * @access public
729
	 *
730
	 * @return string Route matching regex.
731
	 */
732
	public function get_route() {
733
		return $this->route;
734
	}
735
736
	/**
737
	 * Sets the route that matched the request.
738
	 *
739
	 * @since 4.4.0
740
	 * @access public
741
	 *
742
	 * @param string $route Route matching regex.
743
	 */
744
	public function set_route( $route ) {
745
		$this->route = $route;
746
	}
747
748
	/**
749
	 * Retrieves the attributes for the request.
750
	 *
751
	 * These are the options for the route that was matched.
752
	 *
753
	 * @since 4.4.0
754
	 * @access public
755
	 *
756
	 * @return array Attributes for the request.
757
	 */
758
	public function get_attributes() {
759
		return $this->attributes;
760
	}
761
762
	/**
763
	 * Sets the attributes for the request.
764
	 *
765
	 * @since 4.4.0
766
	 * @access public
767
	 *
768
	 * @param array $attributes Attributes for the request.
769
	 */
770
	public function set_attributes( $attributes ) {
771
		$this->attributes = $attributes;
772
	}
773
774
	/**
775
	 * Sanitizes (where possible) the params on the request.
776
	 *
777
	 * This is primarily based off the sanitize_callback param on each registered
778
	 * argument.
779
	 *
780
	 * @since 4.4.0
781
	 * @access public
782
	 *
783
	 * @return true|null True if there are no parameters to sanitize, null otherwise.
784
	 */
785
	public function sanitize_params() {
786
787
		$attributes = $this->get_attributes();
788
789
		// No arguments set, skip sanitizing.
790
		if ( empty( $attributes['args'] ) ) {
791
			return true;
792
		}
793
794
		$order = $this->get_parameter_order();
795
796
		foreach ( $order as $type ) {
797
			if ( empty( $this->params[ $type ] ) ) {
798
				continue;
799
			}
800
			foreach ( $this->params[ $type ] as $key => $value ) {
801
				// Check if this param has a sanitize_callback added.
802
				if ( isset( $attributes['args'][ $key ] ) && ! empty( $attributes['args'][ $key ]['sanitize_callback'] ) ) {
803
					$this->params[ $type ][ $key ] = call_user_func( $attributes['args'][ $key ]['sanitize_callback'], $value, $this, $key );
804
				}
805
			}
806
		}
807
		return null;
808
	}
809
810
	/**
811
	 * Checks whether this request is valid according to its attributes.
812
	 *
813
	 * @since 4.4.0
814
	 * @access public
815
	 *
816
	 * @return bool|WP_Error True if there are no parameters to validate or if all pass validation,
817
	 *                       WP_Error if required parameters are missing.
818
	 */
819
	public function has_valid_params() {
820
821
		$attributes = $this->get_attributes();
822
		$required = array();
823
824
		// No arguments set, skip validation.
825
		if ( empty( $attributes['args'] ) ) {
826
			return true;
827
		}
828
829
		foreach ( $attributes['args'] as $key => $arg ) {
830
831
			$param = $this->get_param( $key );
832
			if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) {
833
				$required[] = $key;
834
			}
835
		}
836
837 View Code Duplication
		if ( ! empty( $required ) ) {
838
			return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) );
839
		}
840
841
		/*
842
		 * Check the validation callbacks for each registered arg.
843
		 *
844
		 * This is done after required checking as required checking is cheaper.
845
		 */
846
		$invalid_params = array();
847
848
		foreach ( $attributes['args'] as $key => $arg ) {
849
850
			$param = $this->get_param( $key );
851
852
			if ( null !== $param && ! empty( $arg['validate_callback'] ) ) {
853
				$valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
854
855
				if ( false === $valid_check ) {
856
					$invalid_params[ $key ] = __( 'Invalid parameter.' );
857
				}
858
859
				if ( is_wp_error( $valid_check ) ) {
860
					$invalid_params[ $key ] = $valid_check->get_error_message();
861
				}
862
			}
863
		}
864
865 View Code Duplication
		if ( $invalid_params ) {
866
			return new WP_Error( 'rest_invalid_param', sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), array( 'status' => 400, 'params' => $invalid_params ) );
867
		}
868
869
		return true;
870
871
	}
872
873
	/**
874
	 * Checks if a parameter is set.
875
	 *
876
	 * @since 4.4.0
877
	 * @access public
878
	 *
879
	 * @param string $offset Parameter name.
880
	 * @return bool Whether the parameter is set.
881
	 */
882
	public function offsetExists( $offset ) {
883
		$order = $this->get_parameter_order();
884
885
		foreach ( $order as $type ) {
886
			if ( isset( $this->params[ $type ][ $offset ] ) ) {
887
				return true;
888
			}
889
		}
890
891
		return false;
892
	}
893
894
	/**
895
	 * Retrieves a parameter from the request.
896
	 *
897
	 * @since 4.4.0
898
	 * @access public
899
	 *
900
	 * @param string $offset Parameter name.
901
	 * @return mixed|null Value if set, null otherwise.
902
	 */
903
	public function offsetGet( $offset ) {
904
		return $this->get_param( $offset );
905
	}
906
907
	/**
908
	 * Sets a parameter on the request.
909
	 *
910
	 * @since 4.4.0
911
	 * @access public
912
	 *
913
	 * @param string $offset Parameter name.
914
	 * @param mixed  $value  Parameter value.
915
	 */
916
	public function offsetSet( $offset, $value ) {
917
		$this->set_param( $offset, $value );
918
	}
919
920
	/**
921
	 * Removes a parameter from the request.
922
	 *
923
	 * @since 4.4.0
924
	 * @access public
925
	 *
926
	 * @param string $offset Parameter name.
927
	 */
928
	public function offsetUnset( $offset ) {
929
		$order = $this->get_parameter_order();
930
931
		// Remove the offset from every group.
932
		foreach ( $order as $type ) {
933
			unset( $this->params[ $type ][ $offset ] );
934
		}
935
	}
936
937
	/**
938
	 * Retrieves a WP_REST_Request object from a full URL.
939
	 *
940
	 * @static
941
	 * @since 4.5.0
942
	 * @access public
943
	 *
944
	 * @param string $url URL with protocol, domain, path and query args.
945
	 * @return WP_REST_Request|false WP_REST_Request object on success, false on failure.
946
	 */
947
	public static function from_url( $url ) {
948
		$bits = parse_url( $url );
949
		$query_params = array();
950
951
		if ( ! empty( $bits['query'] ) ) {
952
			wp_parse_str( $bits['query'], $query_params );
953
		}
954
955
		$api_root = rest_url();
956
		if ( get_option( 'permalink_structure' ) && 0 === strpos( $url, $api_root ) ) {
957
			// Pretty permalinks on, and URL is under the API root
958
			$api_url_part = substr( $url, strlen( untrailingslashit( $api_root ) ) );
959
			$route = parse_url( $api_url_part, PHP_URL_PATH );
960
		} elseif ( ! empty( $query_params['rest_route'] ) ) {
961
			// ?rest_route=... set directly
962
			$route = $query_params['rest_route'];
963
			unset( $query_params['rest_route'] );
964
		}
965
966
		$request = false;
967
		if ( ! empty( $route ) ) {
968
			$request = new WP_REST_Request( 'GET', $route );
969
			$request->set_query_params( $query_params );
970
		}
971
972
		/**
973
		 * Filters the request generated from a URL.
974
		 *
975
		 * @since 4.5.0
976
		 *
977
		 * @param WP_REST_Request|false $request Generated request object, or false if URL
978
		 *                                       could not be parsed.
979
		 * @param string                $url     URL the request was generated from.
980
		 */
981
		return apply_filters( 'rest_request_from_url', $request, $url );
982
	}
983
}
984