开发者

Interception of Requests by HTML Purifier

How does something like HTML Purifier or CSRF Magic actually intercept an HTTP request? Its documentation says its based of the idea of the Django Middleware framework of Python but I am yet to find some documentation of how it intercepts HTTP Requests. It works without actually having t开发者_StackOverflow社区he need to install any PHP extensions.

Can anyone shed some light on the issue?

Regards


CSRF Magic uses PHP's output control functions. It captures the output of your script, modifies it, and uses a special handler function to modify the output it captured before it is printed. So, the real magic is in ob_start. Read up on it if you're interested. Also, since CSRF Magic is an open-source project, you can read the script itself for more detailed information.

It ultimately comes down to line 371:

if ($GLOBALS['csrf']['rewrite'])     ob_start('csrf_ob_handler');

This line says that, if the condition is true (and it usually is), to start an output buffer (ob_start) and, when the output is finished, to run csrf_ob_handler on that output. csrf_ob_handler modifies the original output of the script to add hidden inputs, then prints that result.


If without jokes - "intercept" http request and send response is what any web-application should do at all.
And you don't need any extensions, because PHP was designed exactly to be a tool for creating web-applications.
To read data of HTTP-request, use arrays $_GET, $_POST, $_SERVER (and php://input sometimes), and to send the Response you can just echo "that's my response!";.

I can even give you two my classes to work with Request and Response, hope it will be useful (or just interesting, at least):

<?php
namespace Jamm\HTTP;

class Request
{
    protected $method;
    protected $headers;
    protected $data;
    protected $accept;

    const method_GET = 'GET';
    const method_POST = 'POST';
    const method_PUT = 'PUT';
    const method_DELETE = 'DELETE';

    /**
     * @param bool $parse - parse current input to object's variables (input request)
     * @return \Jamm\HTTP\Request
     *
     */
    public function __construct($parse = false)
    {
        $this->method = self::method_GET;
        if ($parse) $this->BuildFromInput();
        $this->setHeader('Content-type', 'text/plain');
    }

    public function BuildFromInput()
    {
        $this->headers = $_SERVER;
        $this->accept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : '';
        $this->method = $_SERVER['REQUEST_METHOD'];
        switch ($this->method)
        {
            case 'HEAD':
            case 'GET':
                $this->data = $_GET;
                break;
            case 'POST':
                $this->data = $_POST;
                break;
            default:
                parse_str(file_get_contents('php://input'), $this->data);
        }
    }

    /**
     * Return header from array by key, or all keys
     * @param string $key
     * @return null|array|mixed
     */
    public function getHeaders($key = null)
    {
        if (!empty($key))
        {
            return isset($this->headers[$key]) ? $this->headers[$key] : NULL;
        }
        else return $this->headers;
    }

    /**
     * Get type of request method
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * Return key or all the keys of request
     * @param string $key
     * @return null|array|string|numeric
     */
    public function getData($key = null)
    {
        if (empty($key)) return $this->data;
        else
        {
            return isset($this->data[$key]) ? $this->data[$key] : NULL;
        }
    }

    /**
     * Return HTTP_ACCEPT header
     * @return string
     */
    public function getAccept()
    {
        return $this->accept;
    }

    /**
     * Check, if this type is acceptable
     * @param string $type
     * @return bool
     */
    public function isAcceptable($type)
    {
        if (empty($type) || (stripos($this->getAccept(), $type)!==false)) return true;
        return false;
    }

    public function setHeader($header, $value)
    {
        $this->headers[$header] = $value;
    }

    /**
     * Set the request method
     * @param $method
     * @return void
     */
    public function setMethod($method)
    {
        $this->method = strtoupper($method);
        if ($this->method!=self::method_GET) $this->setHeader('Content-type', 'application/x-www-form-urlencoded');
    }

    public function setDataKey($key, $value)
    {
        $this->data[$key] = $value;
    }

    public function SetAccept($accept)
    {
        $this->accept = $accept;
    }

    /**
     * Send request by URL. Pass $Response argument, if you need response
     * @param $URL
     * @param IResponse|null $Response
     * @return bool|IResponse
     */
    public function Send($URL, IResponse $Response = NULL)
    {
        $url_data = parse_url($URL);
        $fp = fsockopen($url_data['host'], 80);
        if (!$fp) return false;
        $path = (isset($url_data['path']) ? $url_data['path'] : '/').
                (isset($url_data['query']) ? '?'.$url_data['query'] : '');
        $data = $this->getData();
        if (!empty($data) && is_array($data)) $data = http_build_query($data);

        if ($this->method==self::method_GET)
        {
            fwrite($fp, $this->method." $path?$data HTTP/1.0\r\n");
        }
        else
        {
            fwrite($fp, $this->method." $path HTTP/1.0\r\n");
            fwrite($fp, "Content-Length: ".strlen($data)."\r\n");
        }
        fwrite($fp, "Host: {$url_data['host']}\r\n");
        foreach ($this->getHeaders() as $header_name => $header_value)
        {
            fwrite($fp, "$header_name: $header_value\r\n");
        }

        fwrite($fp, "Connection: Close\r\n\r\n");

        if ($this->method!=self::method_GET)
        {
            fwrite($fp, $data);
        }
        if (!empty($Response)) return $this->ReadResponse($fp, $Response);
        else return true;
    }

    /**
     * @param \resource $fresource
     * @param IResponse $response
     * @return IResponse
     */
    protected function ReadResponse($fresource, IResponse $response)
    {
        //read headers
        $status_header = '';
        $headers = array();
        while (!feof($fresource))
        {
            $header = trim(fgets($fresource));
            if (!empty($header))
            {
                if (empty($status_header)) $status_header = $header;
                if (strpos($header, ':')!==false)
                {
                    $header = explode(':', $header);
                    $headers[trim($header[0])] = trim($header[1]);
                }
                else $headers[] = $header;
            }
            else break;
        }
        $response->setHeaders($headers);
        if (!empty($status_header))
        {
            $status_header = explode(' ', $status_header);
            $response->setStatusCode(intval(trim($status_header[1])));
        }

        //read body
        $body = '';
        while (!feof($fresource)) $body .= fread($fresource, 4096);
        fclose($fresource);

        if (!empty($body)) $response->setBody($body);

        return $response;
    }

    /**
     * Set array of data
     * @param array $values
     */
    public function setData(array $values)
    {
        $this->data = $values;
    }
}

And Response:

<?php
namespace Jamm\HTTP;

class Response implements IResponse
{
    protected $status_code;
    protected $body;
    protected $headers;
    protected $serialize_method;

    const serialize_JSON = 'JSON';
    const serialize_XML = 'XML';
    const serialize_PHP = 'PHP';

    const header_Serialized = 'API-Serialized';

    public function __construct($body = '', $status_code = 200)
    {
        $this->body = $body;
        $this->status_code = $status_code;
        $this->serialize_method = self::serialize_JSON;
    }

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

    /** @param int $status_code  */
    public function setStatusCode($status_code)
    {
        $this->status_code = (int)$status_code;
    }

    /**
     * Set header for the response
     * @param string $header
     * @param string|numeric $value
     */
    public function setHeader($header, $value)
    {
        $this->headers[$header] = $value;
        if ($header==='Location' && $this->status_code==200) $this->setStatusCode(301);
    }

    public function getHeader($header)
    {
        return isset($this->headers[$header]) ? $this->headers[$header] : NULL;
    }

    /**
     * Get body of the response
     * @return string
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * Get Result of response - unpack value of body and headers
     * @return bool|mixed
     */
    public function getResult()
    {
        if ($this->getStatusCode() >= 400) return false;

        if (($serialization_method = $this->getHeader(self::header_Serialized)))
        {
            $this->serialize_method = $serialization_method;
            return $this->unserialize($this->body);
        }
        else return $this->body;
    }

    /**
     * Set body of the response
     * @param $body
     */
    public function setBody($body)
    {
        if (!is_scalar($body))
        {
            $this->body = $this->serialize($body);
            $this->setHeader(self::header_Serialized, $this->serialize_method);
        }
        else $this->body = $body;
    }

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

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

    public function serialize($content)
    {
        switch ($this->serialize_method)
        {
            case self::serialize_JSON:
                return json_encode($content);
            default:
                return serialize($content);
        }
    }

    public function unserialize($content)
    {
        switch ($this->serialize_method)
        {
            case self::serialize_JSON:
                return json_decode($content, true);
            default:
                return unserialize($content);
        }
    }

    /**
     * Send headers and body to output
     */
    public function Send()
    {
        $headers = $this->getHeaders();
        if (!empty($headers))
        {
            foreach ($headers as $header_key => $header_value)
            {
                header($header_key.': '.$header_value, true, $this->status_code);
            }
        }
        print $this->body;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜