<?php
/**
 * @package dompdf
 * @link    https://github.com/dompdf/dompdf
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 */
namespace Dompdf;

class Options
{
    /**
     * The root of your DOMPDF installation
     *
     * @var string
     */
    private $rootDir;

    /**
     * The location of a temporary directory.
     *
     * The directory specified must be writable by the executing process.
     * The temporary directory is required to download remote images and when
     * using the PFDLib back end.
     *
     * @var string
     */
    private $tempDir;

    /**
     * The location of the DOMPDF font directory
     *
     * The location of the directory where DOMPDF will store fonts and font metrics
     * Note: This directory must exist and be writable by the executing process.
     *
     * @var string
     */
    private $fontDir;

    /**
     * The location of the DOMPDF font cache directory
     *
     * This directory contains the cached font metrics for the fonts used by DOMPDF.
     * This directory can be the same as $fontDir
     *
     * Note: This directory must exist and be writable by the executing process.
     *
     * @var string
     */
    private $fontCache;

    /**
     * dompdf's "chroot"
     *
     * Utilized by Dompdf's default file:// protocol URI validation rule.
     * All local files opened by dompdf must be in a subdirectory of the directory
     * or directories specified by this option.
     * DO NOT set this value to '/' since this could allow an attacker to use dompdf to
     * read any files on the server.  This should be an absolute path.
     *
     * ==== IMPORTANT ====
     * This setting may increase the risk of system exploit. Do not change
     * this settings without understanding the consequences. Additional
     * documentation is available on the dompdf wiki at:
     * https://github.com/dompdf/dompdf/wiki
     *
     * @var array
     */
    private $chroot;

    /**
    * Protocol whitelist
    *
    * Protocols and PHP wrappers allowed in URIs, and the validation rules
    * that determine if a resource may be loaded. Full support is not guaranteed
    * for the protocols/wrappers specified
    * by this array.
    *
    * @var array
    */
    private $allowedProtocols = [
        "data://" => ["rules" => []],
        "file://" => ["rules" => []],
        "http://" => ["rules" => []],
        "https://" => ["rules" => []]
    ];

    /**
    * Operational artifact (log files, temporary files) path validation
    *
    * @var callable
    */
    private $artifactPathValidation = null;

    /**
     * @var string
     */
    private $logOutputFile = '';

    /**
     * Styles targeted to this media type are applied to the document.
     * This is on top of the media types that are always applied:
     *    all, static, visual, bitmap, paged, dompdf
     *
     * @var string
     */
    private $defaultMediaType = "screen";

    /**
     * The default paper size.
     *
     * North America standard is "letter"; other countries generally "a4"
     * @see \Dompdf\Adapter\CPDF::PAPER_SIZES for valid sizes
     *
     * @var string|float[]
     */
    private $defaultPaperSize = "letter";

    /**
     * The default paper orientation.
     *
     * The orientation of the page (portrait or landscape).
     *
     * @var string
     */
    private $defaultPaperOrientation = "portrait";

    /**
     * The default font family
     *
     * Used if no suitable fonts can be found. This must exist in the font folder.
     *
     * @var string
     */
    private $defaultFont = "serif";

    /**
     * Image DPI setting
     *
     * This setting determines the default DPI setting for images and fonts.  The
     * DPI may be overridden for inline images by explicitly setting the
     * image's width & height style attributes (i.e. if the image's native
     * width is 600 pixels and you specify the image's width as 72 points,
     * the image will have a DPI of 600 in the rendered PDF.  The DPI of
     * background images can not be overridden and is controlled entirely
     * via this parameter.
     *
     * For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
     * If a size in html is given as px (or without unit as image size),
     * this tells the corresponding size in pt at 72 DPI.
     * This adjusts the relative sizes to be similar to the rendering of the
     * html page in a reference browser.
     *
     * In pdf, always 1 pt = 1/72 inch
     *
     * @var int
     */
    private $dpi = 96;

    /**
     * A ratio applied to the fonts height to be more like browsers' line height
     *
     * @var float
     */
    private $fontHeightRatio = 1.1;

    /**
     * The maximum estimated in-memory size of an image allowed to be rendered, in bytes.
     *
     * @var int
     */
    private $imageByteSizeLimit = -1;

    /**
     * Enable embedded PHP
     *
     * If this setting is set to true then DOMPDF will automatically evaluate
     * embedded PHP contained within <script type="text/php"> ... </script> tags.
     *
     * ==== IMPORTANT ====
     * Enabling this for documents you do not trust (e.g. arbitrary remote html
     * pages) is a security risk. Embedded scripts are run with the same level of
     * system access available to dompdf. Set this option to false (recommended)
     * if you wish to process untrusted documents.
     *
     * This setting may increase the risk of system exploit. Do not change
     * this settings without understanding the consequences. Additional
     * documentation is available on the dompdf wiki at:
     * https://github.com/dompdf/dompdf/wiki
     *
     * @var bool
     */
    private $isPhpEnabled = false;

    /**
     * Enable remote file access
     *
     * If this setting is set to true, DOMPDF will access remote sites for
     * images and CSS files as required.
     *
     * ==== IMPORTANT ====
     * This can be a security risk, in particular in combination with isPhpEnabled and
     * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
     * This allows anonymous users to download legally doubtful internet content which on
     * tracing back appears to being downloaded by your server, or allows malicious php code
     * in remote html pages to be executed by your server with your account privileges.
     *
     * This setting may increase the risk of system exploit. Do not change
     * this settings without understanding the consequences. Additional
     * documentation is available on the dompdf wiki at:
     * https://github.com/dompdf/dompdf/wiki
     *
     * @var bool
     */
    private $isRemoteEnabled = false;

    /**
     * List of allowed remote hosts
     *
     * Each value of the array must be a valid hostname.
     *
     * This will be used to filter which resources can be loaded in combination with
     * isRemoteEnabled. If isRemoteEnabled is FALSE, then this will have no effect.
     *
     * Leave to NULL to allow any remote host.
     *
     * @var array|null
     */
    private $allowedRemoteHosts = null;

    /**
     * Enable PDF/A-3 compliance mode
     *
     * ==== EXPERIMENTAL ====
     * This feature is currently only supported with the CPDF backend and will
     * have no effect if used with any other.
     *
     * Currently this mode only takes care of adding the necessary metadata, output intents, etc.
     * It does not enforce font embedding, it's up to you to embed the fonts you plan on using.
     *
     * @var bool
     */
    private $isPdfAEnabled = false;

    /**
     * Enable inline JavaScript
     *
     * If this setting is set to true then DOMPDF will automatically insert
     * JavaScript code contained within <script type="text/javascript"> ... </script>
     * tags as written into the PDF.
     *
     * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer,
     * not browser-based JavaScript executed by Dompdf.
     *
     * @var bool
     */
    private $isJavascriptEnabled = true;

    /**
     * Use the HTML5 Lib parser
     *
     * @deprecated
     * @var bool
     */
    private $isHtml5ParserEnabled = true;

    /**
     * Whether to enable font subsetting or not.
     *
     * @var bool
     */
    private $isFontSubsettingEnabled = true;

    /**
     * @var bool
     */
    private $debugPng = false;

    /**
     * @var bool
     */
    private $debugKeepTemp = false;

    /**
     * @var bool
     */
    private $debugCss = false;

    /**
     * @var bool
     */
    private $debugLayout = false;

    /**
     * @var bool
     */
    private $debugLayoutLines = true;

    /**
     * @var bool
     */
    private $debugLayoutBlocks = true;

    /**
     * @var bool
     */
    private $debugLayoutInline = true;

    /**
     * @var bool
     */
    private $debugLayoutPaddingBox = true;

    /**
     * The PDF rendering backend to use
     *
     * Valid settings are 'PDFLib', 'CPDF', 'GD', and 'auto'. 'auto' will
     * look for PDFLib and use it if found, or if not it will fall back on
     * CPDF. 'GD' renders PDFs to graphic files. {@link Dompdf\CanvasFactory}
     * ultimately determines which rendering class to instantiate
     * based on this setting.
     *
     * @var string
     */
    private $pdfBackend = "CPDF";

    /**
     * PDFlib license key
     *
     * If you are using a licensed, commercial version of PDFlib, specify
     * your license key here.  If you are using PDFlib-Lite or are evaluating
     * the commercial version of PDFlib, comment out this setting.
     *
     * @link http://www.pdflib.com
     *
     * If pdflib present in web server and auto or selected explicitly above,
     * a real license code must exist!
     *
     * @var string
     */
    private $pdflibLicense = "";

    /**
     * HTTP context created with stream_context_create()
     * Will be used for file_get_contents
     *
     * @link https://www.php.net/manual/context.php
     *
     * @var resource
     */
    private $httpContext;

    /**
     * @param array $attributes
     */
    public function __construct(?array $attributes = null)
    {
        $rootDir = realpath(__DIR__ . "/../");
        $this->setChroot(array($rootDir));
        $this->setRootDir($rootDir);
        $this->setTempDir(sys_get_temp_dir());
        $this->setFontDir($rootDir . "/lib/fonts");
        $this->setFontCache($this->getFontDir());

        $ver = "";
        $versionFile = realpath(__DIR__ . '/../VERSION');
        if (file_exists($versionFile) && ($version = file_get_contents($versionFile)) !== false) {
            $version = trim($version);
            if ($version !== '$Format:<%h>$') {
                $ver = "/$version";
            }
        }
        $this->setHttpContext([
            "http" => [
                "follow_location" => false,
                "user_agent" => "Dompdf$ver https://github.com/dompdf/dompdf"
            ]
        ]);

        $this->setAllowedProtocols(["data://", "file://", "http://", "https://"]);

        $this->setArtifactPathValidation([$this, "validateArtifactPath"]);

        // Get the memory limit
        $memory_limit = ini_get('memory_limit');
        $this->setImageByteSizeLimit($memory_limit);

        if (null !== $attributes) {
            $this->set($attributes);
        }
    }

    /**
     * @param array|string $attributes
     * @param null|mixed $value
     * @return $this
     */
    public function set($attributes, $value = null)
    {
        if (!is_array($attributes)) {
            $attributes = [$attributes => $value];
        }

        foreach ($attributes as $key => $value) {
            $methodForMatch = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
            $methodForCall = "set" . ucfirst($methodForMatch);

            if ($methodForMatch === 'enablePhp') {
                $methodForCall = "setIsPhpEnabled";
            } elseif ($methodForMatch === 'enableRemote') {
                $methodForCall = "setIsRemoteEnabled";
            } elseif ($methodForMatch === 'enablePdfA') {
                $methodForCall = "setIsPdfAEnabled";
            } elseif ($methodForMatch === 'enableJavascript') {
                $methodForCall = "setIsJavascriptEnabled";
            } elseif ($methodForMatch === 'enableHtml5Parser') {
                $methodForCall = "setIsHtml5ParserEnabled";
            } elseif ($methodForMatch === 'enableFontSubsetting') {
                $methodForCall = "setIsFontSubsettingEnabled";
            }

            if (method_exists($this, $methodForCall)) {
                $this->{$methodForCall}($value);
            }
        }

        return $this;
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function get($key)
    {
        $methodForMatch = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
        $methodForCall = "get" . ucfirst($methodForMatch);
        
        if ($methodForMatch === 'enablePhp') {
            $methodForCall = "getIsPhpEnabled";
        } elseif ($methodForMatch === 'enableRemote') {
            $methodForCall = "getIsRemoteEnabled";
        } elseif ($methodForMatch === 'enablePdfA') {
            $methodForCall = "getIsPdfAEnabled";
        } elseif ($methodForMatch === 'enableJavascript') {
            $methodForCall = "getIsJavascriptEnabled";
        } elseif ($methodForMatch === 'enableHtml5Parser') {
            $methodForCall = "getIsHtml5ParserEnabled";
        } elseif ($methodForMatch === 'enableFontSubsetting') {
            $methodForCall = "getIsFontSubsettingEnabled";
        }

        if (method_exists($this, $methodForCall)) {
            return $this->{$methodForCall}();
        }
        
        return null;
    }

    /**
     * @param string $pdfBackend
     * @return $this
     */
    public function setPdfBackend($pdfBackend)
    {
        $this->pdfBackend = $pdfBackend;
        return $this;
    }

    /**
     * @return string
     */
    public function getPdfBackend()
    {
        return $this->pdfBackend;
    }

    /**
     * @param string $pdflibLicense
     * @return $this
     */
    public function setPdflibLicense($pdflibLicense)
    {
        $this->pdflibLicense = $pdflibLicense;
        return $this;
    }

    /**
     * @return string
     */
    public function getPdflibLicense()
    {
        return $this->pdflibLicense;
    }

    /**
     * @param array|string $chroot
     * @return $this
     */
    public function setChroot($chroot, $delimiter = ',')
    {
        if (is_string($chroot)) {
            $this->chroot = explode($delimiter, $chroot);
        } elseif (is_array($chroot)) {
            $this->chroot = $chroot;
        }
        return $this;
    }

    /**
     * @return array
     */
    public function getAllowedProtocols()
    {
        return $this->allowedProtocols;
    }

    /**
     * @param array $allowedProtocols The protocols to allow, as an array
     * formatted as ["protocol://" => ["rules" => [callable]], ...]
     * or ["protocol://", ...]
     *
     * @return $this
     */
    public function setAllowedProtocols(array $allowedProtocols)
    {
        $protocols = [];
        foreach ($allowedProtocols as $protocol => $config) {
            if (is_string($protocol)) {
                $protocols[$protocol] = [];
                if (is_array($config)) {
                    $protocols[$protocol] = $config;
                }
            } elseif (is_string($config)) {
                $protocols[$config] = [];
            }
        }
        $this->allowedProtocols = [];
        foreach ($protocols as $protocol => $config) {
            $this->addAllowedProtocol($protocol, ...($config["rules"] ?? []));
        }
        return $this;
    }

    /**
     * Adds a new protocol to the allowed protocols collection
     *
     * @param string $protocol The scheme to add (e.g. "http://")
     * @param callable $rule A callable that validates the protocol
     * @return $this
     */
    public function addAllowedProtocol(string $protocol, callable ...$rules)
    {
        $protocol = strtolower($protocol);
        if (empty($rules)) {
            $rules = [];
            switch ($protocol) {
                case "data://":
                    break;
                case "file://":
                    $rules[] = [$this, "validateLocalUri"];
                    break;
                case "http://":
                case "https://":
                    $rules[] = [$this, "validateRemoteUri"];
                    break;
                case "phar://":
                    $rules[] = [$this, "validatePharUri"];
                    break;
            }
        }
        $this->allowedProtocols[$protocol] = ["rules" => $rules];
        return $this;
    }

    /**
     * @return array
     */
    public function getArtifactPathValidation()
    {
        return $this->artifactPathValidation;
    }

    /**
     * @param callable $validator
     * @return $this
     */
    public function setArtifactPathValidation($validator)
    {
        $this->artifactPathValidation = $validator;
        return $this;
    }

    /**
     * @return array
     */
    public function getChroot()
    {
        $chroot = [];
        if (is_array($this->chroot)) {
            $chroot = $this->chroot;
        }
        return $chroot;
    }

    /**
     * @param boolean $debugCss
     * @return $this
     */
    public function setDebugCss($debugCss)
    {
        $this->debugCss = $debugCss;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugCss()
    {
        return $this->debugCss;
    }

    /**
     * @param boolean $debugKeepTemp
     * @return $this
     */
    public function setDebugKeepTemp($debugKeepTemp)
    {
        $this->debugKeepTemp = $debugKeepTemp;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugKeepTemp()
    {
        return $this->debugKeepTemp;
    }

    /**
     * @param boolean $debugLayout
     * @return $this
     */
    public function setDebugLayout($debugLayout)
    {
        $this->debugLayout = $debugLayout;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugLayout()
    {
        return $this->debugLayout;
    }

    /**
     * @param boolean $debugLayoutBlocks
     * @return $this
     */
    public function setDebugLayoutBlocks($debugLayoutBlocks)
    {
        $this->debugLayoutBlocks = $debugLayoutBlocks;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugLayoutBlocks()
    {
        return $this->debugLayoutBlocks;
    }

    /**
     * @param boolean $debugLayoutInline
     * @return $this
     */
    public function setDebugLayoutInline($debugLayoutInline)
    {
        $this->debugLayoutInline = $debugLayoutInline;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugLayoutInline()
    {
        return $this->debugLayoutInline;
    }

    /**
     * @param boolean $debugLayoutLines
     * @return $this
     */
    public function setDebugLayoutLines($debugLayoutLines)
    {
        $this->debugLayoutLines = $debugLayoutLines;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugLayoutLines()
    {
        return $this->debugLayoutLines;
    }

    /**
     * @param boolean $debugLayoutPaddingBox
     * @return $this
     */
    public function setDebugLayoutPaddingBox($debugLayoutPaddingBox)
    {
        $this->debugLayoutPaddingBox = $debugLayoutPaddingBox;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugLayoutPaddingBox()
    {
        return $this->debugLayoutPaddingBox;
    }

    /**
     * @param boolean $debugPng
     * @return $this
     */
    public function setDebugPng($debugPng)
    {
        $this->debugPng = $debugPng;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getDebugPng()
    {
        return $this->debugPng;
    }

    /**
     * @param string $defaultFont
     * @return $this
     */
    public function setDefaultFont($defaultFont)
    {
        if (!($defaultFont === null || trim($defaultFont) === "")) {
            $this->defaultFont = $defaultFont;
        } else {
            $this->defaultFont = "serif";
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getDefaultFont()
    {
        return $this->defaultFont;
    }

    /**
     * @param string $defaultMediaType
     * @return $this
     */
    public function setDefaultMediaType($defaultMediaType)
    {
        $this->defaultMediaType = $defaultMediaType;
        return $this;
    }

    /**
     * @return string
     */
    public function getDefaultMediaType()
    {
        return $this->defaultMediaType;
    }

    /**
     * @param string|float[] $defaultPaperSize
     * @return $this
     */
    public function setDefaultPaperSize($defaultPaperSize): self
    {
        $this->defaultPaperSize = $defaultPaperSize;
        return $this;
    }

    /**
     * @param string $defaultPaperOrientation
     * @return $this
     */
    public function setDefaultPaperOrientation(string $defaultPaperOrientation): self
    {
        $this->defaultPaperOrientation = $defaultPaperOrientation;
        return $this;
    }

    /**
     * @return string|float[]
     */
    public function getDefaultPaperSize()
    {
        return $this->defaultPaperSize;
    }

    /**
     * @return string
     */
    public function getDefaultPaperOrientation(): string
    {
        return $this->defaultPaperOrientation;
    }

    /**
     * @param int $dpi
     * @return $this
     */
    public function setDpi($dpi)
    {
        $this->dpi = $dpi;
        return $this;
    }

    /**
     * @return int
     */
    public function getDpi()
    {
        return $this->dpi;
    }

    /**
     * @param string $fontCache
     * @return $this
     */
    public function setFontCache($fontCache)
    {
        if (!is_callable($this->artifactPathValidation) || ($this->artifactPathValidation)($fontCache, "fontCache") === true) {
            $this->fontCache = $fontCache;
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getFontCache()
    {
        return $this->fontCache;
    }

    /**
     * @param string $fontDir
     * @return $this
     */
    public function setFontDir($fontDir)
    {
        if (!is_callable($this->artifactPathValidation) || ($this->artifactPathValidation)($fontDir, "fontDir") === true) {
            $this->fontDir = $fontDir;
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getFontDir()
    {
        return $this->fontDir;
    }

    /**
     * @param float $fontHeightRatio
     * @return $this
     */
    public function setFontHeightRatio($fontHeightRatio)
    {
        $this->fontHeightRatio = $fontHeightRatio;
        return $this;
    }

    /**
     * @return float
     */
    public function getFontHeightRatio()
    {
        return $this->fontHeightRatio;
    }

    /**
     * @param int $imageMemoryLimit
     * @return $this
     */
    public function setImageByteSizeLimit($imageByteSizeLimit)
    {
        // Convert shorthand notation to bytes
        if (preg_match('/^(\\d+)([KMG])$/i', $imageByteSizeLimit, $matches)) {
            if (strtoupper($matches[2]) == 'K') {
                $imageByteSizeLimit = (int)$matches[1] * 1024;
            } elseif (strtoupper($matches[2]) == 'M') {
                $imageByteSizeLimit = (int)$matches[1] * 1024 * 1024;
            } elseif (strtoupper($matches[2]) == 'G') {
                $imageByteSizeLimit = (int)$matches[1] * 1024 * 1024 * 1024;
            }
        }
        if (is_numeric($imageByteSizeLimit)) {
            $this->imageByteSizeLimit = $imageByteSizeLimit > 0 ? $imageByteSizeLimit : -1;
        }
        return $this;
    }

    /**
     * @return int
     */
    public function getImageByteSizeLimit()
    {
        return $this->imageByteSizeLimit;
    }

    /**
     * @param boolean $isFontSubsettingEnabled
     * @return $this
     */
    public function setIsFontSubsettingEnabled($isFontSubsettingEnabled)
    {
        $this->isFontSubsettingEnabled = $isFontSubsettingEnabled;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getIsFontSubsettingEnabled()
    {
        return $this->isFontSubsettingEnabled;
    }

    /**
     * @return boolean
     */
    public function isFontSubsettingEnabled()
    {
        return $this->getIsFontSubsettingEnabled();
    }

    /**
     * @deprecated
     * @param boolean $isHtml5ParserEnabled
     * @return $this
     */
    public function setIsHtml5ParserEnabled($isHtml5ParserEnabled)
    {
        $this->isHtml5ParserEnabled = $isHtml5ParserEnabled;
        return $this;
    }

    /**
     * @deprecated
     * @return boolean
     */
    public function getIsHtml5ParserEnabled()
    {
        return $this->isHtml5ParserEnabled;
    }

    /**
     * @deprecated
     * @return boolean
     */
    public function isHtml5ParserEnabled()
    {
        return $this->getIsHtml5ParserEnabled();
    }

    /**
     * @param boolean $isJavascriptEnabled
     * @return $this
     */
    public function setIsJavascriptEnabled($isJavascriptEnabled)
    {
        $this->isJavascriptEnabled = $isJavascriptEnabled;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getIsJavascriptEnabled()
    {
        return $this->isJavascriptEnabled;
    }

    /**
     * @return boolean
     */
    public function isJavascriptEnabled()
    {
        return $this->getIsJavascriptEnabled();
    }

    /**
     * @param boolean $isPhpEnabled
     * @return $this
     */
    public function setIsPhpEnabled($isPhpEnabled)
    {
        $this->isPhpEnabled = $isPhpEnabled;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getIsPhpEnabled()
    {
        return $this->isPhpEnabled;
    }

    /**
     * @return boolean
     */
    public function isPhpEnabled()
    {
        return $this->getIsPhpEnabled();
    }

    /**
     * @param boolean $isRemoteEnabled
     * @return $this
     */
    public function setIsRemoteEnabled($isRemoteEnabled)
    {
        $this->isRemoteEnabled = $isRemoteEnabled;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getIsRemoteEnabled()
    {
        return $this->isRemoteEnabled;
    }

    /**
     * @return boolean
     */
    public function isRemoteEnabled()
    {
        return $this->getIsRemoteEnabled();
    }

    /**
     * @param array|null $allowedRemoteHosts
     * @return $this
     */
    public function setAllowedRemoteHosts($allowedRemoteHosts)
    {
        if (is_array($allowedRemoteHosts)) {
            // Set hosts to lowercase
            foreach ($allowedRemoteHosts as &$host) {
                $host = mb_strtolower($host, "UTF-8");
            }

            unset($host);
        }

        $this->allowedRemoteHosts = $allowedRemoteHosts;
        return $this;
    }

    /**
     * @return array|null
     */
    public function getAllowedRemoteHosts()
    {
        return $this->allowedRemoteHosts;
    }

    /**
     * @param boolean $isRemoteEnabled
     * @return $this
     */
    public function setIsPdfAEnabled($isPdfAEnabled)
    {
        $this->isPdfAEnabled = $isPdfAEnabled;
        return $this;
    }

    /**
     * @return boolean
     */
    public function getIsPdfAEnabled()
    {
        return $this->isPdfAEnabled;
    }

    /**
     * @return boolean
     */
    public function isPdfAEnabled()
    {
        return $this->getIsPdfAEnabled();
    }

    /**
     * @param string $logOutputFile
     * @return $this
     */
    public function setLogOutputFile($logOutputFile)
    {
        if (!is_callable($this->artifactPathValidation) || ($this->artifactPathValidation)($logOutputFile, "logOutputFile") === true) {
            $this->logOutputFile = $logOutputFile;
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getLogOutputFile()
    {
        return $this->logOutputFile;
    }

    /**
     * @param string $tempDir
     * @return $this
     */
    public function setTempDir($tempDir)
    {
        if (!is_callable($this->artifactPathValidation) || ($this->artifactPathValidation)($tempDir, "tempDir") === true) {
            $this->tempDir = $tempDir;
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getTempDir()
    {
        return $this->tempDir;
    }

    /**
     * @param string $rootDir
     * @return $this
     */
    public function setRootDir($rootDir)
    {
        if (!is_callable($this->artifactPathValidation) || ($this->artifactPathValidation)($rootDir, "rootDir") === true) {
            $this->rootDir = $rootDir;
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getRootDir()
    {
        return $this->rootDir;
    }

    /**
     * Sets the HTTP context
     *
     * @param resource|array $httpContext
     * @return $this
     */
    public function setHttpContext($httpContext)
    {
        $this->httpContext = is_array($httpContext) ? stream_context_create($httpContext) : $httpContext;
        return $this;
    }

    /**
     * Returns the HTTP context
     *
     * @return resource
     */
    public function getHttpContext()
    {
        return $this->httpContext;
    }


    public function validateArtifactPath(?string $path, string $option)
    {
        if ($path === null) {
            return true;
        }
        $parsed_uri = parse_url($path);
        if ($parsed_uri === false || (array_key_exists("scheme", $parsed_uri) && strtolower($parsed_uri["scheme"]) === "phar")) {
            return false;
        }
        return true;
    }

    public function validateLocalUri(string $uri)
    {
        if ($uri === null || strlen($uri) === 0) {
            return [false, "The URI must not be empty."];
        }

        $realfile = realpath(str_replace("file://", "", $uri));
        if ($realfile === false) {
            return [false, "File not found."];
        }

        $dirs = $this->chroot;
        $dirs[] = $this->rootDir;
        $chrootValid = false;
        foreach ($dirs as $chrootPath) {
            $chrootPath = realpath($chrootPath);
            if ($chrootPath === false) {
                continue;
            }

            $normalizedChrootPath = rtrim($chrootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
            if ($realfile === $chrootPath || strncmp($realfile, $normalizedChrootPath, strlen($normalizedChrootPath)) === 0) {
                $chrootValid = true;
                break;
            }
        }
        if ($chrootValid !== true) {
            return [false, "Permission denied. The file could not be found under the paths specified by Options::chroot."];
        }

        return [true, null];
    }

    public function validatePharUri(string $uri)
    {
        if ($uri === null || strlen($uri) === 0) {
            return [false, "The URI must not be empty."];
        }

        $file = substr(substr($uri, 0, strpos($uri, ".phar") + 5), 7);
        return $this->validateLocalUri($file);
    }

    public function validateRemoteUri(string $uri)
    {
        if ($uri === null || strlen($uri) === 0) {
            return [false, "The URI must not be empty."];
        }

        if (!$this->isRemoteEnabled) {
            return [false, "Remote file requested, but remote file download is disabled."];
        }

        if (is_array($this->allowedRemoteHosts) && count($this->allowedRemoteHosts) > 0) {
            $host = parse_url($uri, PHP_URL_HOST);
            $host = mb_strtolower($host, "UTF-8");

            if (!in_array($host, $this->allowedRemoteHosts, true)) {
                return [false, "Remote host is not in allowed list: " . $host];
            }
        }

        return [true, null];
    }
}
