diff --git a/app/Services/ActivityPubFetchService.php b/app/Services/ActivityPubFetchService.php index 119199eaa..2e9f68402 100644 --- a/app/Services/ActivityPubFetchService.php +++ b/app/Services/ActivityPubFetchService.php @@ -2,76 +2,132 @@ namespace App\Services; -use Illuminate\Support\Facades\Http; -use App\Profile; -use App\Util\ActivityPub\Helpers; use App\Util\ActivityPub\HttpSignature; +use Cache; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\RequestException; +use Illuminate\Support\Facades\Http; class ActivityPubFetchService { + const CACHE_KEY = 'pf:services:apfetchs:'; + public static function get($url, $validateUrl = true) { - if($validateUrl === true) { - if(!Helpers::validateUrl($url)) { - return 0; - } + if (! self::validateUrl($url)) { + return false; } + $domain = parse_url($url, PHP_URL_HOST); + if (! $domain) { + return false; + } + $domainKey = base64_encode($domain); + $urlKey = hash('sha256', $url); + $key = self::CACHE_KEY.$domainKey.':'.$urlKey; - $baseHeaders = [ - 'Accept' => 'application/activity+json, application/ld+json', - ]; + return Cache::remember($key, 3600, function () use ($url) { + $baseHeaders = [ + 'Accept' => 'application/activity+json', + ]; - $headers = HttpSignature::instanceActorSign($url, false, $baseHeaders, 'get'); - $headers['Accept'] = 'application/activity+json, application/ld+json'; - $headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')'; + $headers = HttpSignature::instanceActorSign($url, false, $baseHeaders, 'get'); + $headers['Accept'] = 'application/activity+json'; + $headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')'; - try { - $res = Http::withoutVerifying() - ->withOptions([ + try { + $res = Http::withOptions([ 'allow_redirects' => [ 'max' => 2, 'protocols' => ['https'], - ] - ]) - ->withHeaders($headers) - ->timeout(30) - ->connectTimeout(5) - ->retry(3, 500) - ->get($url); - } catch (RequestException $e) { - return; - } catch (ConnectionException $e) { - return; - } catch (Exception $e) { - return; + ]]) + ->withHeaders($headers) + ->timeout(30) + ->connectTimeout(5) + ->retry(3, 500) + ->get($url); + } catch (RequestException $e) { + return; + } catch (ConnectionException $e) { + return; + } catch (Exception $e) { + return; + } + + if (! $res->ok()) { + return; + } + + if (! $res->hasHeader('Content-Type')) { + return; + } + + $acceptedTypes = [ + 'application/activity+json; charset=utf-8', + 'application/activity+json', + 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + ]; + + $contentType = $res->getHeader('Content-Type')[0]; + + if (! $contentType) { + return; + } + + if (! in_array($contentType, $acceptedTypes)) { + return; + } + + return $res->body(); + }); + } + + public static function validateUrl($url) + { + if (is_array($url)) { + $url = $url[0]; } - if(!$res->ok()) { - return; - } - - if(!$res->hasHeader('Content-Type')) { - return; - } - - $acceptedTypes = [ - 'application/activity+json; charset=utf-8', - 'application/activity+json', - 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' + $localhosts = [ + '127.0.0.1', 'localhost', '::1', ]; - $contentType = $res->getHeader('Content-Type')[0]; - - if(!$contentType) { - return; + if (strtolower(mb_substr($url, 0, 8)) !== 'https://') { + return false; } - if(!in_array($contentType, $acceptedTypes)) { - return; + if (substr_count($url, '://') !== 1) { + return false; } - return $res->body(); + if (mb_substr($url, 0, 8) !== 'https://') { + $url = 'https://'.substr($url, 8); + } + + $valid = filter_var($url, FILTER_VALIDATE_URL); + + if (! $valid) { + return false; + } + + $host = parse_url($valid, PHP_URL_HOST); + + if (in_array($host, $localhosts)) { + return false; + } + + if (config('security.url.verify_dns')) { + if (DomainService::hasValidDns($host) === false) { + return false; + } + } + + if (app()->environment() === 'production') { + $bannedInstances = InstanceService::getBannedDomains(); + if (in_array($host, $bannedInstances)) { + return false; + } + } + + return $url; } } diff --git a/app/Services/DomainService.php b/app/Services/DomainService.php index 01f050ca0..a55cd1dcc 100644 --- a/app/Services/DomainService.php +++ b/app/Services/DomainService.php @@ -3,25 +3,30 @@ namespace App\Services; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Redis; class DomainService { - const CACHE_KEY = 'pf:services:domains:'; + const CACHE_KEY = 'pf:services:domains:'; public static function hasValidDns($domain) { - if(!$domain || !strlen($domain) || strpos($domain, '.') == -1) { + if (! $domain || ! strlen($domain) || strpos($domain, '.') == -1) { return false; } - if(config('security.url.trusted_domains')) { - if(in_array($domain, explode(',', config('security.url.trusted_domains')))) { + if (config('security.url.trusted_domains')) { + if (in_array($domain, explode(',', config('security.url.trusted_domains')))) { return true; } } - return Cache::remember(self::CACHE_KEY . 'valid-dns:' . $domain, 14400, function() use($domain) { + $valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME); + + if (! $valid) { + return false; + } + + return Cache::remember(self::CACHE_KEY.'valid-dns:'.$domain, 1800, function () use ($domain) { return count(dns_get_record($domain, DNS_A | DNS_AAAA)) > 0; }); }