Skip to content

Instantly share code, notes, and snippets.

@westonruter
Last active July 18, 2020 06:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save westonruter/48e546e4636bec5fafd8a29e0fc7a001 to your computer and use it in GitHub Desktop.
Save westonruter/48e546e4636bec5fafd8a29e0fc7a001 to your computer and use it in GitHub Desktop.
<?php
/**
* AMP Debug Validation Request plugin initialization file.
*
* @package AMP_Debug_Validation_Request
* @author Weston Ruter, Google
* @license GPL-2.0-or-later
* @copyright 2019 Google Inc.
*
* @wordpress-plugin
* Plugin Name: AMP Debug Validation Request
* Plugin URI: https://gist.github.com/westonruter/48e546e4636bec5fafd8a29e0fc7a001
* Description: Debug validation request failures.
* Version: 0.3.0
* Author: Weston Ruter, Google
* Author URI: https://weston.ruter.net/
* License: GNU General Public License v2 (or later)
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
* Gist Plugin URI: https://gist.github.com/westonruter/48e546e4636bec5fafd8a29e0fc7a001
*/
namespace AMP_Debug_Validation_Request;
use AMP_Validation_Manager;
use WP_Admin_Bar;
use WP_Error;
const QUERY_VAR = 'amp_debug_validate_request';
add_action(
'parse_query',
function () {
if ( ! isset( $_GET[ QUERY_VAR ] ) || ! class_exists( 'AMP_Validation_Manager' ) ) {
return;
}
if ( ! AMP_Validation_Manager::has_cap() ) {
wp_die( 'Please log in before doing this.' );
}
if ( ! wp_verify_nonce( wp_unslash( $_GET[ QUERY_VAR ] ), QUERY_VAR ) ) {
wp_die( 'Unable to verify nonce.' );
}
$current_url = amp_get_current_url();
$current_url = remove_query_arg( QUERY_VAR, $current_url );
$results = validate_url( $current_url );
$data = [
'success' => ! is_wp_error( $results ),
'results' => $results,
];
header( 'Content-Type: application/json' );
echo wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
exit;
}
);
add_action(
'admin_bar_menu',
function ( WP_Admin_Bar $admin_bar ) {
if ( ! $admin_bar->get_node( 'amp' ) || ! AMP_Validation_Manager::has_cap() || ! class_exists( 'AMP_Validation_Manager' ) ) {
return;
}
$admin_bar->add_node(
[
'id' => 'amp-debug-validation-response-directly',
'title' => 'Debug AMP validation response directly',
'href' => add_query_arg( AMP_Validation_Manager::VALIDATE_QUERY_VAR, AMP_Validation_Manager::get_amp_validate_nonce() ),
'parent' => 'amp',
'meta' => [
'target' => '_blank',
],
]
);
$admin_bar->add_node(
[
'id' => 'amp-debug-validation-response-with-loopback-request',
'title' => 'Debug AMP validation response w/ loopback request',
'href' => add_query_arg( QUERY_VAR, wp_create_nonce( QUERY_VAR ) ),
'parent' => 'amp',
'meta' => [
'target' => '_blank',
],
]
);
},
1000
);
// Work around issue where amp_validate is included in url key of response.
add_action(
'wp',
function () {
if ( ! class_exists( 'AMP_Validation_Manager' ) ) {
return;
}
$_SERVER['REQUEST_URI'] = remove_query_arg( AMP_Validation_Manager::VALIDATE_QUERY_VAR, $_SERVER['REQUEST_URI'] );
}
);
/**
* Validate URL.
*
* Patched version of `AMP_Validation_Manager::validate_url()`.
*
* @param string $url URL.
* @return array|WP_Error
*/
function validate_url( $url ) {
$added_query_vars = [
AMP_Validation_Manager::VALIDATE_QUERY_VAR => AMP_Validation_Manager::get_amp_validate_nonce(),
AMP_Validation_Manager::CACHE_BUST_QUERY_VAR => wp_rand(),
];
$validation_url = add_query_arg( $added_query_vars, $url );
$r = null;
/** This filter is documented in wp-includes/class-http.php */
$allowed_redirects = apply_filters( 'http_request_redirection_count', 5 );
for ( $redirect_count = 0; $redirect_count < $allowed_redirects; $redirect_count++ ) {
$r = wp_remote_get(
$validation_url,
[
'cookies' => wp_unslash( $_COOKIE ), // Pass along cookies so private pages and drafts can be accessed.
'timeout' => 15, // Increase from default of 5 to give extra time for the plugin to identify the sources for any given validation errors.
'sslverify' => false,
'redirection' => 0, // Because we're in a loop for redirection.
'headers' => [
'Cache-Control' => 'no-cache',
],
]
);
// If the response is not a redirect, then break since $r is all we need.
$response_code = wp_remote_retrieve_response_code( $r );
$location_header = wp_remote_retrieve_header( $r, 'Location' );
$is_redirect = (
$response_code
&&
$response_code > 300 && $response_code < 400
&&
$location_header
);
if ( ! $is_redirect ) {
break;
}
// Ensure absolute URL.
if ( '/' === substr( $location_header, 0, 1 ) ) {
$location_header = preg_replace( '#(^https?://[^/]+)/.*#', '$1', home_url( '/' ) ) . $location_header;
}
// Block redirecting to a different host.
$location_header = wp_validate_redirect( $location_header );
if ( ! $location_header ) {
break;
}
$validation_url = add_query_arg( $added_query_vars, $location_header );
}
if ( is_wp_error( $r ) ) {
return $r;
}
$response = trim( wp_remote_retrieve_body( $r ) );
if ( wp_remote_retrieve_response_code( $r ) >= 400 ) {
$data = json_decode( $response, true );
return new WP_Error(
is_array( $data ) && isset( $data['code'] ) ? $data['code'] : wp_remote_retrieve_response_code( $r ),
is_array( $data ) && isset( $data['message'] ) ? $data['message'] : wp_remote_retrieve_response_message( $r )
);
}
if ( wp_remote_retrieve_response_code( $r ) >= 300 ) {
return new WP_Error(
'http_request_failed',
__( 'Too many redirects.', 'amp' )
);
}
$url = remove_query_arg(
array_keys( $added_query_vars ),
$validation_url
);
// Strip byte order mark (BOM).
while ( "\xEF\xBB\xBF" === substr( $response, 0, 3 ) ) {
$response = substr( $response, 3 );
}
// Strip any leading whitespace.
$response = ltrim( $response );
// Strip HTML comments that may have been injected at the end of the response (e.g. by a caching plugin).
$response = preg_replace( '/<!--.*?-->\s*$/s', '', $response );
$data = [
'first_kb_response' => substr( $response, 0, 1024 ),
];
if ( '' === $response ) {
return new WP_Error( 'white_screen_of_death', '', $data );
}
if ( '{' !== substr( $response, 0, 1 ) ) {
return new WP_Error( 'response_not_json', '', $data );
}
$validation = json_decode( $response, true );
if ( json_last_error() || ! isset( $validation['results'] ) || ! is_array( $validation['results'] ) ) {
return new WP_Error( 'malformed_json_validation_errors', '', $data );
}
return array_merge(
$validation,
compact( 'url' )
);
}
@westonruter
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment