<?php 
namespace ParagonIE\Certainty; 
 
use GuzzleHttp\Client; 
use ParagonIE\Certainty\Exception\EncodingException; 
use ParagonIE\Certainty\Exception\NetworkException; 
 
/** 
 * Class RemoteFetch 
 * 
 * Fetches data over the network. Caches locally. 
 * 
 * @package ParagonIE\Certainty 
 */ 
class RemoteFetch extends Fetch 
{ 
    const CHECK_SIGNATURE_BY_DEFAULT = true; 
    const CHECK_CHRONICLE_BY_DEFAULT = true; 
    const DEFAULT_URL = 'https://raw.githubusercontent.com/paragonie/certainty/master/data/'; 
 
    /** 
     * @var \DateInterval $cacheTimeout 
     */ 
    protected $cacheTimeout; 
 
    /** 
     * @var Client $http 
     */ 
    protected $http; 
 
    /** 
     * @var string $url 
     */ 
    protected $url = ''; 
 
    /** 
     * RemoteFetch constructor. 
     * 
     * @param string $dataDir 
     * @param string $url 
     * @param Client|null $http 
     * @param \DateInterval|string|null $timeout 
     * @throws \TypeError 
     */ 
    public function __construct( 
        $dataDir = '', 
        $url = self::DEFAULT_URL, 
        Client $http = null, 
        $timeout = null 
    ) { 
        parent::__construct($dataDir); 
        $this->url = $url; 
 
        if (\is_null($http)) { 
            if (\file_exists($this->dataDirectory . '/ca-certs.json')) { 
                $http = Certainty::getGuzzleClient(new Fetch($this->dataDirectory)); 
            } else { 
                $http = new Client(); 
            } 
        } 
        /** @var Client $http */ 
        $this->http = $http; 
 
        if (\is_null($timeout)) { 
            /* Default: 24 hours */ 
            $timeoutObj = new \DateInterval('P01D'); 
        } elseif (\is_string($timeout)) { 
            $timeoutObj = new \DateInterval($timeout); 
        } elseif ($timeout instanceof \DateInterval) { 
            $timeoutObj = $timeout; 
        } else { 
            throw new \TypeError('Invalid timeout. Expected a DateInterval or string.'); 
        } 
        /** @var \DateInterval $timeoutObj */ 
        $this->cacheTimeout = $timeoutObj; 
    } 
 
    /** 
     * Do we need to fetch updates? 
     * 
     * @return bool 
     */ 
    public function cacheExpired() 
    { 
        if (!\file_exists($this->dataDirectory . '/ca-certs.cache')) { 
            return true; 
        } 
        /** @var string $cacheTime */ 
        $cacheTime = \file_get_contents($this->dataDirectory . '/ca-certs.cache'); 
        if (!\is_string($cacheTime)) { 
            return true; 
        } 
        $expires = (new \DateTime($cacheTime))->add($this->cacheTimeout); 
        return $expires <= new \DateTime('NOW'); 
    } 
 
    /** 
     * List bundles 
     * 
     * @param string $customValidator 
     * @return array<int, Bundle> 
     * @throws NetworkException 
     */ 
    protected function listBundles($customValidator = '') 
    { 
        if ($this->cacheExpired()) { 
            if (!$this->remoteFetchBundles()) { 
                throw new NetworkException('Could not download bundles'); 
            } 
        } 
        return parent::listBundles($customValidator); 
    } 
 
    /** 
     * This handles the actual HTTP request. 
     * 
     * @return bool 
     * @throws EncodingException 
     */ 
    protected function remoteFetchBundles() 
    { 
        $request = $this->http->get($this->url . '/ca-certs.json'); 
        $body = (string) $request->getBody(); 
        $jsonDecoded = \json_decode($body, true); 
        if (!\is_array($jsonDecoded)) { 
            throw new EncodingException(\json_last_error_msg()); 
        } 
 
        if (\file_exists($this->dataDirectory . '/ca-certs.json')) { 
            \rename( 
                $this->dataDirectory . '/ca-certs.json', 
                $this->dataDirectory . '/ca-certs-backup-' . \date('YmdHis') . '.json' 
            ); 
        } 
        \file_put_contents($this->dataDirectory . '/ca-certs.json', $body); 
 
        foreach ($jsonDecoded as $item) { 
            if (!isset($item['file'])) { 
                continue; 
            } 
            $filename = $item['file']; 
            if (!\preg_match('#^cacert(\-[0-9]{4}\-[0-9]{2}\-[0-9]{2})?\.pem$#', $filename)) { 
                // Invalid filename 
                continue; 
            } 
            if (!\file_exists($this->dataDirectory . '/' . $filename)) { 
                $request = $this->http->get($this->url . '/' . $filename); 
                $body = (string) $request->getBody(); 
                \file_put_contents($this->dataDirectory . '/' . $filename, $body); 
            } 
        } 
 
        return !\is_bool( 
            \file_put_contents( 
                $this->dataDirectory . '/ca-certs.cache', 
                (new \DateTime())->format(\DateTime::ATOM) 
            ) 
        ); 
    } 
 
    /** 
     * @param \DateInterval $interval 
     * @return self 
     */ 
    public function setCacheTimeout(\DateInterval $interval) 
    { 
        $this->cacheTimeout = $interval; 
        return $this; 
    } 
 
    /** 
     * Replace the HTTP client with a new one. 
     * 
     * @param Client $client 
     * @return $this 
     */ 
    public function setHttpClient(Client $client) 
    { 
        $this->http = $client; 
        return $this; 
    } 
 
    /** 
     * @param string $url 
     * @return self 
     */ 
    public function setRemoteSource($url = '') 
    { 
        $this->url = $url; 
        return $this; 
    } 
} 
 
 |