<?php
/*
 * html2canvas-php-proxy 1.0.0
 *
 * Copyright (c) 2018 Guilherme Nascimento (brcontainer@yahoo.com.br)
 *
 * Released under the MIT license
 */

// Turn off errors because the script already own uses "error_get_last"
// ini_set('display_errors', 'Off');

// Setup

/*
If execution has reached the time limit prevents page goes blank (off errors)
or generate an error in PHP, which does not work with the DEBUG (from html2canvas.js)
*/

class sfBugTrackerProxy
{
  protected
    $parameters = array(),
    $http_port = 0,
    $tmp = null, //tmp var usage
    $response = array();

  /**
   * Class constructor.
   *
   * @see initialize()
   */
    public function __construct($parameters = array())
    {
        $this->initialize($parameters);
    }

  /**
   * Initializes this Filter.
   *
   * @param sfContext $context    The current application context
   * @param array     $parameters An associative array of initialization parameters
   *
   * @return boolean true
   */
    public function initialize($parameters = array(), $options = array())
    {
        $maxExec = (int) ini_get('max_execution_time');
        $this->parameters = array_merge(array(
            'H2CP_PATH' => sfConfig::get('sf_cache_dir'),                   // Relative folder where the images are saved
            'H2CP_PERMISSION' => 0666,                // use 644 or 666 for remove execution for prevent sploits
            'H2CP_CACHE' => 60 * 5 * 1000,            // Limit access-control and cache, define 0/false/null/-1 to prevent cache
            'H2CP_TIMEOUT' => 20,                     // Timeout from load Socket
            'H2CP_MAX_LOOP' => 10,                    // Configure loop limit for redirects (location header)
            'H2CP_DATAURI' => false,                  // Enable use of "data URI scheme"
            'H2CP_PREFER_CURL' => true,               // Enable curl if avaliable or disable
            'H2CP_SECPREFIX' => 'h2cp_',              // Prefix temp filename
            'H2CP_ALLOWED_DOMAINS' => '*',   // * allow all domains, *.site.com for sub-domains, or fixed domains use array( 'site.com', 'www.site.com' )
            'H2CP_ALLOWED_PORTS' => '80, 443' , // Allowed ports

                /*
                 * Set false for disable SSL check
                 * Set true for enable SSL check, require config `curl.cainfo=/path/to/cacert.pem` in php.ini
                 * Set path (string) if need config CAINFO manually like this define('H2CP_SSL_VERIFY_PEER', '/path/to/cacert.pem');
                 */
            'H2CP_SSL_VERIFY_PEER' => false,

                // Constants (don't change)
            'H2CP_EOL' => chr(10),
            'H2CP_WOL' => chr(13),
            'H2CP_GMDATECACHE' => gmdate('D, d M Y H:i:s'),
            'H2CP_INIT_EXEC' => time(),
            'H2CP_JSONP' => empty($parameters['callback']) ? false : $parameters['callback'],
            'H2CP_MAX_EXEC' => $maxExec < 1 ? 0 : ($maxExec - 5),//reduces 5 seconds to ensure the execution of the DEBUG
        ), $options);

        return true;
    }

    public function get($name, $default = null)
    {
        if( in_array($name, $this->parameters) )
            return $this->parameters[$name];
        return $default;
    }


    /**
     * For show ASCII documents with "data uri scheme"
     * @param string $s    to encode
     * @return string      always return string
     */
    public function asciiToInline($str)
    {
        static $translate;

        if ($translate === null) {
            $translate = array(
                $this->get('H2CP_EOL') => '%0A',
                $this->get('H2CP_WOL') => '%0D',
                ' ' => '%20',
                '"' => '%22',
                '#' => '%23',
                '&' => '%26',
                '\/' => '%2F',
                '\\' => '%5C',
                ':' => '%3A',
                '?' => '%3F',
                chr(0) => '%00',
                chr(8) => '',
                chr(9) => '%09'
            );
        }

        return strtr($str, $translate);
    }

    /**
     * Detect SSL stream transport
     * @return boolean  returns false if have an problem, returns true if ok
    */
    public function supportSSL()
    {
        static $supported;

        if ($supported !== null) {
            return $supported;
        }

        return $supported = in_array('ssl', stream_get_transports());
    }

    /**
     * Remove old files defined by $this->get('H2CP_CACHE')
     * @return void  return always void
     */
    public function removeOldFiles()
    {
        $p = $this->get('H2CP_PATH') . '/';

        if (
            ($this->get('H2CP_MAX_EXEC') === 0 || (time() - $this->get('H2CP_INIT_EXEC')) < $this->get('H2CP_MAX_EXEC')) && //prevents this function locks the process that was completed
            (file_exists($p) || is_dir($p))
        ) {
            $h = opendir($p);
            if (false !== $h) {
                while (false !== ($f = readdir($h))) {
                    if (
                        is_file($p . $f) &&
                        strpos($f, $this->get('H2CP_SECPREFIX')) !== false &&
                        ($this->get('H2CP_INIT_EXEC') - filectime($p . $f)) > ($this->get('H2CP_CACHE') * 2)
                    ) {
                        unlink($p . $f);
                    }
                }
            }
        }
    }

    /**
     * Detect if content-type is valid and get charset if available
     * @param string $content  content-type
     * @return array           always return array
     */
    public function checkContentType($content)
    {
        $content = strtolower($content);
        $encode = null;

        if (preg_match('#[;](\s|)+charset[=]#', $content) === 1) {
            $encode = preg_split('#[;](\s|)+charset[=]#', $content);
            $encode = empty($encode[1]) ? null : trim($encode[1]);
        }

        $mime = trim(
            preg_replace('/[;]([\\s\\S]|)+$/', '',
                str_replace('content-type:', '',
                    str_replace('/x-', '/', $content)
                )
            )
        );

        if (in_array($mime, array(
            'image/bmp', 'image/windows-bmp', 'image/ms-bmp',
            'image/jpeg', 'image/jpg', 'image/png', 'image/gif',
            'text/html', 'application/xhtml', 'application/xhtml+xml',
            'image/svg+xml', //SVG image
            'image/svg-xml' //Old servers (bug)
        )) === false) {
            return array('error' => $mime . ' mimetype is invalid');
        }

        return array(
            'mime' => $mime, 'encode' => $encode
        );
    }

    /**
     * enconde string in "json" (only strings), json_encode (native in php) don't support for php4
     * @param string $str  to encode
     * @return string      always return string
     */
    public function JsonEncodeString($str, $onlyEncode=false)
    {
        static $translate;

        if ($translate === null) {
            $translate = array(
                0 => '\\0',
                8 => '\\b',
                9 => '\\t',
                10 => '\\n',
                12 => '\\f',
                13 => '\\r',
                34 => '\\"',
                47 => '\\/',
                92 => '\\\\'
            );
        }

        $tmp = '';
        $enc = '';
        $j = strlen($str);

        for ($i = 0; $i < $j; ++$i) {
            $tmp = substr($str, $i, 1);
            $c = ord($tmp);
            if ($c > 126) {
                $d = '000' . dechex($c);
                $tmp = '\\u' . substr($d, strlen($d) - 4);
            } else {
                if (isset($translate[$c])) {
                    $tmp = $translate[$c];
                } elseif (($c > 31) === false) {
                    $d = '000' . dechex($c);
                    $tmp = '\\u' . substr($d, strlen($d) - 4);
                }
            }

            $enc .= $tmp;
        }

        if ($onlyEncode) {
            return $enc;
        } else {
            return '"' . $enc . '"';
        }
    }

    /**
     * set headers in document
     * @param boolean $nocache  If false set cache (if $this->get('H2CP_CACHE') > 0), If true set no-cache in document
     * @return void             return always void
     */
    public function setHeaders($nocache)
    {
        if ($nocache === false && is_int($this->get('H2CP_CACHE')) && $this->get('H2CP_CACHE') > 0) {
            //save to browser cache
            header('Last-Modified: ' . $this->get('H2CP_GMDATECACHE') . ' GMT');
            header('Cache-Control: max-age=' . ($this->get('H2CP_CACHE') - 1));
            header('Pragma: max-age=' . ($this->get('H2CP_CACHE') - 1));
            header('Expires: ' . gmdate('D, d M Y H:i:s', $this->get('H2CP_INIT_EXEC') + $this->get('H2CP_CACHE') - 1));
            header('Access-Control-Max-Age:' . $this->get('H2CP_CACHE'));
        } else {
            //no-cache
            header('Pragma: no-cache');
            header('Cache-Control: no-cache');
            header('Expires: '. $this->get('H2CP_GMDATECACHE') .' GMT');
        }

        //set access-control
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Request-Method: *');
        header('Access-Control-Allow-Methods: OPTIONS, GET');
        header('Access-Control-Allow-Headers: *');
    }

    /**
     * Converte relative-url to absolute-url
     * @param string $url       set base url
     * @param string $relative  set relative url
     * @return string           return always string, if have an error, return blank string (scheme invalid)
    */
    public function relativeToAbsolute($url, $relative)
    {
        if (strpos($relative, '//') === 0) {//http link //site.com/test
            return 'http:' . $relative;
        }

        if (preg_match('#^[a-z0-9]+[:]#i', $relative) !== 0) {
            $pu = parse_url($relative);

            if (preg_match('#^(http|https)$#i', $pu['scheme']) === 0) {
                return '';
            }

            $relative = '';

            if (isset($pu['path'])) {
                $relative .= $pu['path'];
            }

            if (isset($pu['query'])) {
                $relative .= '?' . $pu['query'];
            }

            if (isset($pu['fragment'])) {
                $relative .= '#' . $pu['fragment'];
            }

            return $this->relativeToAbsolute($pu['scheme'] . '://' . $pu['host'], $relative);
        }

        if (preg_match('/^[?#]/', $relative) !== 0) {
            return $url . $relative;
        }

        $pu = parse_url($url);
        $pu['path'] = isset($pu['path']) ? preg_replace('#/[^/]*$#', '', $pu['path']) : '';

        $pm = parse_url('http://1/' . $relative);
        $pm['path'] = isset($pm['path']) ? $pm['path'] : '';

        $isPath = $pm['path'] !== '' && strpos(strrev($pm['path']), '/') === 0 ? true : false;

        if (strpos($relative, '/') === 0) {
            $pu['path'] = '';
        }

        $b = $pu['path'] . '/' . $pm['path'];
        $b = str_replace('\\', '/', $b);//Confuso ???

        $ab = explode('/', $b);
        $j = count($ab);

        $ab = array_filter($ab, 'strlen');
        $nw = array();

        for ($i = 0; $i < $j; ++$i) {
            if (isset($ab[$i]) === false || $ab[$i] === '.') {
                continue;
            }

            if ($ab[$i] === '..') {
                array_pop($nw);
            } else {
                $nw[] = $ab[$i];
            }
        }

        $relative  = $pu['scheme'] . '://' . $pu['host'] . '/' . implode('/', $nw) . ($isPath ? '/' : '');

        if (isset($pm['query'])) {
            $relative .= '?' . $pm['query'];
        }

        if (isset($pm['fragment'])) {
            $relative .= '#' . $pm['fragment'];
        }

        $nw = null;
        $ab = null;
        $pm = null;
        $pu = null;

        return $relative;
    }

    /**
     * validate url
     * @param string $url  set base url
     * @return boolean     return always boolean
    */
    public function isHttpUrl($url)
    {
        return preg_match('#^http(|s)[:][/][/][a-z0-9]#i', $url) === 1;
    }

    /**
     * check if url is allowed
     * @param string $url  set base url
     * @return boolean     return always boolean
    */
    public function isAllowedUrl($url, &$message) {
        $uri = parse_url($url);

        $domains = preg_split('/,\\s*/', $this->get('H2CP_ALLOWED_DOMAINS'));
        $ports = preg_split('/,\\s*/', $this->get('H2CP_ALLOWED_PORTS'));

        if (in_array('*', $domains) === false) {
            $ok = false;

            foreach ($domains as $domain) {
                if ($domain === $uri['host']) {
                    $ok = true;
                    break;
                } elseif (strpos($domain, '*') !== false) {
                    $domain = strtr($domain, array(
                        '*' => '\\w+',
                        '.' => '\\.'
                    ));

                    if (preg_match('#^' . $domain . '$#i', $uri['host']) === 1) {
                        $ok = true;
                        break;
                    }
                }
            }

            if ($ok === false) {
                $message = '"' . $uri['host'] . '" domain is not allowed';
                return false;
            }
        }

        if (empty($uri['port'])) {
            $port = strcasecmp('https', $uri['scheme']) === 0 ? 443 : 80;
        } else {
            $port = $uri['port'];
        }

        // $ok = false;

        // foreach ($this->get('H2CP_ALLOWED_PORTS') as $allowed_port) {
        //     if ($port == $allowed_port) {
        //         $ok = true;
        //         break;
        //     }
        // }

        if (in_array($port, $ports)) {
            return true;
        }

        // if (empty($uri['port'])) {
        //     $port = strcasecmp('https', $uri['scheme']) === 0 ? 443 : 80;
        // } else {
        //     $port = $uri['port'];
        // }

        $message = '"' . $port . '" port is not allowed';
        $message .= "\n".var_export($ports, true);
        $message .= "\n".var_export($domains, true);

        return false;
    }

    /**
     * create folder for images download
     * @return boolean  return always boolean
    */
    public function createFolder()
    {
        if (file_exists($this->get('H2CP_PATH')) === false || is_dir($this->get('H2CP_PATH')) === false) {
            return mkdir($this->get('H2CP_PATH'), $this->get('H2CP_PERMISSION'));
        }

        return true;
    }

    /**
     * create temp file which will receive the download
     * @param string  $basename  set url
     * @param boolean $isEncode  If true uses the "first" temporary name
     * @return boolean|array     If you can not create file return false, If create file return array
    */
    public function createTmpFile($basename, $isEncode)
    {
        $folder = preg_replace('#[/]$#', '', $this->get('H2CP_PATH')) . '/';

        if ($isEncode === false) {
            $basename = $this->get('H2CP_SECPREFIX') . strlen($basename) . '.' . sha1($basename);
        }

        $tmpMime  = '.' . mt_rand(0, 1000) . '_';
        $tmpMime .= $isEncode ? time() : $this->get('H2CP_INIT_EXEC');

        if (file_exists($folder . $basename . $tmpMime)) {
            return $this->createTmpFile($basename, true);
        }

        $source = fopen($folder . $basename . $tmpMime, 'w');

        if ($source !== false) {
            return array(
                'location' => $folder . $basename . $tmpMime,
                'source' => $source
            );
        }

        return false;
    }

    /**
     * download http request using curl extension (If found HTTP 3xx)
     * @param string   $url       url requested
     * @param resource $toSource  save downloaded url contents
     * @return array              retuns array
    */
    public function curlDownloadSource($url, $toSource)
    {
        $uri = parse_url($url);

        //Reformat url
        $currentUrl  = (empty($uri['scheme']) ? 'http': $uri['scheme']) . '://';
        $currentUrl .= empty($uri['host'])    ? '': $uri['host'];

        if (isset($uri['port'])) {
            $currentUrl .= ':' . $uri['port'];
        }

        $currentUrl .= empty($uri['path'])    ? '/': $uri['path'];
        $currentUrl .= empty($uri['query'])   ? '': ('?' . $uri['query']);

        $ch = curl_init();

        if ($this->get('H2CP_SSL_VERIFY_PEER')) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        } elseif (is_string($this->get('H2CP_SSL_VERIFY_PEER'))) {
            if (is_file($this->get('H2CP_SSL_VERIFY_PEER'))) {
                curl_close($ch);
                return array('error' => 'Not found certificate: ' . $this->get('H2CP_SSL_VERIFY_PEER'));
            }

            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            curl_setopt($ch, CURLOPT_CAINFO, $this->get('H2CP_SSL_VERIFY_PEER'));
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        }

        curl_setopt($ch, CURLOPT_TIMEOUT, $this->get('H2CP_TIMEOUT'));
        curl_setopt($ch, CURLOPT_URL, $currentUrl);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, $this->get('H2CP_MAX_LOOP'));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);

        if (isset($uri['user'])) {
            curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
            curl_setopt($ch, CURLOPT_USERPWD, $uri['user'] . ':' . (isset($uri['pass']) ? $uri['pass'] : ''));
        }

        $headers = array();

        if (false === empty($_SERVER['HTTP_ACCEPT'])) {
            $headers[] = 'Accept: ' . $_SERVER['HTTP_ACCEPT'];
        }

        if (false === empty($_SERVER['HTTP_USER_AGENT'])) {
            $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'];
        }

        if (false === empty($_SERVER['HTTP_REFERER'])) {
            $headers[] = 'Referer: ' . $_SERVER['HTTP_REFERER'];
        }

        $headers[] = 'Host: ' . $uri['host'];
        $headers[] = 'Connection: close';

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $data = curl_exec($ch);

        $curl_err = curl_errno($ch);

        $result = null;

        if ($curl_err !== 0) {
            $result = array('error' => 'CURL failed: ' . $curl_err);
        } else {
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

            if ($httpCode != 200) {
                $result = array('error' => 'Request returned HTTP_' . $httpCode);
            }

            if ($result === null) {
                $result = $this->checkContentType($contentType);

                if (empty($result['error'])) {
                    fwrite($toSource, $data);
                }
            }
        }

        curl_close($ch);

        return $result;
    }

    /**
     * download http request recursive (If found HTTP 3xx)
     * @param string   $url       url requested
     * @param resource $toSource  save downloaded url contents
     * @return array              retuns array
    */
    public function downloadSource($url, $toSource, $caller)
    {
        $errno = 0;
        $errstr = '';

        ++$caller;

        if ($caller > $this->get('H2CP_MAX_LOOP')) {
            return array('error' => 'Limit of ' . $this->get('H2CP_MAX_LOOP') . ' redirects was exceeded, maybe there is a problem: ' . $url);
        }

        $uri = parse_url($url);
        $secure = strcasecmp($uri['scheme'], 'https') === 0;

        if ($secure) {
            if ($this->supportSSL() === false) {
                return array('error' => 'No SSL stream support detected');
            }
        }

        $port = empty($uri['port']) ? ($secure ? 443 : 80) : ((int) $uri['port']);
        $host = ($secure ? 'ssl://' : '') . $uri['host'];

        $fp = fsockopen($host, $port, $errno, $errstr, $this->get('H2CP_TIMEOUT'));

        if ($fp === false) {
            return array('error' => 'SOCKET: ' . $errstr . '(' . $errno . ') - ' . $host . ':' . $port);
        } else {
            fwrite(
                $fp, 'GET ' . (
                    empty($uri['path'])  ? '/' : $uri['path']
                ) . (
                    empty($uri['query']) ? '' : ('?' . $uri['query'])
                ) . ' HTTP/1.0' . $this->get('H2CP_WOL') . $this->get('H2CP_EOL')
            );

            if (isset($uri['user'])) {
                $auth = base64_encode($uri['user'] . ':' . (isset($uri['pass']) ? $uri['pass'] : ''));
                fwrite($fp, 'Authorization: Basic ' . $auth . $this->get('H2CP_WOL') . $this->get('H2CP_EOL'));
            }

            if (false === empty($_SERVER['HTTP_ACCEPT'])) {
                fwrite($fp, 'Accept: ' . $_SERVER['HTTP_ACCEPT'] . $this->get('H2CP_WOL') . $this->get('H2CP_EOL'));
            }

            if (false === empty($_SERVER['HTTP_USER_AGENT'])) {
                fwrite($fp, 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'] . $this->get('H2CP_WOL') . $this->get('H2CP_EOL'));
            }

            if (false === empty($_SERVER['HTTP_REFERER'])) {
                fwrite($fp, 'Referer: ' . $_SERVER['HTTP_REFERER'] . $this->get('H2CP_WOL') . $this->get('H2CP_EOL'));
            }

            fwrite($fp, 'Host: ' . $uri['host'] . $this->get('H2CP_WOL') . $this->get('H2CP_EOL'));
            fwrite($fp, 'Connection: close' . $this->get('H2CP_WOL') . $this->get('H2CP_EOL') . $this->get('H2CP_WOL') . $this->get('H2CP_EOL'));

            $isRedirect = true;
            $isBody = false;
            $isHttp = false;
            $encode = null;
            $mime = null;
            $data = '';

            while (false === feof($fp)) {
                if ($this->get('H2CP_MAX_EXEC') !== 0 && (time() - $this->get('H2CP_INIT_EXEC')) >= $this->get('H2CP_MAX_EXEC')) {
                    return array('error' => 'Maximum execution time of ' . ($this->get('H2CP_MAX_EXEC') + 5) . ' seconds exceeded, configure this with ini_set/set_time_limit or "php.ini" (if safe_mode is enabled)');
                }

                $data = fgets($fp);

                if ($data === false) {
                    continue;
                }

                if ($isHttp === false) {
                    if (preg_match('#^HTTP[/]1[.]#i', $data) === 0) {
                        fclose($fp);//Close connection
                        $data = '';
                        return array('error' => 'This request did not return a HTTP response valid');
                    }

                    $tmp = preg_replace('#(HTTP/1[.]\\d |[^0-9])#i', '',
                        preg_replace('#^(HTTP/1[.]\\d \\d{3}) [\\w\\W]+$#i', '$1', $data)
                    );

                    if ($tmp === '304') {
                        fclose($fp);//Close connection
                        $data = '';
                        return array('error' => 'Request returned HTTP_304, this status code is incorrect because the html2canvas not send Etag');
                    } else {
                        $isRedirect = preg_match('#^(301|302|303|307|308)$#', $tmp) !== 0;

                        if ($isRedirect === false && $tmp !== '200') {
                            fclose($fp);
                            $data = '';
                            return array('error' => 'Request returned HTTP_' . $tmp);
                        }

                        $isHttp = true;

                        continue;
                    }
                }

                if ($isBody === false) {
                    if (preg_match('#^location[:]#i', $data) !== 0) {//200 force 302
                        fclose($fp);//Close connection

                        $data = trim(preg_replace('#^location[:]#i', '', $data));

                        if ($data === '') {
                            return array('error' => '"Location:" header is blank');
                        }

                        $nextUri = $data;
                        $data = $this->relativeToAbsolute($url, $data);

                        if ($data === '') {
                            return array('error' => 'Invalid scheme in url (' . $nextUri . ')');
                        }

                        if ($this->isHttpUrl($data) === false) {
                            return array('error' => '"Location:" header redirected for a non-http url (' . $data . ')');
                        }

                        return $this->downloadSource($data, $toSource, $caller);
                    } elseif (preg_match('#^content[-]length[:]( 0|0)$#i', $data) !== 0) {
                        fclose($fp);
                        $data = '';
                        return array('error' => 'source is blank (Content-length: 0)');
                    } elseif (preg_match('#^content[-]type[:]#i', $data) !== 0) {
                        $response = $this->checkContentType($data);

                        if (isset($response['error'])) {
                            fclose($fp);
                            return $response;
                        }

                        $encode = $response['encode'];
                        $mime = $response['mime'];
                    } elseif ($isBody === false && trim($data) === '') {
                        $isBody = true;
                        continue;
                    }
                } elseif ($isRedirect) {
                    fclose($fp);
                    $data = '';
                    return array('error' => 'The response should be a redirect "' . $url . '", but did not inform which header "Localtion:"');
                } elseif ($mime === null) {
                    fclose($fp);
                    $data = '';
                    return array('error' => 'Not set the mimetype from "' . $url . '"');
                } else {
                    fwrite($toSource, $data);
                    continue;
                }
            }

            fclose($fp);

            $data = '';

            if ($isBody === false) {
                return array('error' => 'Content body is empty');
            } elseif ($mime === null) {
                return array('error' => 'Not set the mimetype from "' . $url . '"');
            }

            return array(
                'mime' => $mime,
                'encode' => $encode
            );
        }
    }

    public function parse($parameters)
    {
        if (empty($_SERVER['HTTP_HOST'])) {
            $response = array('error' => 'The client did not send the Host header');
        } elseif (empty($_SERVER['SERVER_PORT'])) {
            $response = array('error' => 'The Server-proxy did not send the PORT (configure PHP)');
        } elseif ($this->get('H2CP_MAX_EXEC') < 10) {
            $response = array('error' => 'Execution time is less 15 seconds, configure this with ini_set/set_time_limit or "php.ini" (if safe_mode is enabled), recommended time is 30 seconds or more');
        } elseif ($this->get('H2CP_MAX_EXEC') <= $this->get('H2CP_TIMEOUT')) {
            $response = array('error' => 'The execution time is not configured enough to TIMEOUT in SOCKET, configure this with ini_set/set_time_limit or "php.ini" (if safe_mode is enabled), recommended that the "max_execution_time =;" be a minimum of 5 seconds longer or reduce the TIMEOUT in "define(\'H2CP_TIMEOUT\', ' . $this->get('H2CP_TIMEOUT') . ');"');
        } elseif (empty($parameters['url'])) {
            $response = array('error' => 'No such parameter "url"');
        } elseif ($this->isHttpUrl($parameters['url']) === false) {
            $response = array('error' => 'Only http scheme and https scheme are allowed');
        } elseif ($this->isAllowedUrl($parameters['url'], $message) === false) {
            $response = array('error' => $message);
        } elseif ($this->createFolder() === false) {
            $err = error_get_last();
            $response = array('error' => 'Can not create directory'. (
                $err !== null && empty($err['message']) ? '' : (': ' . $err['message'])
            ));
            $err = null;
        } else {
            $http_port = (int) $_SERVER['SERVER_PORT'];

            $tmp = $this->createTmpFile($parameters['url'], false);

            if ($tmp === false) {
                $err = error_get_last();
                $response = array('error' => 'Can not create file'. (
                    $err !== null && empty($err['message']) ? '' : (': ' . $err['message'])
                ));
                $err = null;
            } else {
                $response = $this->get('H2CP_PREFER_CURL') && function_exists('curl_init') ?
                                $this->curlDownloadSource($parameters['url'], $tmp['source']) :
                                    $this->downloadSource($parameters['url'], $tmp['source'], 0);

                fclose($tmp['source']);
            }
        }

        //set mime-type
        header('Content-Type: application/javascript');

        if (is_array($response) && false === empty($response['mime'])) {
            clearstatcache();

            if (false === file_exists($tmp['location'])) {
                $response = array('error' => 'Request was downloaded, but file can not be found, try again');
            } elseif (filesize($tmp['location']) < 1) {
                $response = array('error' => 'Request was downloaded, but there was some problem and now the file is empty, try again');
            } else {
                $extension = str_replace(array('image/', 'text/', 'application/'), '', $response['mime']);
                $extension = str_replace(array('windows-bmp', 'ms-bmp'), 'bmp', $extension);
                $extension = str_replace(array('svg+xml', 'svg-xml'), 'svg', $extension);
                $extension = str_replace('xhtml+xml', 'xhtml', $extension);
                $extension = str_replace('jpeg', 'jpg', $extension);

                $locationFile = preg_replace('#[.][0-9_]+$#', '.' . $extension, $tmp['location']);

                if (file_exists($locationFile)) {
                    unlink($locationFile);
                }

                if (rename($tmp['location'], $locationFile)) {
                    //set cache
                    $this->setHeaders(false);

                    $this->removeOldFiles();

                    $mime = $response['mime'];

                    if ($response['encode'] !== null) {
                        $mime .= ';charset=' . $this->JsonEncodeString($response['encode'], true);
                    }

                    if ($this->get('H2CP_JSONP') === false) {
                        header('Content-Type: ' . $mime);
                        echo file_get_contents($locationFile);
                    } elseif ($this->get('H2CP_DATAURI')) {
                        $tmp = $response = null;

                        header('Content-Type: application/javascript');

                        if (strpos($mime, 'image/svg') !== 0 && strpos($mime, 'image/') === 0) {
                            echo $this->get('H2CP_JSONP'), '("data:', $mime, ';base64,',
                                base64_encode(
                                    file_get_contents($locationFile)
                                ),
                            '");';
                        } else {
                            echo $this->get('H2CP_JSONP'), '("data:', $mime, ',',
                                $this->asciiToInline(file_get_contents($locationFile)),
                            '");';
                        }
                    } else {
                        $tmp = $response = null;

                        header('Content-Type: application/javascript');

                        $dir_name = dirname($_SERVER['SCRIPT_NAME']);

                        if ($dir_name === '\/' || $dir_name === '\\') {
                            $dir_name = '';
                        }

                        echo $this->get('H2CP_JSONP'), '(',
                            $this->JsonEncodeString(
                                ($http_port === 443 ? 'https://' : 'http://') .
                                preg_replace('#:[0-9]+$#', '', $_SERVER['HTTP_HOST']) .
                                ($http_port === 80 || $http_port === 443 ? '' : (
                                    ':' . $_SERVER['SERVER_PORT']
                                )) .
                                $dir_name. '/' .
                                $locationFile
                            ),
                        ');';
                    }
                    exit;
                } else {
                    $response = array('error' => 'Failed to rename the temporary file');
                }
            }
        }

        if (is_array($tmp) && isset($tmp['location']) && file_exists($tmp['location'])) {
            // Remove temporary file if an error occurred
            unlink($tmp['location']);
        }

        $this->setHeaders(true); // no-cache

        header('Content-Type: application/javascript');

        $this->removeOldFiles();

        echo $this->get('H2CP_JSONP'), '(',
            $this->JsonEncodeString(
                'error: html2canvas-proxy-php: ' . $response['error']
            ),
        ');';
    }
}