| @@ -6,27 +6,35 @@ | ||
| 6 | 6 | | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| | |
| 7 | 7 | | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | | |
| 8 | 8 | | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| | |
| 9 | 9 | | | |
| 10 | - | (c) Jerome Bruandet ~ https://code-profiler.com/ | | |
| 10 | + | (c) Jerome Bruandet ~ https://nintechnet.com/codeprofiler/ | | |
| 11 | 11 | +=====================================================================+ |
| 12 | 12 | */ |
| 13 | 13 | |
| 14 | -if (! defined( 'ABSPATH' ) ) { die( 'Forbidden' ); } | |
| 14 | +if (! defined('ABSPATH') ) { | |
| 15 | + die('Forbidden'); | |
| 16 | +} | |
| 15 | 17 | |
| 16 | -// ===================================================================== | |
| 18 | +// ===================================================================== 2023-11-17 | |
| 17 | 19 | // Start the profiler. |
| 18 | 20 | |
| 19 | -add_action( 'wp_ajax_codeprofiler_start_profiler', 'codeprofiler_start_profiler' ); | |
| 21 | +add_action('wp_ajax_codeprofiler_start_profiler', 'codeprofiler_start_profiler'); | |
| 20 | 22 | |
| 21 | 23 | function codeprofiler_start_profiler() { |
| 22 | 24 | |
| 23 | 25 | $response = ['status' => 'error']; |
| 24 | 26 | |
| 25 | - $cp_options = get_option( 'code-profiler' ); | |
| 27 | + code_profiler_hide_errors(); | |
| 26 | 28 | |
| 29 | + $cp_options = get_option('code-profiler'); | |
| 30 | + | |
| 31 | + code_profiler_log_debug( | |
| 32 | + esc_html__('Entering AJAX endpoint (profiler initialization)', 'code-profiler') | |
| 33 | + ); | |
| 34 | + | |
| 27 | 35 | // If this is an AJAX call, make sure it comes from an admin/superadmin. |
| 28 | - if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_start_profiler' ) { | |
| 36 | + if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_start_profiler') { | |
| 29 | 37 | // Admin/Superadmin only |
| 30 | 38 | if (! is_super_admin() ) { |
| 31 | 39 | $msg = esc_html__('You are not allowed to performed this action', 'code-profiler'); |
| 32 | 40 | $response['message'] = $msg; |
| @@ -34,16 +42,29 @@ | ||
| 34 | 42 | code_profiler_wp_send_json( $response ); |
| 35 | 43 | } |
| 36 | 44 | } |
| 37 | 45 | |
| 46 | + code_profiler_log_debug( | |
| 47 | + esc_html__('Verifying security nonce', 'code-profiler') | |
| 48 | + ); | |
| 49 | + | |
| 38 | 50 | // Verify the security nonce |
| 39 | - if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce' ) ) { | |
| 40 | - $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler'); | |
| 51 | + if ( empty( $_POST['cp_nonce'] ) || | |
| 52 | + ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) { | |
| 53 | + | |
| 54 | + $msg = esc_html__( | |
| 55 | + 'Missing or wrong security nonce. Reload the page and try again', | |
| 56 | + 'code-profiler' | |
| 57 | + ); | |
| 41 | 58 | $response['message'] = $msg; |
| 42 | 59 | code_profiler_log_error( $msg ); |
| 43 | 60 | code_profiler_wp_send_json( $response ); |
| 44 | 61 | } |
| 45 | 62 | |
| 63 | + code_profiler_log_debug( | |
| 64 | + esc_html__('Checking MU plugin availability', 'code-profiler') | |
| 65 | + ); | |
| 66 | + | |
| 46 | 67 | // Verify the MU plugin is loaded |
| 47 | 68 | if (! defined('CODE_PROFILER_MU_ON') ) { |
| 48 | 69 | $msg = esc_html__('The MU plugin is not loaded, please check the log', 'code-profiler'); |
| 49 | 70 | $response['message'] = $msg; |
| @@ -50,34 +71,55 @@ | ||
| 50 | 71 | code_profiler_log_error( $msg ); |
| 51 | 72 | code_profiler_wp_send_json( $response ); |
| 52 | 73 | } |
| 53 | 74 | |
| 75 | + code_profiler_log_debug( | |
| 76 | + esc_html__('Retrieving parameters #1', 'code-profiler') | |
| 77 | + ); | |
| 78 | + | |
| 54 | 79 | // Frontend or backend |
| 55 | - if ( empty( $_POST['where'] ) || ! in_array( $_POST['where'], [ 'frontend', 'backend', 'custom' ] ) ) { | |
| 56 | - $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'where' ); | |
| 80 | + if ( empty( $_POST['where'] ) || | |
| 81 | + ! in_array( $_POST['where'], ['frontend', 'backend', 'custom'] ) ) { | |
| 82 | + | |
| 83 | + $msg = sprintf( | |
| 84 | + esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'where' | |
| 85 | + ); | |
| 57 | 86 | $response['message'] = $msg; |
| 58 | 87 | code_profiler_log_error( $msg ); |
| 59 | 88 | code_profiler_wp_send_json( $response ); |
| 60 | 89 | } |
| 90 | + $cp_options['mem_where'] = $_POST['where']; | |
| 91 | + | |
| 92 | + code_profiler_log_debug( | |
| 93 | + esc_html__('Retrieving parameters #2', 'code-profiler') | |
| 94 | + ); | |
| 95 | + | |
| 61 | 96 | if ( empty( $_POST['post'] ) ) { |
| 62 | - $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'post' ); | |
| 97 | + $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'post'); | |
| 63 | 98 | $response['message'] = $msg; |
| 64 | 99 | code_profiler_log_error( $msg ); |
| 65 | 100 | code_profiler_wp_send_json( $response ); |
| 66 | 101 | } |
| 102 | + $cp_options['mem_post'] = $_POST['post']; | |
| 67 | 103 | |
| 68 | 104 | // Make sure we have no more that 4 decimals, because when returning |
| 69 | 105 | // it via AJAX, it will display more decimals than that |
| 70 | - $microtime = number_format( microtime( true ), 4, '.', '' ); | |
| 106 | + $microtime = number_format( microtime( true ), 4, '.', ''); | |
| 71 | 107 | |
| 108 | + code_profiler_log_debug( | |
| 109 | + esc_html__('Retrieving parameters #3', 'code-profiler') | |
| 110 | + ); | |
| 111 | + | |
| 72 | 112 | // Authentication |
| 73 | - if ( empty( $_POST['user'] ) || ! in_array( $_POST['user'], [ 'authenticated', 'unauthenticated' ] ) ) { | |
| 74 | - $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'user' ); | |
| 75 | - $response['message'] = $msg; | |
| 76 | - code_profiler_log_error( $msg ); | |
| 77 | - code_profiler_wp_send_json( $response ); | |
| 113 | + if ( empty( $_POST['user'] ) || $_POST['user'] != 'authenticated' ) { | |
| 114 | + $_POST['user'] = 'unauthenticated'; | |
| 78 | 115 | } |
| 116 | + $cp_options['mem_user'] = $_POST['user']; | |
| 79 | 117 | |
| 118 | + code_profiler_log_debug( | |
| 119 | + esc_html__('Retrieving parameters #4', 'code-profiler') | |
| 120 | + ); | |
| 121 | + | |
| 80 | 122 | if ( empty( $_POST['profile'] ) || strlen( $_POST['profile'] ) > 100 ) { |
| 81 | 123 | $profile = code_profiler_profile_name(); |
| 82 | 124 | } else { |
| 83 | 125 | $profile = sanitize_file_name( $_POST['profile'] ); |
| @@ -83,19 +125,26 @@ | ||
| 83 | 125 | $profile = sanitize_file_name( $_POST['profile'] ); |
| 84 | 126 | } |
| 85 | 127 | |
| 86 | 128 | // URI to profile |
| 87 | - $url = esc_url_raw( $_POST['post'] ); | |
| 129 | + $url = esc_url_raw( $_POST['post'] ); | |
| 130 | + $siteurl = esc_html( site_url() ); | |
| 88 | 131 | code_profiler_log_info( sprintf( |
| 89 | - esc_html__('Starting Code Profiler v%s for %s (profile: %s)', 'code-profiler' ), | |
| 132 | + /* Translators: version, site url, profile name, profile url */ | |
| 133 | + esc_html__('Initializing Code Profiler v%s on %s. Profile: %s - %s', 'code-profiler'), | |
| 90 | 134 | CODE_PROFILER_VERSION, |
| 91 | - $url, | |
| 92 | - $profile | |
| 135 | + $siteurl, | |
| 136 | + $profile, | |
| 137 | + $url | |
| 93 | 138 | ) ); |
| 94 | 139 | |
| 140 | + code_profiler_log_debug( | |
| 141 | + esc_html__('Retrieving parameters #5', 'code-profiler') | |
| 142 | + ); | |
| 143 | + | |
| 95 | 144 | // User-agent |
| 96 | 145 | if ( empty( $_POST['ua'] ) ) { |
| 97 | - $ua = 'FireFox'; | |
| 146 | + $ua = 'Firefox'; | |
| 98 | 147 | } else { |
| 99 | 148 | $ua = sanitize_text_field( $_POST['ua'] ); |
| 100 | 149 | } |
| 101 | 150 | foreach( CODE_PROFILER_UA as $types => $types_array ) { |
| @@ -106,20 +155,50 @@ | ||
| 106 | 155 | } |
| 107 | 156 | } |
| 108 | 157 | } |
| 109 | 158 | if ( empty( $ua_signature ) ) { |
| 110 | - $ua_signature = CODE_PROFILER_UA['Desktop']['FireFox']; | |
| 159 | + $ua_signature = CODE_PROFILER_UA['Desktop']['Firefox']; | |
| 111 | 160 | } |
| 161 | + $cp_options['ua'] = $ua; | |
| 112 | 162 | |
| 163 | + // Theme | |
| 164 | + $themes = code_profiler_get_themes(); | |
| 165 | + if ( empty( $_POST['theme'] ) || empty( $themes[ $_POST['theme'] ] ) ) { | |
| 166 | + $theme = ''; | |
| 167 | + unset( $cp_options['mem_theme'] ); | |
| 168 | + } else { | |
| 169 | + code_profiler_log_debug( | |
| 170 | + esc_html__('Retrieving parameters #6', 'code-profiler') | |
| 171 | + ); | |
| 172 | + $theme = $_POST['theme']; | |
| 173 | + $cp_options['mem_theme'] = $theme; | |
| 174 | + // Append the template to the stylesheet | |
| 175 | + if (! empty( $themes[ $theme ]['t'] ) ) { | |
| 176 | + $theme .= "::{$themes[ $theme ]['t']}"; | |
| 177 | + } else { | |
| 178 | + $theme .= "::$theme"; | |
| 179 | + } | |
| 180 | + } | |
| 181 | + | |
| 182 | + code_profiler_log_debug( | |
| 183 | + esc_html__('Creating security key', 'code-profiler') | |
| 184 | + ); | |
| 185 | + | |
| 113 | 186 | // Create security key |
| 114 | 187 | $profiler_key = bin2hex( random_bytes( 16 ) ); |
| 115 | - $cp_options['hash'] = sha1( $profiler_key ); | |
| 116 | - update_option( 'code-profiler', $cp_options ); | |
| 188 | + touch( CODE_PROFILER_UPLOAD_DIR .'/key_'. sha1( $profiler_key ) .'.tmp'); | |
| 117 | 189 | |
| 118 | - // Build query | |
| 190 | + code_profiler_log_debug( | |
| 191 | + esc_html__('Building HTTP query', 'code-profiler') | |
| 192 | + ); | |
| 193 | + | |
| 194 | + /** | |
| 195 | + * Build the query. | |
| 196 | + */ | |
| 197 | + $raw_url = $url; | |
| 119 | 198 | $url = add_query_arg( [ |
| 120 | 199 | 'CODE_PROFILER_ON' => $microtime, |
| 121 | - 'profiler_key' => $profiler_key, | |
| 200 | + 'profiler_key' => $profiler_key | |
| 122 | 201 | ], $url ); |
| 123 | 202 | |
| 124 | 203 | global $wp_version; |
| 125 | 204 | $headers = [ |
| @@ -127,18 +206,55 @@ | ||
| 127 | 206 | 'Pragma' => 'no-cache', |
| 128 | 207 | 'Expires' => '0', |
| 129 | 208 | 'httpversion' => '1.1', |
| 130 | 209 | // Devs must be allowed to use it on localhost over TLS too |
| 131 | - 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), | |
| 132 | - 'timeout' => 180, // 180-second timeout instead of the default 5s | |
| 210 | + 'sslverify' => apply_filters('https_local_ssl_verify', false ), | |
| 211 | + 'timeout' => 300, // 300-second timeout instead of the default 5s | |
| 133 | 212 | 'redirection' => 0, // We don't want to be redirected |
| 134 | 213 | 'headers' => [ |
| 135 | - 'code-profiler-key' => $profiler_key, | |
| 136 | - 'Accept-Language' => 'en-US,en;q=0.5', | |
| 137 | - 'User-Agent' => $ua_signature | |
| 214 | + // Lowercase header name | |
| 215 | + 'code-profiler-key' => $profiler_key, | |
| 216 | + 'accept-language' => 'en-US,en;q=0.5', | |
| 217 | + 'user-agent' => $ua_signature, | |
| 218 | + 'theme' => $theme | |
| 138 | 219 | ] |
| 139 | 220 | ]; |
| 140 | 221 | |
| 222 | + // Custom HTTP headers | |
| 223 | + if (! empty( $_POST['custom_headers'] ) ) { | |
| 224 | + $custom_headers = explode( PHP_EOL, trim( stripslashes( $_POST['custom_headers'] ) ) ); | |
| 225 | + if (! empty( $custom_headers[0] ) ) { | |
| 226 | + code_profiler_log_debug( | |
| 227 | + esc_html__('Building custom HTTP headers', 'code-profiler') | |
| 228 | + ); | |
| 229 | + $is_custom_headers = ''; | |
| 230 | + foreach( $custom_headers as $custom_header ) { | |
| 231 | + if ( strpos( $custom_header, ':') === false ) { | |
| 232 | + continue; | |
| 233 | + } | |
| 234 | + list( $key, $value ) = explode(':', $custom_header, 2 ); | |
| 235 | + // Lowercase header name | |
| 236 | + $key = trim( strtolower( $key ) ); | |
| 237 | + $value = trim( $value ); | |
| 238 | + // We want printable ASCII characters only | |
| 239 | + $value = code_profiler_ASCII_filter( $value ); | |
| 240 | + if (! empty( $key ) && ! empty( $value ) ) { | |
| 241 | + $headers['headers'][ $key ] = $value; | |
| 242 | + $is_custom_headers .= "$key: $value\n"; | |
| 243 | + } | |
| 244 | + } | |
| 245 | + } | |
| 246 | + } | |
| 247 | + if (! empty( $is_custom_headers ) ) { | |
| 248 | + $cp_options['custom_headers'] = json_encode( $is_custom_headers ); | |
| 249 | + } else { | |
| 250 | + unset( $cp_options['custom_headers'] ); | |
| 251 | + } | |
| 252 | + | |
| 253 | + code_profiler_log_debug( | |
| 254 | + esc_html__('Checking HTTP options', 'code-profiler') | |
| 255 | + ); | |
| 256 | + | |
| 141 | 257 | // Forward basic authentication if any (not available from WP CLI) |
| 142 | 258 | if ( function_exists('apache_request_headers') ) { |
| 143 | 259 | $apache_headers = apache_request_headers(); |
| 144 | 260 | if ( isset( $apache_headers['Authorization'] ) ) { |
| @@ -143,40 +259,216 @@ | ||
| 143 | 259 | $apache_headers = apache_request_headers(); |
| 144 | 260 | if ( isset( $apache_headers['Authorization'] ) ) { |
| 145 | 261 | $headers['headers']['Authorization'] = $apache_headers['Authorization']; |
| 146 | 262 | } |
| 147 | - // WP-CLI (wp code-profiler run --u=FOO --p=BAR) | |
| 263 | + // WP-CLI ($ wp code-profiler run --u=FOO --p=BAR) | |
| 148 | 264 | } elseif ( defined('WP_CLI') && ! empty( $_POST['Authorization'] ) ) { |
| 149 | 265 | $headers['headers']['Authorization'] = $_POST['Authorization']; |
| 150 | 266 | } |
| 151 | 267 | |
| 152 | - if ( $_POST['user'] == 'authenticated' ) { | |
| 268 | + if ( $_POST['user'] == 'authenticated') { | |
| 269 | + | |
| 270 | + code_profiler_log_debug( | |
| 271 | + esc_html__('Creating authentication cookies', 'code-profiler') | |
| 272 | + ); | |
| 273 | + | |
| 153 | 274 | // Used for authentication |
| 154 | 275 | if ( is_ssl() ) { |
| 155 | 276 | $cookie_auth = SECURE_AUTH_COOKIE; |
| 156 | - $scheme = 'secure_auth'; | |
| 277 | + $scheme = 'secure_auth'; | |
| 157 | 278 | } else { |
| 158 | 279 | $cookie_auth = AUTH_COOKIE; |
| 159 | - $scheme = 'auth'; | |
| 280 | + $scheme = 'auth'; | |
| 160 | 281 | } |
| 161 | - $id = get_current_user_id(); | |
| 162 | - $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( $id, time() + 180, $scheme ); | |
| 163 | - $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $id, time() + 180, 'logged_in' ); | |
| 164 | - $session_id = session_id(); | |
| 282 | + | |
| 283 | + // Retrieve the user name (since 1.4.3) | |
| 284 | + if (! defined('WP_CLI') ) { | |
| 285 | + if ( empty( $_POST['username'] ) ) { | |
| 286 | + $msg = esc_html__('Missing authenticated username', 'code-profiler'); | |
| 287 | + $response['message'] = $msg; | |
| 288 | + code_profiler_log_error( $msg ); | |
| 289 | + code_profiler_wp_send_json( $response ); | |
| 290 | + } | |
| 291 | + $username = sanitize_user( $_POST['username'] ); | |
| 292 | + $user_object = get_user_by('login', $username ); | |
| 293 | + if ( $user_object === false ) { | |
| 294 | + $msg = sprintf( esc_html__('User [%s] does not exist.', 'code-profiler'), $username); | |
| 295 | + $response['message'] = $msg; | |
| 296 | + code_profiler_log_error( $msg ); | |
| 297 | + code_profiler_wp_send_json( $response ); | |
| 298 | + } | |
| 299 | + $cp_options['mem_username'] = strtolower( $username ); | |
| 300 | + $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( | |
| 301 | + $user_object->ID, | |
| 302 | + time() + 180, | |
| 303 | + $scheme | |
| 304 | + ); | |
| 305 | + if ( empty( $headers['cookies'][ $cookie_auth ] ) ) { | |
| 306 | + $msg = esc_html__('Unable to create the authentication cookie', 'code-profiler'); | |
| 307 | + code_profiler_log_error( $msg ); | |
| 308 | + $response['message'] = $msg; | |
| 309 | + code_profiler_wp_send_json( $response ); | |
| 310 | + } | |
| 311 | + $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( | |
| 312 | + $user_object->ID, | |
| 313 | + time() + 180, | |
| 314 | + 'logged_in' | |
| 315 | + ); | |
| 316 | + if ( empty( $headers['cookies'][ LOGGED_IN_COOKIE ] ) ) { | |
| 317 | + $msg = esc_html__('Unable to create the "logged_in" cookie', 'code-profiler'); | |
| 318 | + code_profiler_log_error( $msg ); | |
| 319 | + $response['message'] = $msg; | |
| 320 | + code_profiler_wp_send_json( $response ); | |
| 321 | + } | |
| 322 | + // WP CLI | |
| 323 | + } else { | |
| 324 | + $id = get_current_user_id(); | |
| 325 | + $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( | |
| 326 | + $id, | |
| 327 | + time() + 180, | |
| 328 | + $scheme | |
| 329 | + ); | |
| 330 | + if ( empty( $headers['cookies'][ $cookie_auth ] ) ) { | |
| 331 | + $msg = esc_html__('Unable to create the authentication cookie', 'code-profiler'); | |
| 332 | + code_profiler_log_error( $msg ); | |
| 333 | + $response['message'] = $msg; | |
| 334 | + code_profiler_wp_send_json( $response ); | |
| 335 | + } | |
| 336 | + $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( | |
| 337 | + $id, | |
| 338 | + time() + 180, | |
| 339 | + 'logged_in' | |
| 340 | + ); | |
| 341 | + if ( empty( $headers['cookies'][ LOGGED_IN_COOKIE ] ) ) { | |
| 342 | + $msg = esc_html__('Unable to create the "logged_in" cookie', 'code-profiler'); | |
| 343 | + code_profiler_log_error( $msg ); | |
| 344 | + $response['message'] = $msg; | |
| 345 | + code_profiler_wp_send_json( $response ); | |
| 346 | + } | |
| 347 | + } | |
| 348 | + $session_id = session_id(); | |
| 165 | 349 | if ( $session_id !== false ) { |
| 166 | - $session_name = session_name(); | |
| 167 | - $headers['cookies'][ $session_name ] = $session_id; | |
| 350 | + $session_name = session_name(); | |
| 351 | + $headers['cookies'][ $session_name ] = $session_id; | |
| 168 | 352 | } |
| 169 | 353 | } |
| 170 | 354 | |
| 171 | - if ( function_exists( 'opcache_reset' ) ) { | |
| 355 | + if ( function_exists('opcache_reset') ) { | |
| 356 | + code_profiler_log_debug( | |
| 357 | + esc_html__('Clearing opcode cache', 'code-profiler') | |
| 358 | + ); | |
| 172 | 359 | opcache_reset(); |
| 173 | 360 | } |
| 174 | 361 | |
| 362 | + // GET or POST method | |
| 363 | + if (! empty( $_POST['method'] ) && $_POST['method'] == 'post') { | |
| 364 | + $safe_method = 'wp_safe_remote_post'; | |
| 365 | + $cp_options['mem_method'] = 'post'; | |
| 366 | + | |
| 367 | + // Content-type | |
| 368 | + $content_type = [ | |
| 369 | + 1 => 'application/x-www-form-urlencoded', | |
| 370 | + 2 => 'application/json' | |
| 371 | + ]; | |
| 372 | + if ( empty( $_POST['content_type'] ) || | |
| 373 | + ! in_array( $_POST['content_type'], [ 1, 2 ] ) ) { | |
| 374 | + | |
| 375 | + $cp_options['mem_content_type'] = 1; | |
| 376 | + } else { | |
| 377 | + $cp_options['mem_content_type'] = (int) $_POST['content_type']; | |
| 378 | + } | |
| 379 | + $headers['headers']['content-type'] = $content_type[ $cp_options['mem_content_type'] ]; | |
| 380 | + | |
| 381 | + // Optional POST payload | |
| 382 | + if (! empty( $_POST['payload'] ) ) { | |
| 383 | + $_payload = trim( stripslashes( $_POST['payload'] ) ); | |
| 384 | + | |
| 385 | + code_profiler_log_debug( | |
| 386 | + esc_html__('Building POST payload', 'code-profiler') | |
| 387 | + ); | |
| 388 | + | |
| 389 | + // application/x-www-form-urlencoded | |
| 390 | + if ( $cp_options['mem_content_type'] == 1 ) { | |
| 391 | + $payload_array = explode( PHP_EOL, $_payload ); | |
| 392 | + foreach( $payload_array as $item ) { | |
| 393 | + $payload = explode('=', trim( $item ), 2 ); | |
| 394 | + if ( isset( $payload[1] ) ) { | |
| 395 | + $payload[0] = trim( $payload[0] ); | |
| 396 | + $payload[1] = trim( $payload[1] ); | |
| 397 | + $headers['body'][ $payload[0] ] = $payload[1]; | |
| 398 | + } | |
| 399 | + } | |
| 400 | + } else { | |
| 401 | + | |
| 402 | + $headers['body'] = $_payload; | |
| 403 | + } | |
| 404 | + $cp_options['payload'] = json_encode( $_payload ); | |
| 405 | + | |
| 406 | + } else { | |
| 407 | + // POST request without a payload | |
| 408 | + unset( $cp_options['payload'] ); | |
| 409 | + } | |
| 410 | + | |
| 411 | + } else { | |
| 412 | + $safe_method = 'wp_safe_remote_get'; | |
| 413 | + $cp_options['mem_method'] = 'get'; | |
| 414 | + } | |
| 415 | + | |
| 416 | + // Optional user-defined cookies | |
| 417 | + if (! empty( $_POST['cookies'] ) ) { | |
| 418 | + | |
| 419 | + code_profiler_log_debug( | |
| 420 | + esc_html__('Building HTTP Cookies', 'code-profiler') | |
| 421 | + ); | |
| 422 | + | |
| 423 | + $cookies_array = explode( PHP_EOL, trim( stripslashes( $_POST['cookies'] ) ) ); | |
| 424 | + foreach( $cookies_array as $item ) { | |
| 425 | + $cookie = explode('=', trim( $item ), 2 ); | |
| 426 | + if ( isset( $cookie[1] ) ) { | |
| 427 | + $cookie[0] = trim( $cookie[0] ); | |
| 428 | + $cookie[1] = trim( $cookie[1] ); | |
| 429 | + $headers['cookies'][ $cookie[0] ] = $cookie[1]; | |
| 430 | + } | |
| 431 | + } | |
| 432 | + $cp_options['cookies'] = json_encode( $_POST['cookies'] ); | |
| 433 | + } else { | |
| 434 | + unset( $cp_options['cookies'] ); | |
| 435 | + } | |
| 436 | + | |
| 437 | + /** | |
| 438 | + * Optional file and folder exclusions. | |
| 439 | + */ | |
| 440 | + $tmp_exclusions = []; | |
| 441 | + if (! empty( $_POST['exclusions'] ) ) { | |
| 442 | + $tmp_array = explode( PHP_EOL, trim( stripslashes( $_POST['exclusions'] ) ) ); | |
| 443 | + foreach( $tmp_array as $item ) { | |
| 444 | + $item = trim( code_profiler_ASCII_filter( $item ) ); | |
| 445 | + if ( $item ) { | |
| 446 | + $tmp_exclusions[] = $item; | |
| 447 | + } | |
| 448 | + } | |
| 449 | + } | |
| 450 | + /** | |
| 451 | + * Remove duplicates. | |
| 452 | + */ | |
| 453 | + $exclusions = array_unique( $tmp_exclusions ); | |
| 454 | + | |
| 455 | + if ( $exclusions) { | |
| 456 | + $cp_options['exclusions'] = json_encode( $exclusions ); | |
| 457 | + } else { | |
| 458 | + unset( $cp_options['exclusions'] ); | |
| 459 | + } | |
| 460 | + | |
| 461 | + update_option('code-profiler', $cp_options ); | |
| 462 | + | |
| 463 | + code_profiler_log_debug( | |
| 464 | + esc_html__('Sending HTTP request', 'code-profiler') | |
| 465 | + ); | |
| 466 | + | |
| 175 | 467 | // We must allow developers to run the profiler |
| 176 | 468 | // on a local IP (e.g, http://127.0.0.1/) |
| 177 | - add_filter( 'http_request_host_is_external', '__return_true' ); | |
| 178 | - $res = wp_safe_remote_get( $url, $headers ); | |
| 469 | + add_filter('http_request_host_is_external', '__return_true'); | |
| 470 | + $res = $safe_method( $url, $headers ); | |
| 179 | 471 | |
| 180 | 472 | // Connection error |
| 181 | 473 | if ( is_wp_error( $res ) ) { |
| 182 | 474 | $msg = esc_html__('Cannot connect to the requested page: %s', 'code-profiler'); |
| @@ -187,23 +479,115 @@ | ||
| 187 | 479 | code_profiler_log_error( sprintf( $msg, $res->get_error_message() ) ); |
| 188 | 480 | code_profiler_wp_send_json( $response ); |
| 189 | 481 | } |
| 190 | 482 | |
| 483 | + code_profiler_log_debug( | |
| 484 | + esc_html__('Fetching HTTP response', 'code-profiler') | |
| 485 | + ); | |
| 486 | + | |
| 487 | + /** | |
| 488 | + * Always log last HTTP response headers and body, | |
| 489 | + * except sensitive data (cookies & PHP session ID). | |
| 490 | + */ | |
| 491 | + if ( isset( $res['headers'] ) && isset( $res['body'] ) ) { | |
| 492 | + /** | |
| 493 | + * Search for an existing log or create it. | |
| 494 | + */ | |
| 495 | + $last_log = code_profiler_glob( | |
| 496 | + CODE_PROFILER_UPLOAD_DIR, | |
| 497 | + '^last_request\.\d+?\.\d+?\.log$', | |
| 498 | + true | |
| 499 | + ); | |
| 500 | + if ( empty( $last_log[0] ) ) { | |
| 501 | + $last_log[0] = CODE_PROFILER_UPLOAD_DIR .'/last_request.'. microtime( true ) .'.log'; | |
| 502 | + } | |
| 503 | + /** | |
| 504 | + * Parse headers. | |
| 505 | + */ | |
| 506 | + $headers = "HTTP {$res['response']['code']} {$res['response']['message']}\n"; | |
| 507 | + | |
| 508 | + foreach( $res['headers'] as $key => $value ) { | |
| 509 | + /** | |
| 510 | + * Remove cookies. | |
| 511 | + */ | |
| 512 | + if ( $key == 'set-cookie') { | |
| 513 | + $headers .= ucfirst( $key ) .': *** '. __('Removed', 'code-profiler') ." ***\n"; | |
| 514 | + } else { | |
| 515 | + /** | |
| 516 | + * HTTP headers can contain arrays. | |
| 517 | + */ | |
| 518 | + if ( is_array( $value ) ) { | |
| 519 | + foreach( $value as $k => $v ) { | |
| 520 | + $headers .= ucfirst( $key ) .": $v\n"; | |
| 521 | + } | |
| 522 | + } else { | |
| 523 | + $headers .= ucfirst( $key ) .": $value\n"; | |
| 524 | + } | |
| 525 | + } | |
| 526 | + } | |
| 527 | + /** | |
| 528 | + * Save to the log. | |
| 529 | + */ | |
| 530 | + file_put_contents( $last_log[0], | |
| 531 | + "==================================================\n". | |
| 532 | + __('Requested page:', 'code-profiler') ."\n\n". | |
| 533 | + "$raw_url\n". | |
| 534 | + "==================================================\n". | |
| 535 | + __('Response headers:', 'code-profiler') ."\n\n". | |
| 536 | + $headers . | |
| 537 | + "==================================================\n". | |
| 538 | + __('Response body:', 'code-profiler') ."\n\n". | |
| 539 | + print_r( $res['body'], true ). | |
| 540 | + "\n==================================================\n" | |
| 541 | + ); | |
| 542 | + } | |
| 543 | + | |
| 191 | 544 | // HTTP status code |
| 192 | 545 | if (! empty( $cp_options['http_response'] ) ) { |
| 193 | 546 | if ( preg_match( "/{$cp_options['http_response']}/", $res['response']['code'] ) ) { |
| 194 | - $msg = esc_html__('The website returned the following HTTP status code: %s %s.', 'code-profiler'). | |
| 195 | - ' '. | |
| 196 | - esc_html__('By default, the profiler will always abort and throw an error if the server did not return a 200 HTTP status code. You can change that behaviour in the Settings section if the page you are profiling needs to return a different code (3xx, 4xx or 5xx).', 'code-profiler'); | |
| 547 | + | |
| 548 | + $msg = ''; | |
| 549 | + | |
| 550 | + $log = esc_html__( | |
| 551 | + /* Translators: HTTP response code and message */ | |
| 552 | + 'The website returned the following HTTP status code: %s %s.', 'code-profiler' | |
| 553 | + ); | |
| 554 | + | |
| 555 | + if ( $res['response']['code'] < 500 ) { | |
| 556 | + $log .= ' '. esc_html__('By default, the profiler will always abort and throw an error if the server did not return a 200 HTTP status code. You can change that behaviour in the "Settings" section if the page you are profiling needs to return a different code (3xx, 4xx or 5xx).', 'code-profiler'); | |
| 557 | + } | |
| 558 | + | |
| 559 | + $msg .= $log .' '. esc_html__('You may find more details about this error in your PHP error log and/or in the "Logs" section.', 'code-profiler'); | |
| 560 | + | |
| 197 | 561 | $response['message'] = sprintf( |
| 198 | 562 | $msg, |
| 199 | 563 | (int) $res['response']['code'], |
| 200 | - esc_html( $res['response']['message'] ) | |
| 564 | + $res['response']['message'] | |
| 201 | 565 | ); |
| 202 | - code_profiler_log_error( sprintf( $msg, $res['response']['code'], $res['response']['message'] ) ); | |
| 566 | + code_profiler_log_error( | |
| 567 | + sprintf( $log, $res['response']['code'], $res['response']['message'] ) | |
| 568 | + ); | |
| 569 | + | |
| 570 | + // If it is a 301/302 redirection, we write the new URL to the log | |
| 571 | + if ( in_array( $res['response']['code'], [301, 302] ) && | |
| 572 | + isset( $res['headers']['location'] ) ) { | |
| 573 | + | |
| 574 | + code_profiler_log_error( | |
| 575 | + sprintf( | |
| 576 | + /* Translators: URL */ | |
| 577 | + esc_html__('The URL redirects to: %s', 'code-profiler'), | |
| 578 | + $res['headers']['location'] | |
| 579 | + ) | |
| 580 | + ); | |
| 581 | + } | |
| 203 | 582 | code_profiler_wp_send_json( $response ); |
| 204 | 583 | } |
| 205 | 584 | } |
| 585 | + | |
| 586 | + code_profiler_log_debug( | |
| 587 | + esc_html__('Decoding body', 'code-profiler') | |
| 588 | + ); | |
| 589 | + | |
| 206 | 590 | // Check response |
| 207 | 591 | $message = json_decode( $res['body'], true ); |
| 208 | 592 | if ( isset( $message['status'] ) && isset( $message['message'] ) ) { |
| 209 | 593 | $response['status'] = $message['status']; |
| @@ -209,26 +593,32 @@ | ||
| 209 | 593 | $response['status'] = $message['status']; |
| 210 | 594 | $response['message'] = $message['message']; |
| 211 | 595 | code_profiler_wp_send_json( $response ); |
| 212 | 596 | } |
| 213 | - code_profiler_log_info( esc_html__('Collecting data to analyze', 'code-profiler' ) ); | |
| 597 | + code_profiler_log_info( | |
| 598 | + esc_html__('Collecting data to analyze', 'code-profiler') | |
| 599 | + ); | |
| 214 | 600 | // Return success |
| 215 | 601 | $response = ['status' => 'success']; |
| 216 | 602 | $response['message'] = 'success'; |
| 217 | 603 | $response['microtime'] = $microtime; |
| 218 | 604 | |
| 605 | + code_profiler_log_debug( | |
| 606 | + esc_html__('Leaving AJAX endpoint', 'code-profiler') | |
| 607 | + ); | |
| 608 | + | |
| 219 | 609 | // AJAX action? |
| 220 | - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { | |
| 610 | + if ( defined('DOING_AJAX') && DOING_AJAX ) { | |
| 221 | 611 | code_profiler_wp_send_json( $response ); |
| 222 | - } else { | |
| 223 | - return json_encode( $response ); | |
| 224 | 612 | } |
| 225 | 613 | |
| 614 | + return json_encode( $response ); | |
| 615 | + | |
| 226 | 616 | } |
| 227 | 617 | |
| 228 | -// ===================================================================== | |
| 618 | +// ===================================================================== 2023-11-17 | |
| 229 | 619 | |
| 230 | -add_action( 'wp_ajax_codeprofiler_prepare_report', 'codeprofiler_prepare_report' ); | |
| 620 | +add_action('wp_ajax_codeprofiler_prepare_report', 'codeprofiler_prepare_report'); | |
| 231 | 621 | |
| 232 | 622 | function codeprofiler_prepare_report() { |
| 233 | 623 | |
| 234 | 624 | $response = ['status' => 'error']; |
| @@ -233,9 +623,9 @@ | ||
| 233 | 623 | |
| 234 | 624 | $response = ['status' => 'error']; |
| 235 | 625 | |
| 236 | 626 | // If this is an AJAX call, make sure it comes from an admin/superadmin. |
| 237 | - if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_prepare_report' ) { | |
| 627 | + if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_prepare_report') { | |
| 238 | 628 | // Admin/Superadmin only |
| 239 | 629 | if (! is_super_admin() ) { |
| 240 | 630 | $msg = esc_html__('You are not allowed to performed this action', 'code-profiler'); |
| 241 | 631 | $response['message'] = $msg; |
| @@ -243,17 +633,34 @@ | ||
| 243 | 633 | code_profiler_wp_send_json( $response ); |
| 244 | 634 | } |
| 245 | 635 | } |
| 246 | 636 | |
| 637 | + code_profiler_log_debug( | |
| 638 | + esc_html__('Entering AJAX endpoint (report preparation)', 'code-profiler') | |
| 639 | + ); | |
| 640 | + | |
| 641 | + code_profiler_hide_errors(); | |
| 642 | + | |
| 643 | + code_profiler_log_debug( | |
| 644 | + esc_html__('Verifying security nonce', 'code-profiler') | |
| 645 | + ); | |
| 646 | + | |
| 247 | 647 | // Verify the security nonce |
| 248 | - if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce' ) ) { | |
| 249 | - $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler'); | |
| 648 | + if ( empty( $_POST['cp_nonce'] ) || | |
| 649 | + ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) { | |
| 650 | + | |
| 651 | + $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', | |
| 652 | + 'code-profiler'); | |
| 250 | 653 | $response['message'] = $msg; |
| 251 | 654 | code_profiler_log_error( $msg ); |
| 252 | 655 | code_profiler_wp_send_json( $response ); |
| 253 | 656 | } |
| 254 | 657 | |
| 255 | - if ( empty( $_POST['microtime'] ) || ! preg_match( '/^\d{10}\.\d+$/', $_POST['microtime'] ) ) { | |
| 658 | + code_profiler_log_debug( | |
| 659 | + esc_html__('Retrieving profile ID', 'code-profiler') | |
| 660 | + ); | |
| 661 | + | |
| 662 | + if ( empty( $_POST['microtime'] ) || ! preg_match('/^\d{10}\.\d+$/', $_POST['microtime'] ) ) { | |
| 256 | 663 | $msg = esc_html__('Missing parameter (microtime).', 'code-profiler'); |
| 257 | 664 | $response['message'] = $msg; |
| 258 | 665 | code_profiler_log_error( $msg ); |
| 259 | 666 | code_profiler_wp_send_json( $response ); |
| @@ -259,8 +666,12 @@ | ||
| 259 | 666 | code_profiler_wp_send_json( $response ); |
| 260 | 667 | } |
| 261 | 668 | $microtime = sanitize_text_field( $_POST['microtime'] ); |
| 262 | 669 | |
| 670 | + code_profiler_log_debug( | |
| 671 | + esc_html__('Retrieving profile name', 'code-profiler') | |
| 672 | + ); | |
| 673 | + | |
| 263 | 674 | $profile = sanitize_file_name( $_POST['profile'] ); |
| 264 | 675 | if ( empty( $profile ) ) { |
| 265 | 676 | $msg = esc_html__('Missing profile name.', 'code-profiler'); |
| 266 | 677 | $response['message'] = $msg; |
| @@ -267,9 +678,11 @@ | ||
| 267 | 678 | code_profiler_log_error( $msg ); |
| 268 | 679 | code_profiler_wp_send_json( $response ); |
| 269 | 680 | } |
| 270 | 681 | |
| 271 | - code_profiler_log_info( esc_html__('Preparing report', 'code-profiler') ); | |
| 682 | + code_profiler_log_info( | |
| 683 | + esc_html__('Preparing the report', 'code-profiler') | |
| 684 | + ); | |
| 272 | 685 | require 'class-report.php'; |
| 273 | 686 | $report = new CodeProfiler_Report( $profile, $microtime ); |
| 274 | 687 | $report->prepare_report(); |
| 275 | 688 | |
| @@ -274,26 +687,107 @@ | ||
| 274 | 687 | $report->prepare_report(); |
| 275 | 688 | |
| 276 | 689 | // Take a 1s break so that we can spot any potential error |
| 277 | 690 | // in the backend before AJAX refresh the page |
| 278 | - usleep(1000000); | |
| 691 | + usleep( 1000000 ); | |
| 279 | 692 | |
| 280 | - // Clear hash | |
| 281 | - $cp_options = get_option( 'code-profiler' ); | |
| 282 | - unset( $cp_options['hash'] ); | |
| 283 | - update_option( 'code-profiler', $cp_options ); | |
| 284 | - | |
| 285 | - code_profiler_log_info( esc_html__('All done, exiting profiler', 'code-profiler') ); | |
| 693 | + code_profiler_log_info( | |
| 694 | + esc_html__('All done, exiting profiler', 'code-profiler') | |
| 695 | + ); | |
| 286 | 696 | $response['cp_profile'] = $microtime; |
| 287 | 697 | $response['status'] = 'success'; |
| 288 | 698 | $response['message'] = 'success'; |
| 289 | 699 | |
| 700 | + code_profiler_log_debug( | |
| 701 | + esc_html__('Leaving AJAX endpoint', 'code-profiler') | |
| 702 | + ); | |
| 703 | + | |
| 290 | 704 | // AJAX action? |
| 291 | - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { | |
| 705 | + if ( defined('DOING_AJAX') && DOING_AJAX ) { | |
| 292 | 706 | code_profiler_wp_send_json( $response ); |
| 293 | - } else { | |
| 294 | - return json_encode( $response ); | |
| 295 | 707 | } |
| 708 | + | |
| 709 | + return json_encode( $response ); | |
| 710 | + | |
| 711 | +} | |
| 712 | + | |
| 713 | +// ===================================================================== 2023-11-17 | |
| 714 | +// Rename a profile. | |
| 715 | + | |
| 716 | +add_action('wp_ajax_codeprofiler_rename', 'codeprofiler_rename'); | |
| 717 | + | |
| 718 | +function codeprofiler_rename() { | |
| 719 | + | |
| 720 | + $response = ['status' => 'error']; | |
| 721 | + | |
| 722 | + code_profiler_hide_errors(); | |
| 723 | + | |
| 724 | + // Admin/Superadmin only | |
| 725 | + if (! is_super_admin() ) { | |
| 726 | + $response['message'] = esc_html__( | |
| 727 | + 'You are not allowed to performed this action.', 'code-profiler' | |
| 728 | + ); | |
| 729 | + wp_send_json( $response ); | |
| 730 | + } | |
| 731 | + | |
| 732 | + // Verify the security nonce | |
| 733 | + if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'rename-profile') ) { | |
| 734 | + $response['message'] = esc_html__( | |
| 735 | + 'Missing or wrong security nonce. Reload the page and try again.', 'code-profiler' | |
| 736 | + ); | |
| 737 | + wp_send_json( $response ); | |
| 738 | + } | |
| 739 | + | |
| 740 | + if ( empty( $_POST['new_name'] ) ) { | |
| 741 | + $response['message'] = esc_html__('Please enter a name for this profile.', 'code-profiler'); | |
| 742 | + wp_send_json( $response ); | |
| 743 | + } | |
| 744 | + $new_name = sanitize_file_name( $_POST['new_name'] ); | |
| 745 | + if ( strlen( $new_name ) > 100 ) { | |
| 746 | + $new_name = substr( $new_name, 0, 100 ); | |
| 747 | + } | |
| 748 | + if ( empty( $new_name ) ) { | |
| 749 | + $response['message'] = esc_html__('Please enter a name for this profile.', 'code-profiler'); | |
| 750 | + wp_send_json( $response ); | |
| 751 | + } | |
| 752 | + | |
| 753 | + if ( empty( $_POST['profile'] ) || ! preg_match('/^\d{10}\.\d{4}$/', $_POST['profile'] ) ) { | |
| 754 | + $response['message'] = esc_html__('Missing profile identifier.', 'code-profiler'); | |
| 755 | + wp_send_json( $response ); | |
| 756 | + } | |
| 757 | + $profile = $_POST['profile']; | |
| 758 | + | |
| 759 | + $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, "^$profile", true ); | |
| 760 | + | |
| 761 | + $res = false; | |
| 762 | + | |
| 763 | + if ( is_array( $glob ) ) { | |
| 764 | + foreach( $glob as $path ) { | |
| 765 | + // preg_quote is needed for Windows servers because ABSPATH will contain backslashes | |
| 766 | + if ( preg_match('`^'. preg_quote( CODE_PROFILER_UPLOAD_DIR . DIRECTORY_SEPARATOR ) . | |
| 767 | + '(\d{10}\.\d{4})\..+?\.([a-z]+?\.profile)$`', $path, $match ) ) { | |
| 768 | + | |
| 769 | + $res = rename( $path, CODE_PROFILER_UPLOAD_DIR . "/{$match[1]}.$new_name.{$match[2]}" ); | |
| 770 | + if ( $res === false ) { | |
| 771 | + $response['message'] = esc_html__( | |
| 772 | + 'The operation failed.', 'code-profiler' | |
| 773 | + ); | |
| 774 | + wp_send_json( $response ); | |
| 775 | + } | |
| 776 | + } | |
| 777 | + } | |
| 778 | + } | |
| 779 | + | |
| 780 | + if ( $res === false ) { | |
| 781 | + $response['message'] = esc_html__( | |
| 782 | + 'The operation failed.', 'code-profiler' | |
| 783 | + ); | |
| 784 | + wp_send_json( $response ); | |
| 785 | + } | |
| 786 | + | |
| 787 | + $response['status'] = 'success'; | |
| 788 | + $response['newname'] = $new_name; | |
| 789 | + wp_send_json( $response ); | |
| 296 | 790 | |
| 297 | 791 | } |
| 298 | 792 | |
| 299 | 793 | // ===================================================================== |