header( 'Location', $url ); return $response; } /** * Persist a safe redirect target until the OAuth callback. * * @param string $url Raw redirect_to value. */ public static function persist_cookie( string $url ): void { $url = self::normalize( $url, '' ); if ( '' === $url || self::is_unsafe_target( $url ) ) { return; } $path = defined( 'COOKIEPATH' ) ? COOKIEPATH : '/'; $domain = defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : ''; setcookie( self::COOKIE_NAME, $url, time() + 600, $path, $domain, is_ssl(), true ); } /** * Read and clear the post-login cookie. * * @param string $default_url Fallback URL. */ public static function consume_cookie( string $default_url ): string { $raw = ''; if ( isset( $_COOKIE[ self::COOKIE_NAME ] ) ) { $raw = (string) $_COOKIE[ self::COOKIE_NAME ]; } self::clear_cookie(); return self::normalize( $raw, $default_url ); } /** * @param string $url Raw value. */ private static function decode_url( string $url ): string { $url = trim( wp_unslash( $url ) ); $decoded = rawurldecode( $url ); if ( str_contains( $decoded, '%3A' ) || str_contains( $decoded, '%2F' ) ) { $decoded = rawurldecode( $decoded ); } return trim( $decoded ); } /** * @param string $url Candidate. */ private static function ensure_absolute( string $url ): string { $url = trim( $url ); if ( '' === $url ) { return home_url( '/' ); } if ( str_starts_with( $url, '//' ) ) { $url = ( is_ssl() ? 'https:' : 'http:' ) . $url; } if ( str_starts_with( $url, '/' ) ) { return home_url( $url ); } if ( self::has_scheme_and_host( $url ) ) { return $url; } return home_url( '/' . ltrim( $url, '/' ) ); } /** * @param string $url Candidate. */ private static function has_scheme_and_host( string $url ): bool { $parts = wp_parse_url( $url ); if ( ! is_array( $parts ) ) { return false; } $scheme = strtolower( (string) ( $parts['scheme'] ?? '' ) ); $host = (string) ( $parts['host'] ?? '' ); return '' !== $host && in_array( $scheme, array( 'http', 'https' ), true ); } /** * Never send the browser back into the REST OAuth callback. * * @param string $url Candidate. */ private static function is_unsafe_target( string $url ): bool { $path = (string) wp_parse_url( $url, PHP_URL_PATH ); if ( str_contains( $path, '/wp-json/' ) ) { return true; } if ( str_contains( $path, '/forwp-account/v1/callback' ) ) { return true; } return false; } /** * Clear the redirect cookie. */ private static function clear_cookie(): void { $path = defined( 'COOKIEPATH' ) ? COOKIEPATH : '/'; $domain = defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : ''; setcookie( self::COOKIE_NAME, '', time() - HOUR_IN_SECONDS, $path, $domain, is_ssl(), true ); unset( $_COOKIE[ self::COOKIE_NAME ] ); } /** * Last-resort redirect if headers were already sent. * * @param string $url Absolute URL. */ private static function print_html_fallback( string $url ): void { header( 'Content-Type: text/html; charset=utf-8' ); echo ''; echo ''; echo ''; echo '' . esc_html__( 'Continue', '4wp-account' ) . ''; echo ''; echo ''; } }