, ttl: int}|WP_Error */ public static function get_json( string $url ) { if ( ! self::is_https_url( $url ) ) { return new WP_Error( 'fetch_url', 'not an https URL' ); } $res = wp_safe_remote_get( $url, array( 'timeout' => self::TIMEOUT, 'redirection' => 0, 'limit_response_size' => self::MAX_BYTES, 'cookies' => array(), 'headers' => array( 'Accept' => 'application/json' ), 'user-agent' => 'AgentAdapt/' . AGENTADAPT_VERSION . ' (+' . home_url( '/' ) . ')', ) ); if ( is_wp_error( $res ) ) { return new WP_Error( 'fetch_failed', $res->get_error_message() ); } $code = (int) wp_remote_retrieve_response_code( $res ); if ( 200 !== $code ) { return new WP_Error( 'fetch_status', 'HTTP ' . $code ); } $body = (string) wp_remote_retrieve_body( $res ); if ( strlen( $body ) >= self::MAX_BYTES ) { return new WP_Error( 'fetch_size', 'response too large' ); } try { $json = json_decode( $body, true, 32, JSON_THROW_ON_ERROR ); } catch ( Throwable $e ) { return new WP_Error( 'fetch_json', 'not JSON' ); } if ( ! is_array( $json ) || array_is_list( $json ) ) { return new WP_Error( 'fetch_json', 'not a JSON object' ); } return array( 'json' => $json, 'ttl' => self::ttl( (string) wp_remote_retrieve_header( $res, 'cache-control' ) ) ); } /** * Cache lifetime from a Cache-Control header, clamped. * * @param string $cache_control Header value. */ public static function ttl( string $cache_control ): int { if ( preg_match( '/(?:^|,)\s*no-(?:store|cache)\b/i', $cache_control ) ) { return self::MIN_TTL; } if ( preg_match( '/max-age\s*=\s*(\d+)/i', $cache_control, $m ) ) { return max( self::MIN_TTL, min( self::MAX_TTL, (int) $m[1] ) ); } return self::DEFAULT_TTL; } /** * Absolute https URL with a host, no userinfo, no fragment. * * @param string $url URL. */ public static function is_https_url( string $url ): bool { if ( strlen( $url ) > 512 ) { return false; } $p = wp_parse_url( $url ); return is_array( $p ) && 'https' === strtolower( $p['scheme'] ?? '' ) && ! empty( $p['host'] ) && ! isset( $p['user'] ) && ! isset( $p['pass'] ) && ! isset( $p['fragment'] ); } }