Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Http/Response.php
<?php

namespace XF\Http;


use function
call_user_func, call_user_func_array, intval, is_array, is_string, strlen;

class
Response
{
    protected
$contentType = 'unknown/unknown';
    protected
$charset = 'utf-8';
    protected
$httpCode = 200;
    protected
$compressIfAble = true;
    protected
$includeContentLength = true;
    protected
$cookieConfig = [
       
'path' => '/',
       
'domain' => '',
       
'prefix' => '',
       
'secure' => false
   
];

    protected
$headers = [];
    protected
$cookies = [];
    protected
$body = '';
    protected
$compress = false;

    public function
setCookieConfig(array $config)
    {
       
$this->cookieConfig = array_merge($this->cookieConfig, $config);
    }

    public function
getCookiePath()
    {
        return
$this->cookieConfig['path'];
    }

    public function
getCookieDomain()
    {
        return
$this->cookieConfig['domain'];
    }

    public function
getCookiePrefix()
    {
        return
$this->cookieConfig['prefix'];
    }

    public function
contentType($contentType = null, $charset = null)
    {
        if (
$contentType === null)
        {
            return
$this->contentType;
        }

        if (!
preg_match('#^[a-zA-Z0-9]+/[a-zA-Z0-9-+]+$#', $contentType))
        {
            throw new \
InvalidArgumentException('Invalid content type');
        }
       
$this->contentType = $contentType;

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

        return
$this;
    }

    public function
charset($charset = null)
    {
        if (
$charset === null)
        {
            return
$this->charset;
        }

       
$this->charset = $charset;

        return
$this;
    }

    public function
httpCode($httpCode = null)
    {
        if (
$httpCode === null)
        {
            return
$this->httpCode;
        }

       
$this->httpCode = intval($httpCode);

        return
$this;
    }

    public function
redirect($url = null, $httpCode = null)
    {
        if (
$url === null)
        {
            return
$this->header('Location');
        }

       
$this->header('Location', $url);
       
$this->httpCode($httpCode);

        return
$this;
    }

    public function
header($name, $value = null, $overwrite = true)
    {
       
$name = $this->standardizeHeaderName($name);

        if (
$value === null)
        {
            return
$this->headers[$name] ?? false;
        }

        if (
$overwrite || !isset($this->headers[$name]))
        {
           
$this->headers[$name] = $value;
        }
        else
        {
           
$existingValue = $this->headers[$name];
            if (!
is_array($existingValue))
            {
               
$newValue = [$existingValue];
            } else
            {
               
$newValue = $existingValue;
            }

            if (
is_array($value))
            {
               
$newValue = array_merge($newValue, $value);
            } else
            {
               
$newValue[] = $value;
            }
           
$this->headers[$name] = $newValue;
        }

        return
$this;
    }

    public function
headerExists($name)
    {
       
$name = $this->standardizeHeaderName($name);

        return isset(
$this->headers[$name]);
    }

    public function
setDownloadFileName($fileName, $inline = false)
    {
       
$type = ($inline ? 'inline' : 'attachment');

       
$fileName = str_replace('"', '', $fileName);
        if (
preg_match('/[\x80-\xFF]/', $fileName))
        {
           
$altNamePart = "; filename*=UTF-8''" . rawurlencode($fileName);
        }
        else
        {
           
$altNamePart = '';
        }

       
$this->header('Content-Disposition',
           
$type . '; filename="' . str_replace('"', '', $fileName) . '"' . $altNamePart,
           
true
       
);

        return
$this;
    }

    protected function
isInlineDisplaySafe(string $extension, &$contentType = null): bool
   
{
        return (
            \
XF\Util\File::isImageInlineDisplaySafe($extension, $contentType)
            || \
XF\Util\File::isVideoInlineDisplaySafe($extension, $contentType)
            || \
XF\Util\File::isAudioInlineDisplaySafe($extension, $contentType)
        );
    }

    public function
setAttachmentFileParams($fileName, $extension = null)
    {
        if (
$extension === null)
        {
           
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
        }

       
$extension = strtolower($extension);

        if (
$this->isInlineDisplaySafe($extension, $contentType))
        {
           
$this->contentType($contentType, '')
                ->
setDownloadFileName($fileName, true);
        }
        else
        {
           
$this->contentType('application/octet-stream', '')
                ->
setDownloadFileName($fileName);
        }

        return
$this;
    }

    public function
removeHeader($name)
    {
       
$name = $this->standardizeHeaderName($name);
        unset(
$this->headers[$name]);

        return
$this;
    }

    public function
headers()
    {
        return
$this->headers;
    }

    public function
replaceHeaders(array $headers)
    {
       
$this->headers = $headers;

        return
$this;
    }

    protected function
standardizeHeaderName($name)
    {
       
$name = preg_replace('#\s+#', ' ', str_replace('-', ' ', trim($name)));
       
$name = str_replace(' ', '-', ucwords($name));
        return
$name;
    }

    public function
body($body = null)
    {
        if (
$body === null)
        {
            return
$this->body;
        }

       
$this->body = $body;

        return
$this;
    }

    public function
responseFile($fileName)
    {
        return new
ResponseFile($fileName);
    }

    public function
responseStream($resource, $length = null)
    {
        return new
ResponseStream($resource, $length);
    }

    public function
compressIfAble($compress = null)
    {
        if (
$compress === null)
        {
            return
$this->compressIfAble;
        }

       
$this->compressIfAble = (bool)$compress;

        return
$this;
    }

    public function
includeContentLength($use = null)
    {
        if (
$use === null)
        {
            return
$this->includeContentLength;
        }

       
$this->includeContentLength = (bool)$use;

        return
$this;
    }

    public function
send(Request $request = null)
    {
       
$this->prepareForOutput($request);
       
$this->sendHeaders();
       
$this->sendBody();
    }

    public function
prepareForOutput(Request $request = null)
    {
       
$this->compress = ($this->compressIfAble && $request && $this->contentIsCompressible($request));
        if (
$this->compress)
        {
           
$this->header('content-encoding', 'gzip');
           
$this->header('vary', 'Accept-Encoding');
        }

        if (
$this->header('Location') && ($this->httpCode < 300 || $this->httpCode >= 400))
        {
           
$this->httpCode = 302;
        }

        return
$this;
    }

    public function
getCookie($name, $addPrefix = false)
    {
        if (
$addPrefix)
        {
           
$name = $this->cookieConfig['prefix'] . $name;;
        }

        return
$this->cookies[$name] ?? [];
    }

    public function
getCookies()
    {
        return
$this->cookies;
    }

    public function
getCookiesExcept(array $skip, $addPrefix = false)
    {
       
$cookies = $this->cookies;
        foreach (
$skip AS $name)
        {
            if (
$addPrefix)
            {
               
$name = $this->cookieConfig['prefix'] . $name;;
            }
            unset(
$cookies[$name]);
        }

        return
$cookies;
    }

    public function
setCookie($name, $value, $lifetime = 0, $secure = null, $httpOnly = true, $sameSite = null)
    {
       
$cookieConfig = $this->cookieConfig;

       
$path = $cookieConfig['path'];
       
$domain = $cookieConfig['domain'];
       
$name = $cookieConfig['prefix'] . $name;

        if (
$secure === null)
        {
           
$secure = $cookieConfig['secure'];
        }

        return
$this->setCookieRaw($name, $value, $lifetime, $path, $domain, $secure, $httpOnly, $sameSite);
    }

    public function
setCookieRaw($name, $value = '', $lifetime = 0, $path = '/', $domain = '', $secure = false, $httpOnly = true, $sameSite = null)
    {
        if (
$value === false)
        {
           
$expire = \XF::$time - 86400 * 365;
           
$value = '';
        }
        else
        {
           
$expire = ($lifetime ? (\XF::$time + $lifetime) : 0);
        }

       
$this->cookies[$name] = [$name, $value, $expire, $path, $domain, $secure, $httpOnly, $sameSite];

        return
$this;
    }

    public function
removeCookie($name)
    {
        unset(
$this->cookies[$name]);

        return
$this;
    }

    public function
sendHeaders()
    {
        foreach (
$this->headers AS $key => $value)
        {
            if (
is_array($value))
            {
                foreach (
$value AS $innerValue)
                {
                   
header("$key: $innerValue", false);
                }
            } else
            {
               
header("$key: $value", false);
            }
        }

       
$sendCode = $this->httpCode;

        if (
$this->contentType)
        {
           
header('Content-Type: ' . $this->contentType
           
. ($this->charset ? '; charset=' . $this->charset : ''), true, $sendCode);
           
$sendCode = false;
        }

        if (
$sendCode)
        {
           
header('X-No-Headers: true', false, $sendCode);
        }

       
// TODO: we can set cookies as headers directly if lack of
        //  samesite support below PHP 7.3 becomes problematic
       
foreach ($this->cookies AS $cookie)
        {
            if (
PHP_VERSION_ID < 70300)
            {
               
array_pop($cookie); // no SameSite support here

               
call_user_func_array('setcookie', $cookie);
            }
            else
            {
               
$name = array_shift($cookie);
               
$value = array_shift($cookie);

               
$options = array_combine([
                   
'expires',
                   
'path',
                   
'domain',
                   
'secure',
                   
'httponly',
                   
'samesite'
               
], $cookie);

                if (empty(
$options['samesite']))
                {
                    unset(
$options['samesite']);
                }

               
call_user_func('setcookie', $name, $value, $options);
            }
        }
    }

    public function
sendBody()
    {
        if (
$this->body instanceof ResponseFile)
        {
            if (
$this->includeContentLength)
            {
               
header('Content-Length: ' . $this->body->getLength());
            }

           
$this->body->output();
        }
        else if (
$this->body instanceof ResponseStream)
        {
            if (
$this->includeContentLength)
            {
               
$length = $this->body->getLength();
                if (
$length !== null)
                {
                   
header('Content-Length: ' . $length);
                }
            }

           
$this->body->output();
        }
        else
        {
            if (
$this->compress)
            {
               
$toPrint = gzencode($this->body, 1);
            }
            else
            {
               
$toPrint = $this->body;
            }

            if (
$this->includeContentLength)
            {
               
header('Content-Length: ' . strlen($toPrint));
            }

            echo
$toPrint;
        }
    }

    public function
contentIsCompressible(Request $request)
    {
        if (
            !
is_string($this->body)
            || !
preg_match('#^(?:text/|application/(?:json|xml|rss\+xml)$)#i', $this->contentType)
            ||
strpos($request->getServer('HTTP_ACCEPT_ENCODING', ''), 'gzip') === false
       
)
        {
            return
false;
        }

        if (!
function_exists('gzencode'))
        {
            return
false;
        }

        return (
strlen($this->body) >= 20);
    }
}