wp/wp-includes/rest-api/class-wp-rest-server.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    10 /**
    10 /**
    11  * Core class used to implement the WordPress REST API server.
    11  * Core class used to implement the WordPress REST API server.
    12  *
    12  *
    13  * @since 4.4.0
    13  * @since 4.4.0
    14  */
    14  */
       
    15 #[AllowDynamicProperties]
    15 class WP_REST_Server {
    16 class WP_REST_Server {
    16 
    17 
    17 	/**
    18 	/**
    18 	 * Alias for GET transport method.
    19 	 * Alias for GET transport method.
    19 	 *
    20 	 *
    83 	 *
    84 	 *
    84 	 * @since 5.4.0
    85 	 * @since 5.4.0
    85 	 * @var array
    86 	 * @var array
    86 	 */
    87 	 */
    87 	protected $embed_cache = array();
    88 	protected $embed_cache = array();
       
    89 
       
    90 	/**
       
    91 	 * Stores request objects that are currently being handled.
       
    92 	 *
       
    93 	 * @since 6.5.0
       
    94 	 * @var array
       
    95 	 */
       
    96 	protected $dispatching_requests = array();
    88 
    97 
    89 	/**
    98 	/**
    90 	 * Instantiates the REST server.
    99 	 * Instantiates the REST server.
    91 	 *
   100 	 *
    92 	 * @since 4.4.0
   101 	 * @since 4.4.0
   155 	/**
   164 	/**
   156 	 * Checks the authentication headers if supplied.
   165 	 * Checks the authentication headers if supplied.
   157 	 *
   166 	 *
   158 	 * @since 4.4.0
   167 	 * @since 4.4.0
   159 	 *
   168 	 *
   160 	 * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful
   169 	 * @return WP_Error|null|true WP_Error indicates unsuccessful login, null indicates successful
   161 	 *                       or no authentication provided
   170 	 *                            or no authentication provided
   162 	 */
   171 	 */
   163 	public function check_authentication() {
   172 	public function check_authentication() {
   164 		/**
   173 		/**
   165 		 * Filters REST API authentication errors.
   174 		 * Filters REST API authentication errors.
   166 		 *
   175 		 *
   190 
   199 
   191 	/**
   200 	/**
   192 	 * Converts an error to a response object.
   201 	 * Converts an error to a response object.
   193 	 *
   202 	 *
   194 	 * This iterates over all error codes and messages to change it into a flat
   203 	 * This iterates over all error codes and messages to change it into a flat
   195 	 * array. This enables simpler client behaviour, as it is represented as a
   204 	 * array. This enables simpler client behavior, as it is represented as a
   196 	 * list in JSON rather than an object/map.
   205 	 * list in JSON rather than an object/map.
   197 	 *
   206 	 *
   198 	 * @since 4.4.0
   207 	 * @since 4.4.0
   199 	 * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}.
   208 	 * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}.
   200 	 *
   209 	 *
   226 		}
   235 		}
   227 
   236 
   228 		$error = compact( 'code', 'message' );
   237 		$error = compact( 'code', 'message' );
   229 
   238 
   230 		return wp_json_encode( $error );
   239 		return wp_json_encode( $error );
       
   240 	}
       
   241 
       
   242 	/**
       
   243 	 * Gets the encoding options passed to {@see wp_json_encode}.
       
   244 	 *
       
   245 	 * @since 6.1.0
       
   246 	 *
       
   247 	 * @param \WP_REST_Request $request The current request object.
       
   248 	 *
       
   249 	 * @return int The JSON encode options.
       
   250 	 */
       
   251 	protected function get_json_encode_options( WP_REST_Request $request ) {
       
   252 		$options = 0;
       
   253 
       
   254 		if ( $request->has_param( '_pretty' ) ) {
       
   255 			$options |= JSON_PRETTY_PRINT;
       
   256 		}
       
   257 
       
   258 		/**
       
   259 		 * Filters the JSON encoding options used to send the REST API response.
       
   260 		 *
       
   261 		 * @since 6.1.0
       
   262 		 *
       
   263 		 * @param int $options             JSON encoding options {@see json_encode()}.
       
   264 		 * @param WP_REST_Request $request Current request object.
       
   265 		 */
       
   266 		return apply_filters( 'rest_json_encode_options', $options, $request );
   231 	}
   267 	}
   232 
   268 
   233 	/**
   269 	/**
   234 	 * Handles serving a REST API request.
   270 	 * Handles serving a REST API request.
   235 	 *
   271 	 *
   282 		$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
   318 		$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
   283 		$this->send_header( 'X-Robots-Tag', 'noindex' );
   319 		$this->send_header( 'X-Robots-Tag', 'noindex' );
   284 
   320 
   285 		$api_root = get_rest_url();
   321 		$api_root = get_rest_url();
   286 		if ( ! empty( $api_root ) ) {
   322 		if ( ! empty( $api_root ) ) {
   287 			$this->send_header( 'Link', '<' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"' );
   323 			$this->send_header( 'Link', '<' . sanitize_url( $api_root ) . '>; rel="https://api.w.org/"' );
   288 		}
   324 		}
   289 
   325 
   290 		/*
   326 		/*
   291 		 * Mitigate possible JSONP Flash attacks.
   327 		 * Mitigate possible JSONP Flash attacks.
   292 		 *
   328 		 *
   293 		 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   329 		 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   294 		 */
   330 		 */
   295 		$this->send_header( 'X-Content-Type-Options', 'nosniff' );
   331 		$this->send_header( 'X-Content-Type-Options', 'nosniff' );
   296 		$expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );
       
   297 
       
   298 		/**
       
   299 		 * Filters the list of response headers that are exposed to REST API CORS requests.
       
   300 		 *
       
   301 		 * @since 5.5.0
       
   302 		 *
       
   303 		 * @param string[] $expose_headers The list of response headers to expose.
       
   304 		 */
       
   305 		$expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers );
       
   306 
       
   307 		$this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) );
       
   308 
       
   309 		$allow_headers = array(
       
   310 			'Authorization',
       
   311 			'X-WP-Nonce',
       
   312 			'Content-Disposition',
       
   313 			'Content-MD5',
       
   314 			'Content-Type',
       
   315 		);
       
   316 
       
   317 		/**
       
   318 		 * Filters the list of request headers that are allowed for REST API CORS requests.
       
   319 		 *
       
   320 		 * The allowed headers are passed to the browser to specify which
       
   321 		 * headers can be passed to the REST API. By default, we allow the
       
   322 		 * Content-* headers needed to upload files to the media endpoints.
       
   323 		 * As well as the Authorization and Nonce headers for allowing authentication.
       
   324 		 *
       
   325 		 * @since 5.5.0
       
   326 		 *
       
   327 		 * @param string[] $allow_headers The list of request headers to allow.
       
   328 		 */
       
   329 		$allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers );
       
   330 
       
   331 		$this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) );
       
   332 
       
   333 		/**
       
   334 		 * Filters whether to send nocache headers on a REST API request.
       
   335 		 *
       
   336 		 * @since 4.4.0
       
   337 		 *
       
   338 		 * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
       
   339 		 */
       
   340 		$send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
       
   341 		if ( $send_no_cache_headers ) {
       
   342 			foreach ( wp_get_nocache_headers() as $header => $header_value ) {
       
   343 				if ( empty( $header_value ) ) {
       
   344 					$this->remove_header( $header );
       
   345 				} else {
       
   346 					$this->send_header( $header, $header_value );
       
   347 				}
       
   348 			}
       
   349 		}
       
   350 
   332 
   351 		/**
   333 		/**
   352 		 * Filters whether the REST API is enabled.
   334 		 * Filters whether the REST API is enabled.
   353 		 *
   335 		 *
   354 		 * @since 4.4.0
   336 		 * @since 4.4.0
   400 		/*
   382 		/*
   401 		 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
   383 		 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
   402 		 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
   384 		 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
   403 		 * header.
   385 		 * header.
   404 		 */
   386 		 */
       
   387 		$method_overridden = false;
   405 		if ( isset( $_GET['_method'] ) ) {
   388 		if ( isset( $_GET['_method'] ) ) {
   406 			$request->set_method( $_GET['_method'] );
   389 			$request->set_method( $_GET['_method'] );
   407 		} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
   390 		} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
   408 			$request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
   391 			$request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
   409 		}
   392 			$method_overridden = true;
       
   393 		}
       
   394 
       
   395 		$expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );
       
   396 
       
   397 		/**
       
   398 		 * Filters the list of response headers that are exposed to REST API CORS requests.
       
   399 		 *
       
   400 		 * @since 5.5.0
       
   401 		 * @since 6.3.0 The `$request` parameter was added.
       
   402 		 *
       
   403 		 * @param string[]        $expose_headers The list of response headers to expose.
       
   404 		 * @param WP_REST_Request $request        The request in context.
       
   405 		 */
       
   406 		$expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers, $request );
       
   407 
       
   408 		$this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) );
       
   409 
       
   410 		$allow_headers = array(
       
   411 			'Authorization',
       
   412 			'X-WP-Nonce',
       
   413 			'Content-Disposition',
       
   414 			'Content-MD5',
       
   415 			'Content-Type',
       
   416 		);
       
   417 
       
   418 		/**
       
   419 		 * Filters the list of request headers that are allowed for REST API CORS requests.
       
   420 		 *
       
   421 		 * The allowed headers are passed to the browser to specify which
       
   422 		 * headers can be passed to the REST API. By default, we allow the
       
   423 		 * Content-* headers needed to upload files to the media endpoints.
       
   424 		 * As well as the Authorization and Nonce headers for allowing authentication.
       
   425 		 *
       
   426 		 * @since 5.5.0
       
   427 		 * @since 6.3.0 The `$request` parameter was added.
       
   428 		 *
       
   429 		 * @param string[]        $allow_headers The list of request headers to allow.
       
   430 		 * @param WP_REST_Request $request       The request in context.
       
   431 		 */
       
   432 		$allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers, $request );
       
   433 
       
   434 		$this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) );
   410 
   435 
   411 		$result = $this->check_authentication();
   436 		$result = $this->check_authentication();
   412 
   437 
   413 		if ( ! is_wp_error( $result ) ) {
   438 		if ( ! is_wp_error( $result ) ) {
   414 			$result = $this->dispatch( $request );
   439 			$result = $this->dispatch( $request );
   446 		$headers = $result->get_headers();
   471 		$headers = $result->get_headers();
   447 		$this->send_headers( $headers );
   472 		$this->send_headers( $headers );
   448 
   473 
   449 		$code = $result->get_status();
   474 		$code = $result->get_status();
   450 		$this->set_status( $code );
   475 		$this->set_status( $code );
       
   476 
       
   477 		/**
       
   478 		 * Filters whether to send no-cache headers on a REST API request.
       
   479 		 *
       
   480 		 * @since 4.4.0
       
   481 		 * @since 6.3.2 Moved the block to catch the filter added on rest_cookie_check_errors() from wp-includes/rest-api.php.
       
   482 		 *
       
   483 		 * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
       
   484 		 */
       
   485 		$send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
       
   486 
       
   487 		/*
       
   488 		 * Send no-cache headers if $send_no_cache_headers is true,
       
   489 		 * OR if the HTTP_X_HTTP_METHOD_OVERRIDE is used but resulted a 4xx response code.
       
   490 		 */
       
   491 		if ( $send_no_cache_headers || ( true === $method_overridden && str_starts_with( $code, '4' ) ) ) {
       
   492 			foreach ( wp_get_nocache_headers() as $header => $header_value ) {
       
   493 				if ( empty( $header_value ) ) {
       
   494 					$this->remove_header( $header );
       
   495 				} else {
       
   496 					$this->send_header( $header, $header_value );
       
   497 				}
       
   498 			}
       
   499 		}
   451 
   500 
   452 		/**
   501 		/**
   453 		 * Filters whether the REST API request has already been served.
   502 		 * Filters whether the REST API request has already been served.
   454 		 *
   503 		 *
   455 		 * Allow sending the request manually - by returning true, the API result
   504 		 * Allow sending the request manually - by returning true, the API result
   491 			// The 204 response shouldn't have a body.
   540 			// The 204 response shouldn't have a body.
   492 			if ( 204 === $code || null === $result ) {
   541 			if ( 204 === $code || null === $result ) {
   493 				return null;
   542 				return null;
   494 			}
   543 			}
   495 
   544 
   496 			$result = wp_json_encode( $result );
   545 			$result = wp_json_encode( $result, $this->get_json_encode_options( $request ) );
   497 
   546 
   498 			$json_error_message = $this->get_json_last_error();
   547 			$json_error_message = $this->get_json_last_error();
   499 
   548 
   500 			if ( $json_error_message ) {
   549 			if ( $json_error_message ) {
   501 				$this->set_status( 500 );
   550 				$this->set_status( 500 );
   504 					$json_error_message,
   553 					$json_error_message,
   505 					array( 'status' => 500 )
   554 					array( 'status' => 500 )
   506 				);
   555 				);
   507 
   556 
   508 				$result = $this->error_to_response( $json_error_obj );
   557 				$result = $this->error_to_response( $json_error_obj );
   509 				$result = wp_json_encode( $result->data );
   558 				$result = wp_json_encode( $result->data, $this->get_json_encode_options( $request ) );
   510 			}
   559 			}
   511 
   560 
   512 			if ( $jsonp_callback ) {
   561 			if ( $jsonp_callback ) {
   513 				// Prepend '/**/' to mitigate possible JSONP Flash attacks.
   562 				// Prepend '/**/' to mitigate possible JSONP Flash attacks.
   514 				// https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   563 				// https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   523 
   572 
   524 	/**
   573 	/**
   525 	 * Converts a response to data to send.
   574 	 * Converts a response to data to send.
   526 	 *
   575 	 *
   527 	 * @since 4.4.0
   576 	 * @since 4.4.0
   528 	 * @since 5.4.0 The $embed parameter can now contain a list of link relations to include.
   577 	 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include.
   529 	 *
   578 	 *
   530 	 * @param WP_REST_Response $response Response object.
   579 	 * @param WP_REST_Response $response Response object.
   531 	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
   580 	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
   532 	 * @return array {
   581 	 * @return array {
   533 	 *     Data with sub-requests embedded.
   582 	 *     Data with sub-requests embedded.
   618 		foreach ( $links as $rel => $items ) {
   667 		foreach ( $links as $rel => $items ) {
   619 
   668 
   620 			// Convert $rel URIs to their compact versions if they exist.
   669 			// Convert $rel URIs to their compact versions if they exist.
   621 			foreach ( $curies as $curie ) {
   670 			foreach ( $curies as $curie ) {
   622 				$href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) );
   671 				$href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) );
   623 				if ( strpos( $rel, $href_prefix ) !== 0 ) {
   672 				if ( ! str_starts_with( $rel, $href_prefix ) ) {
   624 					continue;
   673 					continue;
   625 				}
   674 				}
   626 
   675 
   627 				// Relation now changes from '$uri' to '$curie:$relation'.
   676 				// Relation now changes from '$uri' to '$curie:$relation'.
   628 				$rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) );
   677 				$rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) );
   647 
   696 
   648 	/**
   697 	/**
   649 	 * Embeds the links from the data into the request.
   698 	 * Embeds the links from the data into the request.
   650 	 *
   699 	 *
   651 	 * @since 4.4.0
   700 	 * @since 4.4.0
   652 	 * @since 5.4.0 The $embed parameter can now contain a list of link relations to include.
   701 	 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include.
   653 	 *
   702 	 *
   654 	 * @param array         $data  Data from the request.
   703 	 * @param array         $data  Data from the request.
   655 	 * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations.
   704 	 * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations.
   656 	 * @return array {
   705 	 * @return array {
   657 	 *     Data with sub-requests embedded.
   706 	 *     Data with sub-requests embedded.
   666 		}
   715 		}
   667 
   716 
   668 		$embedded = array();
   717 		$embedded = array();
   669 
   718 
   670 		foreach ( $data['_links'] as $rel => $links ) {
   719 		foreach ( $data['_links'] as $rel => $links ) {
   671 			// If a list of relations was specified, and the link relation
   720 			/*
   672 			// is not in the list of allowed relations, don't process the link.
   721 			 * If a list of relations was specified, and the link relation
       
   722 			 * is not in the list of allowed relations, don't process the link.
       
   723 			 */
   673 			if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) {
   724 			if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) {
   674 				continue;
   725 				continue;
   675 			}
   726 			}
   676 
   727 
   677 			$embeds = array();
   728 			$embeds = array();
   695 					// Embedded resources get passed context=embed.
   746 					// Embedded resources get passed context=embed.
   696 					if ( empty( $request['context'] ) ) {
   747 					if ( empty( $request['context'] ) ) {
   697 						$request['context'] = 'embed';
   748 						$request['context'] = 'embed';
   698 					}
   749 					}
   699 
   750 
       
   751 					if ( empty( $request['per_page'] ) ) {
       
   752 						$matched = $this->match_request_to_handler( $request );
       
   753 						if ( ! is_wp_error( $matched ) && isset( $matched[1]['args']['per_page']['maximum'] ) ) {
       
   754 							$request['per_page'] = (int) $matched[1]['args']['per_page']['maximum'];
       
   755 						}
       
   756 					}
       
   757 
   700 					$response = $this->dispatch( $request );
   758 					$response = $this->dispatch( $request );
   701 
   759 
   702 					/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
   760 					/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
   703 					$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
   761 					$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
   704 
   762 
   729 	 * The enveloping technique is used to work around browser/client
   787 	 * The enveloping technique is used to work around browser/client
   730 	 * compatibility issues. Essentially, it converts the full HTTP response to
   788 	 * compatibility issues. Essentially, it converts the full HTTP response to
   731 	 * data instead.
   789 	 * data instead.
   732 	 *
   790 	 *
   733 	 * @since 4.4.0
   791 	 * @since 4.4.0
   734 	 * @since 6.0.0 The $embed parameter can now contain a list of link relations to include
   792 	 * @since 6.0.0 The `$embed` parameter can now contain a list of link relations to include.
   735 	 *
   793 	 *
   736 	 * @param WP_REST_Response $response Response object.
   794 	 * @param WP_REST_Response $response Response object.
   737 	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
   795 	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
   738 	 * @return WP_REST_Response New response with wrapped data
   796 	 * @return WP_REST_Response New response with wrapped data
   739 	 */
   797 	 */
   767 	/**
   825 	/**
   768 	 * Registers a route to the server.
   826 	 * Registers a route to the server.
   769 	 *
   827 	 *
   770 	 * @since 4.4.0
   828 	 * @since 4.4.0
   771 	 *
   829 	 *
   772 	 * @param string $namespace  Namespace.
   830 	 * @param string $route_namespace Namespace.
   773 	 * @param string $route      The REST route.
   831 	 * @param string $route           The REST route.
   774 	 * @param array  $route_args Route arguments.
   832 	 * @param array  $route_args      Route arguments.
   775 	 * @param bool   $override   Optional. Whether the route should be overridden if it already exists.
   833 	 * @param bool   $override        Optional. Whether the route should be overridden if it already exists.
   776 	 *                           Default false.
   834 	 *                                Default false.
   777 	 */
   835 	 */
   778 	public function register_route( $namespace, $route, $route_args, $override = false ) {
   836 	public function register_route( $route_namespace, $route, $route_args, $override = false ) {
   779 		if ( ! isset( $this->namespaces[ $namespace ] ) ) {
   837 		if ( ! isset( $this->namespaces[ $route_namespace ] ) ) {
   780 			$this->namespaces[ $namespace ] = array();
   838 			$this->namespaces[ $route_namespace ] = array();
   781 
   839 
   782 			$this->register_route(
   840 			$this->register_route(
   783 				$namespace,
   841 				$route_namespace,
   784 				'/' . $namespace,
   842 				'/' . $route_namespace,
   785 				array(
   843 				array(
   786 					array(
   844 					array(
   787 						'methods'  => self::READABLE,
   845 						'methods'  => self::READABLE,
   788 						'callback' => array( $this, 'get_namespace_index' ),
   846 						'callback' => array( $this, 'get_namespace_index' ),
   789 						'args'     => array(
   847 						'args'     => array(
   790 							'namespace' => array(
   848 							'namespace' => array(
   791 								'default' => $namespace,
   849 								'default' => $route_namespace,
   792 							),
   850 							),
   793 							'context'   => array(
   851 							'context'   => array(
   794 								'default' => 'view',
   852 								'default' => 'view',
   795 							),
   853 							),
   796 						),
   854 						),
   798 				)
   856 				)
   799 			);
   857 			);
   800 		}
   858 		}
   801 
   859 
   802 		// Associative to avoid double-registration.
   860 		// Associative to avoid double-registration.
   803 		$this->namespaces[ $namespace ][ $route ] = true;
   861 		$this->namespaces[ $route_namespace ][ $route ] = true;
   804 		$route_args['namespace']                  = $namespace;
   862 
       
   863 		$route_args['namespace'] = $route_namespace;
   805 
   864 
   806 		if ( $override || empty( $this->endpoints[ $route ] ) ) {
   865 		if ( $override || empty( $this->endpoints[ $route ] ) ) {
   807 			$this->endpoints[ $route ] = $route_args;
   866 			$this->endpoints[ $route ] = $route_args;
   808 		} else {
   867 		} else {
   809 			$this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
   868 			$this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
   824 	 *
   883 	 *
   825 	 * Note that the path regexes (array keys) must have @ escaped, as this is
   884 	 * Note that the path regexes (array keys) must have @ escaped, as this is
   826 	 * used as the delimiter with preg_match()
   885 	 * used as the delimiter with preg_match()
   827 	 *
   886 	 *
   828 	 * @since 4.4.0
   887 	 * @since 4.4.0
   829 	 * @since 5.4.0 Add $namespace parameter.
   888 	 * @since 5.4.0 Added `$route_namespace` parameter.
   830 	 *
   889 	 *
   831 	 * @param string $namespace Optionally, only return routes in the given namespace.
   890 	 * @param string $route_namespace Optionally, only return routes in the given namespace.
   832 	 * @return array `'/path/regex' => array( $callback, $bitmask )` or
   891 	 * @return array `'/path/regex' => array( $callback, $bitmask )` or
   833 	 *               `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
   892 	 *               `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
   834 	 */
   893 	 */
   835 	public function get_routes( $namespace = '' ) {
   894 	public function get_routes( $route_namespace = '' ) {
   836 		$endpoints = $this->endpoints;
   895 		$endpoints = $this->endpoints;
   837 
   896 
   838 		if ( $namespace ) {
   897 		if ( $route_namespace ) {
   839 			$endpoints = wp_list_filter( $endpoints, array( 'namespace' => $namespace ) );
   898 			$endpoints = wp_list_filter( $endpoints, array( 'namespace' => $route_namespace ) );
   840 		}
   899 		}
   841 
   900 
   842 		/**
   901 		/**
   843 		 * Filters the array of available REST API endpoints.
   902 		 * Filters the array of available REST API endpoints.
   844 		 *
   903 		 *
   937 	 *
   996 	 *
   938 	 * @param WP_REST_Request $request Request to attempt dispatching.
   997 	 * @param WP_REST_Request $request Request to attempt dispatching.
   939 	 * @return WP_REST_Response Response returned by the callback.
   998 	 * @return WP_REST_Response Response returned by the callback.
   940 	 */
   999 	 */
   941 	public function dispatch( $request ) {
  1000 	public function dispatch( $request ) {
       
  1001 		$this->dispatching_requests[] = $request;
       
  1002 
   942 		/**
  1003 		/**
   943 		 * Filters the pre-calculated result of a REST API dispatch request.
  1004 		 * Filters the pre-calculated result of a REST API dispatch request.
   944 		 *
  1005 		 *
   945 		 * Allow hijacking the request before dispatching by returning a non-empty. The returned value
  1006 		 * Allow hijacking the request before dispatching by returning a non-empty. The returned value
   946 		 * will be used to serve the request instead.
  1007 		 * will be used to serve the request instead.
   953 		 * @param WP_REST_Request $request Request used to generate the response.
  1014 		 * @param WP_REST_Request $request Request used to generate the response.
   954 		 */
  1015 		 */
   955 		$result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
  1016 		$result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
   956 
  1017 
   957 		if ( ! empty( $result ) ) {
  1018 		if ( ! empty( $result ) ) {
       
  1019 
       
  1020 			// Normalize to either WP_Error or WP_REST_Response...
       
  1021 			$result = rest_ensure_response( $result );
       
  1022 
       
  1023 			// ...then convert WP_Error across.
       
  1024 			if ( is_wp_error( $result ) ) {
       
  1025 				$result = $this->error_to_response( $result );
       
  1026 			}
       
  1027 
       
  1028 			array_pop( $this->dispatching_requests );
   958 			return $result;
  1029 			return $result;
   959 		}
  1030 		}
   960 
  1031 
   961 		$error   = null;
  1032 		$error   = null;
   962 		$matched = $this->match_request_to_handler( $request );
  1033 		$matched = $this->match_request_to_handler( $request );
   963 
  1034 
   964 		if ( is_wp_error( $matched ) ) {
  1035 		if ( is_wp_error( $matched ) ) {
   965 			return $this->error_to_response( $matched );
  1036 			$response = $this->error_to_response( $matched );
       
  1037 			array_pop( $this->dispatching_requests );
       
  1038 			return $response;
   966 		}
  1039 		}
   967 
  1040 
   968 		list( $route, $handler ) = $matched;
  1041 		list( $route, $handler ) = $matched;
   969 
  1042 
   970 		if ( ! is_callable( $handler['callback'] ) ) {
  1043 		if ( ! is_callable( $handler['callback'] ) ) {
   985 					$error = $check_sanitized;
  1058 					$error = $check_sanitized;
   986 				}
  1059 				}
   987 			}
  1060 			}
   988 		}
  1061 		}
   989 
  1062 
   990 		return $this->respond_to_request( $request, $route, $handler, $error );
  1063 		$response = $this->respond_to_request( $request, $route, $handler, $error );
       
  1064 		array_pop( $this->dispatching_requests );
       
  1065 		return $response;
       
  1066 	}
       
  1067 
       
  1068 	/**
       
  1069 	 * Returns whether the REST server is currently dispatching / responding to a request.
       
  1070 	 *
       
  1071 	 * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
       
  1072 	 *
       
  1073 	 * @since 6.5.0
       
  1074 	 *
       
  1075 	 * @return bool Whether the REST server is currently handling a request.
       
  1076 	 */
       
  1077 	public function is_dispatching() {
       
  1078 		return (bool) $this->dispatching_requests;
   991 	}
  1079 	}
   992 
  1080 
   993 	/**
  1081 	/**
   994 	 * Matches a request object to its handler.
  1082 	 * Matches a request object to its handler.
   995 	 *
  1083 	 *
  1004 		$path   = $request->get_route();
  1092 		$path   = $request->get_route();
  1005 
  1093 
  1006 		$with_namespace = array();
  1094 		$with_namespace = array();
  1007 
  1095 
  1008 		foreach ( $this->get_namespaces() as $namespace ) {
  1096 		foreach ( $this->get_namespaces() as $namespace ) {
  1009 			if ( 0 === strpos( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) {
  1097 			if ( str_starts_with( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) {
  1010 				$with_namespace[] = $this->get_routes( $namespace );
  1098 				$with_namespace[] = $this->get_routes( $namespace );
  1011 			}
  1099 			}
  1012 		}
  1100 		}
  1013 
  1101 
  1014 		if ( $with_namespace ) {
  1102 		if ( $with_namespace ) {
  1032 				}
  1120 				}
  1033 			}
  1121 			}
  1034 
  1122 
  1035 			foreach ( $handlers as $handler ) {
  1123 			foreach ( $handlers as $handler ) {
  1036 				$callback = $handler['callback'];
  1124 				$callback = $handler['callback'];
  1037 				$response = null;
       
  1038 
  1125 
  1039 				// Fallback to GET method if no HEAD method is registered.
  1126 				// Fallback to GET method if no HEAD method is registered.
  1040 				$checked_method = $method;
  1127 				$checked_method = $method;
  1041 				if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
  1128 				if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
  1042 					$checked_method = 'GET';
  1129 					$checked_method = 'GET';
  1226 			'authentication'  => array(),
  1313 			'authentication'  => array(),
  1227 			'routes'          => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
  1314 			'routes'          => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
  1228 		);
  1315 		);
  1229 
  1316 
  1230 		$response = new WP_REST_Response( $available );
  1317 		$response = new WP_REST_Response( $available );
  1231 		$response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' );
  1318 
  1232 		$this->add_active_theme_link_to_index( $response );
  1319 		$fields = isset( $request['_fields'] ) ? $request['_fields'] : '';
  1233 		$this->add_site_logo_to_index( $response );
  1320 		$fields = wp_parse_list( $fields );
  1234 		$this->add_site_icon_to_index( $response );
  1321 		if ( empty( $fields ) ) {
       
  1322 			$fields[] = '_links';
       
  1323 		}
       
  1324 
       
  1325 		if ( $request->has_param( '_embed' ) ) {
       
  1326 			$fields[] = '_embedded';
       
  1327 		}
       
  1328 
       
  1329 		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
       
  1330 			$response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' );
       
  1331 			$this->add_active_theme_link_to_index( $response );
       
  1332 			$this->add_site_logo_to_index( $response );
       
  1333 			$this->add_site_icon_to_index( $response );
       
  1334 		} else {
       
  1335 			if ( rest_is_field_included( 'site_logo', $fields ) ) {
       
  1336 				$this->add_site_logo_to_index( $response );
       
  1337 			}
       
  1338 			if ( rest_is_field_included( 'site_icon', $fields ) || rest_is_field_included( 'site_icon_url', $fields ) ) {
       
  1339 				$this->add_site_icon_to_index( $response );
       
  1340 			}
       
  1341 		}
  1235 
  1342 
  1236 		/**
  1343 		/**
  1237 		 * Filters the REST API root index data.
  1344 		 * Filters the REST API root index data.
  1238 		 *
  1345 		 *
  1239 		 * This contains the data describing the API. This includes information
  1346 		 * This contains the data describing the API. This includes information
  1306 	 */
  1413 	 */
  1307 	protected function add_site_icon_to_index( WP_REST_Response $response ) {
  1414 	protected function add_site_icon_to_index( WP_REST_Response $response ) {
  1308 		$site_icon_id = get_option( 'site_icon', 0 );
  1415 		$site_icon_id = get_option( 'site_icon', 0 );
  1309 
  1416 
  1310 		$this->add_image_to_index( $response, $site_icon_id, 'site_icon' );
  1417 		$this->add_image_to_index( $response, $site_icon_id, 'site_icon' );
       
  1418 
       
  1419 		$response->data['site_icon_url'] = get_site_icon_url();
  1311 	}
  1420 	}
  1312 
  1421 
  1313 	/**
  1422 	/**
  1314 	 * Exposes an image through the WordPress REST API.
  1423 	 * Exposes an image through the WordPress REST API.
  1315 	 * This is used for fetching this information when user has no rights
  1424 	 * This is used for fetching this information when user has no rights
  1481 
  1590 
  1482 			if ( isset( $callback['args'] ) ) {
  1591 			if ( isset( $callback['args'] ) ) {
  1483 				$endpoint_data['args'] = array();
  1592 				$endpoint_data['args'] = array();
  1484 
  1593 
  1485 				foreach ( $callback['args'] as $key => $opts ) {
  1594 				foreach ( $callback['args'] as $key => $opts ) {
       
  1595 					if ( is_string( $opts ) ) {
       
  1596 						$opts = array( $opts => 0 );
       
  1597 					} elseif ( ! is_array( $opts ) ) {
       
  1598 						$opts = array();
       
  1599 					}
  1486 					$arg_data             = array_intersect_key( $opts, $allowed_schema_keywords );
  1600 					$arg_data             = array_intersect_key( $opts, $allowed_schema_keywords );
  1487 					$arg_data['required'] = ! empty( $opts['required'] );
  1601 					$arg_data['required'] = ! empty( $opts['required'] );
  1488 
  1602 
  1489 					$endpoint_data['args'][ $key ] = $arg_data;
  1603 					$endpoint_data['args'][ $key ] = $arg_data;
  1490 				}
  1604 				}
  1491 			}
  1605 			}
  1492 
  1606 
  1493 			$data['endpoints'][] = $endpoint_data;
  1607 			$data['endpoints'][] = $endpoint_data;
  1494 
  1608 
  1495 			// For non-variable routes, generate links.
  1609 			// For non-variable routes, generate links.
  1496 			if ( strpos( $route, '{' ) === false ) {
  1610 			if ( ! str_contains( $route, '{' ) ) {
  1497 				$data['_links'] = array(
  1611 				$data['_links'] = array(
  1498 					'self' => array(
  1612 					'self' => array(
  1499 						array(
  1613 						array(
  1500 							'href' => rest_url( $route ),
  1614 							'href' => rest_url( $route ),
  1501 						),
  1615 						),
  1780 			'CONTENT_MD5'    => true,
  1894 			'CONTENT_MD5'    => true,
  1781 			'CONTENT_TYPE'   => true,
  1895 			'CONTENT_TYPE'   => true,
  1782 		);
  1896 		);
  1783 
  1897 
  1784 		foreach ( $server as $key => $value ) {
  1898 		foreach ( $server as $key => $value ) {
  1785 			if ( strpos( $key, 'HTTP_' ) === 0 ) {
  1899 			if ( str_starts_with( $key, 'HTTP_' ) ) {
  1786 				$headers[ substr( $key, 5 ) ] = $value;
  1900 				$headers[ substr( $key, 5 ) ] = $value;
  1787 			} elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) {
  1901 			} elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) {
  1788 				/*
  1902 				/*
  1789 				 * In some server configurations, the authorization header is passed in this alternate location.
  1903 				 * In some server configurations, the authorization header is passed in this alternate location.
  1790 				 * Since it would not be passed in in both places we do not check for both headers and resolve.
  1904 				 * Since it would not be passed in in both places we do not check for both headers and resolve.