collector = $collector; $this->init_hooks(); } /** * Initialize WordPress hooks. * * @return void */ private function init_hooks(): void { add_action( 'init', array( $this, 'register_endpoint' ) ); // Use parse_request instead of template_redirect to handle the request early, // before themes and other plugins (like Twig-based ones) can interfere. add_action( 'parse_request', array( $this, 'handle_request' ) ); } /** * Register the metrics endpoint rewrite rule. * * @return void */ public function register_endpoint(): void { add_rewrite_rule( '^metrics/?$', 'index.php?wp_prometheus_metrics=1', 'top' ); add_rewrite_tag( '%wp_prometheus_metrics%', '([^&]+)' ); } /** * Handle the metrics endpoint request. * * Called during parse_request to intercept before themes/plugins load. * * @param \WP $wp WordPress environment instance. * @return void */ public function handle_request( \WP $wp ): void { if ( empty( $wp->query_vars['wp_prometheus_metrics'] ) ) { return; } // Authenticate the request. if ( ! $this->authenticate() ) { status_header( 401 ); header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' ); header( 'Content-Type: text/plain; charset=utf-8' ); echo 'Unauthorized'; exit; } // Output metrics. status_header( 200 ); header( 'Content-Type: text/plain; version=0.0.4; charset=utf-8' ); header( 'Cache-Control: no-cache, no-store, must-revalidate' ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Prometheus format. echo $this->collector->render(); exit; } /** * Authenticate the metrics request. * * @return bool */ private function authenticate(): bool { $auth_token = get_option( 'wp_prometheus_auth_token', '' ); // If no token is set, deny access. if ( empty( $auth_token ) ) { return false; } // Check for Bearer token in Authorization header. $auth_header = $this->get_authorization_header(); if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) { return hash_equals( $auth_token, $matches[1] ); } // Check for token in query parameter (less secure but useful for testing). // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check. if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) { return true; } return false; } /** * Get the Authorization header from the request. * * @return string */ private function get_authorization_header(): string { if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) ); } if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) { return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ); } if ( function_exists( 'apache_request_headers' ) ) { $headers = apache_request_headers(); if ( isset( $headers['Authorization'] ) ) { return sanitize_text_field( $headers['Authorization'] ); } } return ''; } }