File manager - Edit - /home/kamsoftco/public_html/wpadmin.srindustries.net.in/wp-content/plugins/wp-graphql/src/Router.php
Back
<?php namespace WPGraphQL; use GraphQL\Error\FormattedError; use WP_User; /** * Class Router * This sets up the /graphql endpoint * * @package WPGraphQL * @since 0.0.1 * * phpcs:disable -- PHPStan annotation. * @phpstan-import-type SerializableError from \GraphQL\Executor\ExecutionResult * @phpstan-import-type SerializableResult from \GraphQL\Executor\ExecutionResult * * @phpstan-type WPGraphQLResult = SerializableResult|(\GraphQL\Executor\ExecutionResult|array<int,\GraphQL\Executor\ExecutionResult>) * phpcs:enable */ class Router { /** * Sets the route to use as the endpoint * * @var string $route */ public static $route = 'graphql'; /** * Holds the Global Post for later resetting * * @var string */ protected static $global_post = ''; /** * Set the default status code to 200. * * @var int */ public static $http_status_code = 200; /** * @var ?\WPGraphQL\Request */ protected static $request; /** * Initialize the WPGraphQL Router * * @return void * @throws \Exception */ public function init() { self::$route = graphql_get_endpoint(); /** * Create the rewrite rule for the route * * @since 0.0.1 */ add_action( 'init', [ $this, 'add_rewrite_rule' ], 10 ); /** * Add the query var for the route * * @since 0.0.1 */ add_filter( 'query_vars', [ $this, 'add_query_var' ], 1, 1 ); /** * Redirects the route to the graphql processor * * @since 0.0.1 */ add_action( 'parse_request', [ $this, 'resolve_http_request' ], 10 ); /** * Adds support for application passwords */ add_filter( 'application_password_is_api_request', [ $this, 'is_api_request' ] ); } /** * Returns the GraphQL Request being executed */ public static function get_request(): ?Request { return self::$request; } /** * Adds rewrite rule for the route endpoint * * @return void * @since 0.0.1 * @uses add_rewrite_rule() */ public static function add_rewrite_rule() { add_rewrite_rule( self::$route . '/?$', 'index.php?' . self::$route . '=true', 'top' ); } /** * Determines whether the request is an API request to play nice with * application passwords and potential other WordPress core functionality * for APIs * * @param bool $is_api_request Whether the request is an API request * * @return bool */ public function is_api_request( $is_api_request ) { return true === is_graphql_http_request() ? true : $is_api_request; } /** * Adds the query_var for the route * * @param string[] $query_vars The array of whitelisted query variables. * * @return string[] * @since 0.0.1 */ public static function add_query_var( $query_vars ) { $query_vars[] = self::$route; return $query_vars; } /** * Returns true when the current request is a GraphQL request coming from the HTTP * * NOTE: This will only indicate whether the GraphQL Request is an HTTP request. Many features * need to affect _all_ GraphQL requests, including internal requests using the `graphql()` * function, so be careful how you use this to check your conditions. * * @return bool */ public static function is_graphql_http_request() { /** * Filter whether the request is a GraphQL HTTP Request. Default is null, as the majority * of WordPress requests are NOT GraphQL requests (at least today that's true 😆). * * If this filter returns anything other than null, the function will return now and skip the * default checks. * * @param ?bool $is_graphql_http_request Whether the request is a GraphQL HTTP Request. Default false. */ $pre_is_graphql_http_request = apply_filters( 'graphql_pre_is_graphql_http_request', null ); /** * If the filter has been applied, return now before executing default checks */ if ( null !== $pre_is_graphql_http_request ) { return (bool) $pre_is_graphql_http_request; } // Default is false $is_graphql_http_request = false; // Support wp-graphiql style request to /index.php?graphql. if ( isset( $_GET[ self::$route ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $is_graphql_http_request = true; } elseif ( isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) ) { // Check the server to determine if the GraphQL endpoint is being requested $host = wp_unslash( $_SERVER['HTTP_HOST'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $uri = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( ! is_string( $host ) ) { return false; } if ( ! is_string( $uri ) ) { return false; } $parsed_site_url = wp_parse_url( site_url( self::$route ), PHP_URL_PATH ); $graphql_url = ! empty( $parsed_site_url ) ? wp_unslash( $parsed_site_url ) : self::$route; $parsed_request_url = wp_parse_url( $uri, PHP_URL_PATH ); $request_url = ! empty( $parsed_request_url ) ? wp_unslash( $parsed_request_url ) : ''; // Determine if the route is indeed a graphql request $is_graphql_http_request = str_replace( '/', '', $request_url ) === str_replace( '/', '', $graphql_url ); } /** * Filter whether the request is a GraphQL HTTP Request. Default is false, as the majority * of WordPress requests are NOT GraphQL requests (at least today that's true 😆). * * The request has to "prove" that it is indeed an HTTP request via HTTP for * this to be true. * * Different servers _might_ have different needs to determine whether a request * is a GraphQL request. * * @param bool $is_graphql_http_request Whether the request is a GraphQL HTTP Request. Default false. */ return apply_filters( 'graphql_is_graphql_http_request', $is_graphql_http_request ); } /** * This resolves the http request and ensures that WordPress can respond with the appropriate * JSON response instead of responding with a template from the standard WordPress Template * Loading process * * @return void * @throws \Exception Throws exception. * @throws \Throwable Throws exception. * @since 0.0.1 */ public static function resolve_http_request() { /** * Access the $wp_query object */ global $wp_query; /** * Ensure we're on the registered route for graphql route */ if ( ! self::is_graphql_http_request() || is_graphql_request() ) { return; } /** * Set is_home to false */ $wp_query->is_home = false; /** * Whether it's a GraphQL HTTP Request * * @since 0.0.5 */ if ( ! defined( 'GRAPHQL_HTTP_REQUEST' ) ) { define( 'GRAPHQL_HTTP_REQUEST', true ); } /** * Process the GraphQL query Request */ self::process_http_request(); } /** * Sends an HTTP header. * * @param string $key Header key. * @param string $value Header value. * * @return void * @since 0.0.5 */ public static function send_header( $key, $value ) { /** * Sanitize as per RFC2616 (Section 4.2): * * Any LWS that occurs between field-content MAY be replaced with a * single SP before interpreting the field value or forwarding the * message downstream. */ $value = preg_replace( '/\s+/', ' ', $value ); header( apply_filters( 'graphql_send_header', sprintf( '%s: %s', $key, $value ), $key, $value ) ); } /** * Sends an HTTP status code. * * @param int|null $status_code The status code to send. * * @return void */ protected static function set_status( ?int $status_code = null ) { $status_code = null === $status_code ? self::$http_status_code : $status_code; // validate that the status code is a valid http status code if ( ! is_numeric( $status_code ) || $status_code < 100 || $status_code > 599 ) { $status_code = 500; } status_header( $status_code ); } /** * Returns an array of headers to send with the HTTP response * * @return array<string,string> */ protected static function get_response_headers() { /** * Filtered list of access control headers. * * @param string[] $access_control_headers Array of headers to allow. */ $access_control_allow_headers = apply_filters( 'graphql_access_control_allow_headers', [ 'Authorization', 'Content-Type', ] ); // For cache url header, use the domain without protocol. Path for when it's multisite. // Remove the starting http://, https://, :// from the full hostname/path. $host_and_path = preg_replace( '#^.*?://#', '', graphql_get_endpoint_url() ); $headers = [ 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Headers' => implode( ', ', $access_control_allow_headers ), 'Access-Control-Max-Age' => '600', // cache the result of preflight requests (600 is the upper limit for Chromium). 'Content-Type' => 'application/json ; charset=' . get_option( 'blog_charset' ), 'X-Robots-Tag' => 'noindex', 'X-Content-Type-Options' => 'nosniff', 'X-GraphQL-URL' => (string) $host_and_path, ]; // If the Query Analyzer was instantiated // Get the headers determined from its Analysis if ( self::get_request() instanceof Request && self::get_request()->get_query_analyzer()->is_enabled_for_query() ) { $headers = self::get_request()->get_query_analyzer()->get_headers( $headers ); } if ( true === \WPGraphQL::debug() ) { $headers['X-hacker'] = __( 'If you\'re reading this, you should visit github.com/wp-graphql/wp-graphql and contribute!', 'wp-graphql' ); } /** * Send nocache headers on authenticated requests. * * Prefer the current request's viewer when available (after execution) so we * send no-cache for the request that was actually authenticated, regardless * of global user timing. Fall back to is_user_logged_in() for paths that * run before the Request exists (e.g. 403 auth error, OPTIONS). * * @see https://github.com/wp-graphql/wp-graphql/issues/3340 * * @param bool $rest_send_nocache_headers Whether to send no-cache headers. * * @since 0.0.5 */ $request = self::get_request(); $is_authenticated = $request instanceof Request && $request->app_context->viewer instanceof WP_User && $request->app_context->viewer->exists(); if ( ! $is_authenticated ) { $is_authenticated = is_user_logged_in(); } $send_no_cache_headers = apply_filters( 'graphql_send_nocache_headers', $is_authenticated ); if ( $send_no_cache_headers ) { foreach ( wp_get_nocache_headers() as $no_cache_header_key => $no_cache_header_value ) { $headers[ $no_cache_header_key ] = $no_cache_header_value; } } /** * Filter the $headers to send * * @param array<string,string> $headers The headers to send */ $headers = apply_filters( 'graphql_response_headers_to_send', $headers ); return is_array( $headers ) ? $headers : []; } /** * Set the response headers * * @return void * @since 0.0.1 */ public static function set_headers() { if ( false === headers_sent() ) { /** * Set the HTTP response status */ self::set_status( self::$http_status_code ); /** * Get the response headers */ $headers = self::get_response_headers(); /** * If there are headers, set them for the response */ if ( ! empty( $headers ) && is_array( $headers ) ) { foreach ( $headers as $key => $value ) { self::send_header( $key, $value ); } } /** * Fire an action when the headers are set * * @param array<string,string> $headers The headers sent in the response */ do_action( 'graphql_response_set_headers', $headers ); } } /** * Retrieves the raw request entity (body). * * @since 0.0.5 * * @global string php://input Raw post data. * * @return string Raw request data. */ public static function get_raw_data() { $input = file_get_contents( 'php://input' ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsRemoteFile return ! empty( $input ) ? $input : ''; } /** * This processes the graphql requests that come into the /graphql endpoint via an HTTP request * * @return void * @throws \Throwable Throws Exception. * @global WP_User $current_user The currently authenticated user. * @since 0.0.1 */ public static function process_http_request() { global $current_user; if ( $current_user instanceof WP_User && ! $current_user->exists() ) { /* * If there is no current user authenticated via other means, clear * the cached lack of user, so that an authenticate check can set it * properly. * * This is done because for authentications such as Application * Passwords, we don't want it to be accepted unless the current HTTP * request is a GraphQL API request, which can't always be identified early * enough in evaluation. * * See serve_request in wp-includes/rest-api/class-wp-rest-server.php. */ $current_user = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride } /** * Validate authentication BEFORE any GraphQL hooks fire. * * This is critical for security - we must validate/downgrade authentication * before plugins can hook in and potentially expose sensitive information * based on the (not-yet-validated) authenticated user. * * For cookie-authenticated requests: * - No nonce: User is downgraded to guest * - Invalid nonce: Returns error response immediately * - Valid nonce: Proceeds normally * * @since 2.6.0 */ $auth_error = self::validate_http_request_authentication(); if ( is_wp_error( $auth_error ) ) { /** * Filter the HTTP status code returned for authentication errors. * * By default, invalid nonce errors return 403 Forbidden. Some clients * may expect 200 with a GraphQL error response instead. * * @since 2.6.0 * * @param int $status_code The HTTP status code. Default 403. * @param \WP_Error $auth_error The authentication error. */ self::$http_status_code = apply_filters( 'graphql_authentication_error_status_code', 403, $auth_error ); self::set_headers(); wp_send_json( [ 'errors' => [ [ 'message' => $auth_error->get_error_message(), ], ], ] ); } /** * This action can be hooked to to enable various debug tools, * such as enableValidation from the GraphQL Config. * * @since 0.0.4 */ do_action( 'graphql_process_http_request' ); /** * Respond to pre-flight requests. * * Bail before Request() execution begins. * * @see: https://apollographql.slack.com/archives/C10HTKHPC/p1507649812000123 * @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests */ if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) { self::$http_status_code = 200; self::set_headers(); exit; } $response = []; $query = ''; $operation_name = ''; $variables = []; self::$request = new Request(); try { // Start output buffering to prevent any unwanted output from breaking the JSON response // This addresses issues like plugins calling wp_print_inline_script_tag() during wp_enqueue_scripts ob_start(); $response = self::$request->execute_http(); // Discard any captured output that could break the JSON response ob_end_clean(); // Get the operation params from the request. $params = self::$request->get_params(); $query = isset( $params->query ) ? $params->query : ''; $operation_name = isset( $params->operation ) ? $params->operation : ''; $variables = isset( $params->variables ) ? $params->variables : null; } catch ( \Throwable $error ) { // Make sure to clean up the output buffer even if there's an exception if ( ob_get_level() > 0 ) { ob_end_clean(); } /** * If there are errors, set the status to 500 * and format the captured errors to be output properly * * @since 0.0.4 */ self::$http_status_code = 500; /** * Filter thrown GraphQL errors * * @var SerializableResult $response * * @param SerializableError[] $errors The errors array to be sent in the response. * @param \Throwable $error Thrown error object. * @param \WPGraphQL\Request $request WPGraphQL Request object. */ $response['errors'] = apply_filters( 'graphql_http_request_response_errors', [ FormattedError::createFromException( $error, self::$request->get_debug_flag() ) ], $error, self::$request ); } // Previously there was a small distinction between the response and the result, but // now that we are delegating to Request, just send the response for both. if ( false === headers_sent() ) { self::prepare_headers( $response, $response, $query, $operation_name, $variables ); } /** * Run an action after the HTTP Response is ready to be sent back. This might be a good place for tools * to hook in to track metrics, such as how long the process took from `graphql_process_http_request` * to here, etc. * * @param WPGraphQLResult $response The GraphQL response * @param WPGraphQLResult $result Deprecated. Same as $response. * @param string $operation_name The name of the operation * @param string $query The request that GraphQL executed * @param ?array<string,mixed> $variables Variables to passed to your GraphQL query * @param int|string $status_code The status code for the response * * @since 0.0.5 */ do_action( 'graphql_process_http_request_response', $response, $response, $operation_name, $query, $variables, self::$http_status_code ); /** * Send the response */ wp_send_json( $response ); } /** * Prepare headers for response * * @param mixed[]|\GraphQL\Executor\ExecutionResult $response The response of the GraphQL Request. * @param mixed[]|\GraphQL\Executor\ExecutionResult $_deprecated Deprecated. * @param string $query The GraphQL query. * @param string $operation_name The operation name of the GraphQL Request. * @param ?array<string,mixed> $variables The variables applied to the GraphQL Request. * @param ?\WP_User $user The current user object. * * @return void */ protected static function prepare_headers( $response, $_deprecated, string $query, string $operation_name, $variables, $user = null ) { /** * Filter the $status_code before setting the headers * * @param int $status_code The status code to apply to the headers * @param mixed[]|\GraphQL\Executor\ExecutionResult $response The response of the GraphQL Request * @param mixed[]|\GraphQL\Executor\ExecutionResult $_deprecated Use $response instead. * @param string $query The GraphQL query * @param string $operation_name The operation name of the GraphQL Request * @param ?array<string,mixed> $variables The variables applied to the GraphQL Request * @param ?\WP_User $user The current user object */ self::$http_status_code = apply_filters( 'graphql_response_status_code', self::$http_status_code, $_deprecated, $response, $query, $operation_name, $variables, $user ); /** * Set the response headers */ self::set_headers(); } /** * @deprecated 0.4.1 Use Router::is_graphql_http_request instead. This now resolves to it * @todo remove in v3.0 * @codeCoverageIgnore * * @return bool */ public static function is_graphql_request() { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s is the class name */ esc_html__( 'This method is deprecated and will be removed in the next major version of WPGraphQL. Use %s instead.', 'wp-graphql' ), esc_html( self::class . '::is_graphql_http_request()' ) ), '0.4.1' ); return self::is_graphql_http_request(); } /** * Validates HTTP request authentication BEFORE any GraphQL processing begins. * * This method provides CSRF protection for cookie-authenticated requests. * It runs before `graphql_process_http_request` and other hooks fire, ensuring * plugins cannot inadvertently expose sensitive data based on a user identity * that hasn't been validated yet. * * For cookie-authenticated requests: * - No nonce provided: User is downgraded to guest (CSRF protection) * - Invalid nonce: Returns WP_Error (caller should return error response) * - Valid nonce: Returns null (authentication preserved) * * @since 2.6.0 * * @return \WP_Error|null WP_Error if invalid nonce, null otherwise. */ public static function validate_http_request_authentication(): ?\WP_Error { /** * Only validate for logged-in users. * Guest users don't need validation - they're already unauthenticated. */ if ( ! is_user_logged_in() ) { return null; } /** * Check if an Authorization header is present. * If so, this is likely a non-cookie auth method (JWT, Application Passwords, etc.) * which are inherently CSRF-safe and don't need nonce validation. */ $has_auth_header = ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) || ! empty( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ); if ( $has_auth_header ) { return null; } /** * No Authorization header = cookie-based authentication. * Check for nonce in request param or header. */ $nonce = null; if ( isset( $_REQUEST['_wpnonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $nonce = $_REQUEST['_wpnonce']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) { $nonce = $_SERVER['HTTP_X_WP_NONCE']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } /** * Treat "falsy" nonce values as "no nonce provided". * This handles JavaScript serialization edge cases where null/undefined * get converted to strings. */ $empty_nonce_values = [ '', 'null', 'undefined', 'false', '0' ]; if ( in_array( $nonce, $empty_nonce_values, true ) ) { $nonce = null; } /** * Filter whether to require a nonce for cookie-based authentication. * * By default, WPGraphQL requires a nonce (X-WP-Nonce header or _wpnonce parameter) * for cookie-authenticated requests to prevent CSRF attacks. * * @since 2.5.4 * * @param bool $require_nonce Whether to require a nonce for cookie auth. Default true. * @param null $request The Request instance (null in Router context). */ $require_nonce = apply_filters( 'graphql_cookie_auth_require_nonce', true, null ); /** * If nonce is not required, allow the authenticated request. */ if ( ! $require_nonce ) { return null; } /** * No nonce provided - downgrade to guest (unless plugin prevents it). */ if ( null === $nonce ) { /** * Allow plugins to prevent the downgrade via the graphql_authentication_errors filter. * * @param bool|null $authentication_errors Null to allow default behavior, false to preserve auth. * @param \WPGraphQL\Request|null $request The Request instance (null in Router context). */ $filtered = apply_filters( 'graphql_authentication_errors', null, self::get_request() ); // If a plugin explicitly returned false (no errors), preserve authentication if ( false === $filtered ) { return null; } // Downgrade to guest wp_set_current_user( 0 ); return null; } /** * Nonce provided - validate it. * Support both 'wp_graphql' and 'wp_rest' for backward compatibility. */ $nonce_valid = wp_verify_nonce( $nonce, 'wp_graphql' ) || wp_verify_nonce( $nonce, 'wp_rest' ); if ( ! $nonce_valid ) { return new \WP_Error( 'graphql_cookie_invalid_nonce', __( 'Cookie nonce is invalid', 'wp-graphql' ), [ 'status' => 403 ] ); } return null; } }
| ver. 1.4 |
Github
|
.
| PHP 8.2.30 | Generation time: 0.17 |
proxy
|
phpinfo
|
Settings