get_webfinger(); } /** * Resolve a WebFinger resource * * @param string $uri The WebFinger Resource * * @return string|WP_Error The URL or WP_Error */ public static function resolve( $uri ) { $data = self::get_data( $uri ); if ( \is_wp_error( $data ) ) { return $data; } if ( ! is_array( $data ) || empty( $data['links'] ) ) { return new WP_Error( 'webfinger_missing_links', __( 'No valid Link elements found.', 'activitypub' ), $data ); } foreach ( $data['links'] as $link ) { if ( 'self' === $link['rel'] && ( 'application/activity+json' === $link['type'] || 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' === $link['type'] ) ) { return $link['href']; } } return new WP_Error( 'webfinger_url_no_activitypub', __( 'The Site supports WebFinger but not ActivityPub', 'activitypub' ), $data ); } /** * Transform a URI to an acct @ * * @param string $uri The URI (acct:, mailto:, http:, https:) * * @return string|WP_Error Error or acct URI */ public static function uri_to_acct( $uri ) { $data = self::get_data( $uri ); if ( is_wp_error( $data ) ) { return $data; } // check if subject is an acct URI if ( isset( $data['subject'] ) && \str_starts_with( $data['subject'], 'acct:' ) ) { return $data['subject']; } // search for an acct URI in the aliases if ( isset( $data['aliases'] ) ) { foreach ( $data['aliases'] as $alias ) { if ( \str_starts_with( $alias, 'acct:' ) ) { return $alias; } } } return new WP_Error( 'webfinger_url_no_acct', __( 'No acct URI found.', 'activitypub' ), $data ); } /** * Convert a URI string to an identifier and its host. * Automatically adds acct: if it's missing. * * @param string $url The URI (acct:, mailto:, http:, https:) * * @return WP_Error|array Error reaction or array with * identifier and host as values */ public static function get_identifier_and_host( $url ) { // remove leading @ $url = ltrim( $url, '@' ); if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) { $identifier = 'acct:' . $url; $scheme = 'acct'; } else { $identifier = $url; $scheme = $match[1]; } $host = null; switch ( $scheme ) { case 'acct': case 'mailto': case 'xmpp': if ( strpos( $identifier, '@' ) !== false ) { $host = substr( $identifier, strpos( $identifier, '@' ) + 1 ); } break; default: $host = wp_parse_url( $identifier, PHP_URL_HOST ); break; } if ( empty( $host ) ) { return new WP_Error( 'webfinger_invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) ); } return array( $identifier, $host ); } /** * Get the WebFinger data for a given URI * * @param string $uri The Identifier: @ or URI * * @return WP_Error|array Error reaction or array with * identifier and host as values */ public static function get_data( $uri ) { $identifier_and_host = self::get_identifier_and_host( $uri ); if ( is_wp_error( $identifier_and_host ) ) { return $identifier_and_host; } $transient_key = self::generate_cache_key( $uri ); list( $identifier, $host ) = $identifier_and_host; $data = \get_transient( $transient_key ); if ( $data ) { return $data; } $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier ); $response = wp_safe_remote_get( $webfinger_url, array( 'headers' => array( 'Accept' => 'application/jrd+json' ), ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'webfinger_url_not_accessible', __( 'The WebFinger Resource is not accessible.', 'activitypub' ), $webfinger_url ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); \set_transient( $transient_key, $data, WEEK_IN_SECONDS ); return $data; } /** * Get the Remote-Follow endpoint for a given URI * * @return string|WP_Error Error or the Remote-Follow endpoint URI. */ public static function get_remote_follow_endpoint( $uri ) { $data = self::get_data( $uri ); if ( is_wp_error( $data ) ) { return $data; } if ( empty( $data['links'] ) ) { return new WP_Error( 'webfinger_missing_links', __( 'No valid Link elements found.', 'activitypub' ), $data ); } foreach ( $data['links'] as $link ) { if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) { return $link['template']; } } return new WP_Error( 'webfinger_missing_remote_follow_endpoint', __( 'No valid Remote-Follow endpoint found.', 'activitypub' ), $data ); } /** * Generate a cache key for a given URI * * @param string $uri A WebFinger Resource URI * * @return string The cache key */ public static function generate_cache_key( $uri ) { $uri = ltrim( $uri, '@' ); if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) { $uri = 'acct:' . $uri; } return 'webfinger_' . md5( $uri ); } }