<?php

/**
 * Created by PhpStorm.
 * User: stanley-king
 * Date: 16/1/30
 * Time: 下午3:46
 */

class http_header
{
    const COOKIE_EXPIRES    = "; expires=";
    const COOKIE_MAX_AGE    = "; Max-Age=";
    const COOKIE_DOMAIN     = "; domain=";
    const COOKIE_PATH       = "; path=";
    const COOKIE_SECURE     = "; secure";
    const COOKIE_HTTPONLY   = "; HttpOnly";

    private static $stHeader = NULL;
    private static $http_status_map = array(100 =>"Continue",
                                            101 =>"Switching Protocols" ,
                                            200 =>"OK" ,
                                            201 =>"Created" ,
                                            202 =>"Accepted" ,
                                            203 =>"Non-Authoritative Information" ,
                                            204 =>"No Content" ,
                                            205 =>"Reset Content" ,
                                            206 =>"Partial Content" ,
                                            300 =>"Multiple Choices" ,
                                            301 =>"Moved Permanently" ,
                                            302 =>"Found" ,
                                            303 =>"See Other" ,
                                            304 =>"Not Modified" ,
                                            305 =>"Use Proxy" ,
                                            307 =>"Temporary Redirect" ,
                                            308 =>"Permanent Redirect" ,
                                            400 =>"Bad Request" ,
                                            401 =>"Unauthorized" ,
                                            402 =>"Payment Required" ,
                                            403 =>"Forbidden" ,
                                            404 =>"Not Found" ,
                                            405 =>"Method Not Allowed" ,
                                            406 =>"Not Acceptable" ,
                                            407 =>"Proxy Authentication Required" ,
                                            408 =>"Request Timeout" ,
                                            409 =>"Conflict" ,
                                            410 =>"Gone" ,
                                            411 =>"Length Required" ,
                                            412 =>"Precondition Failed" ,
                                            413 =>"Request Entity Too Large" ,
                                            414 =>"Request-URI Too Long" ,
                                            415 =>"Unsupported Media Type" ,
                                            416 =>"Requested Range Not Satisfiable" ,
                                            417 =>"Expectation Failed" ,
                                            426 =>"Upgrade Required" ,
                                            428 =>"Precondition Required" ,
                                            429 =>"Too Many Requests" ,
                                            431 =>"Request Header Fields Too Large" ,
                                            500 =>"Internal Server Error" ,
                                            501 =>"Not Implemented" ,
                                            502 =>"Bad Gateway" ,
                                            503 =>"Service Unavailable" ,
                                            504 =>"Gateway Timeout" ,
                                            505 =>"HTTP Version Not Supported" ,
                                            506 =>"Variant Also Negotiates" ,
                                            511 =>"Network Authentication Required");

    private $mHeader = NULL;
    private $mStatusLine = '';
    private $mStatusCode = 200;
    private $mSended = false;

    public function start()
    {
        $this->mHeader = new SplDoublyLinkedList();
        $this->mStatusCode = 200;
        $this->mSended = false;
    }

    public function sent()
    {
        if($this->sended == false) {
            $this->mSended = true;
            $sHeader = $this->to_string();
            fcgi_echo($sHeader);
        }

        return $this->mSended;
    }

    private function to_string()
    {
        $sheader = '';

        $status_line = sprintf("HTTP/1.1 %d %s\r\n",$this->mStatusCode,self::$http_status_map[$this->mStatusCode]);
        $sheader .= $status_line;
        foreach($this->mHeader as $val)
        {
            $sheader .= $val . "\r\n";
        }

        $sheader .= "\r\n";

        return $sheader;
    }

    static public function instance()
    {
        if(self::$stHeader == NULL) {
            self::$stHeader = new http_header();
        }
        return self::$stHeader;
    }

    public function setcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null,$url_encode = true, $httponly = null)
    {
        if(empty($name)) {
            return false;
        } else if(strpbrk($name, "=,; \t\r\n\013\014") != false) {
            Log::record('Cookie names cannot contain any of the following \'=,; \\t\\r\\n\\013\\014\'');
            return false;
        }

        if(empty($value)) {
            $cookie = sprintf("Set-Cookie: %s=deleted; expires=%s; Max-Age=0", $name, gmdate("D, d-M-Y H:i:s T",1));
        }
        else
        {
            if(!$url_encode && strpbrk($value,"=,; \t\r\n\013\014") != false) {
                Log::record('Cookie valuse cannot contain any of the following \'=,; \\t\\r\\n\\013\\014\'');
                return false;
            }

            if($url_encode) {
                $cookie = sprintf("Set-Cookie: %s=%s", $name, !empty($value) ? urlencode($value) : "");
            } else {
                $cookie = sprintf("Set-Cookie: %s=%s", $name, !empty($value) ? $value : "");
            }

            if($expire > 0) {
                $tmp = self::COOKIE_EXPIRES . gmdate("D, d-M-Y H:i:s T",$expire);
                $cookie .= $tmp;
            }

            $tsdelta = sprintf("%d", $expire - time());
            $cookie .= self::COOKIE_MAX_AGE . $tsdelta;
        }

        if(!empty($path)) {
            $cookie .= self::COOKIE_PATH . $path;
        }

        if(!empty($domain)) {
            $cookie .= self::COOKIE_DOMAIN . $domain;
        }

        if(!empty($secure)) {
            $cookie .= self::COOKIE_SECURE . $secure;
        }

        if(!empty($httponly)) {
            $cookie .= self::COOKIE_HTTPONLY . $httponly;
        }

        $this->mHeader->push($cookie);

        return true;
    }

    private function replace($string)
    {
        $reg = '/^([^:]*): (.*)/i';

        $string = trim($string);
        if(preg_match($reg,$string,$m)) {
            $sname =strtolower($m[1]);
        }

        $index = 0;
        for($this->mHeader->rewind();$this->mHeader->valid();$this->mHeader->next())
        {
            $val = $this->mHeader->current();
            if(preg_match($reg,$val,$m))
            {
                $hname = strtolower($m[1]);
                if(strcmp($sname,$hname) == 0) {
                    $this->mHeader->offsetUnset($index);
                    break;
                }
                $index++;
            }
        }

        $this->mHeader->push($string);
    }

    private function newline_check($string)
    {
        $datas = str_split($string);
        foreach($datas as $val)
        {
            if($val == '\r' || $val == '\n') {
                Log::record("Header may not contain more than a single header, new line detected",Log::ERR);
                return false;
            }
        }

        return true;
    }

    private function extract_response_code($header_line)
    {
        $code = 200;
        $nodes = explode($header_line);
        if(count($nodes) > 1)
        {
            $datas = str_split($nodes[1]);

            $max = intval('0') + 9;
            $min = intval('0');

            foreach($datas as $ch)
            {
                if($ch >$max || $ch < $min) {
                    return false;
                }
            }

            $code = intval($nodes[1]);
        }

        return $code;
    }

    private function special_line($header_line,$http_response_code)
    {
        $len = strlen($header_line);

        if($len >= 5 && strncasecmp($header_line,"HTTP/", 5) == 0)
        {
            $this->mStatusCode = $this->extract_response_code($header_line);
            $this->mStatusLine = $header_line;
            return 0;
        }
        else
        {
            $colon_offset = strchr($header_line, ':');
            if($colon_offset)
            {
                if (!strncasecmp($header_line, "Location",strlen("Location")))
                {
                    if( ($this->mStatusCode < 300 || $this->mStatusCode > 399) && $this->mStatusCode != 201)
                    {
                        $method = strtolower(request_helper::method());
                        if ($http_response_code) { /* user specified redirect code */
                            $this->mStatusCode = $http_response_code;
                        } else if ($method == 'get') {
                            $this->mStatusCode = 303;
                        } else {
                            $this->mStatusCode = 302;
                        }
                    }
                }
                else if(!strncasecmp($header_line, "WWW-Authenticate",strlen("WWW-Authenticate"))) {
                    $this->mStatusCode = 401;
                }
            }
        }

        return 1;
    }

    public function header ($string, $replace = true, $http_response_code = null)
    {
        if(empty($string)) return;
        if($this->newline_check($string) == false) return;

        if($this->special_line($string,$http_response_code) == 0) {
            return false;
        }
        if($http_response_code) {
            $this->mStatusCode = $http_response_code;
        }

        if($replace) {
            $this->replace($string);
        } else {
            $this->mHeader->push($string);
        }
    }

    public function remove($name)
    {
        if(empty($name)) {
            $this->mHeader->clear();
        }
        else
        {
            $reg = '/^([^:]*): (.*)/i';

            $index = 0;
            for($this->mHeader->rewind();$this->mHeader->valid();$this->mHeader->next())
            {
                $val = $this->mHeader->current();
                if(preg_match($reg,$val,$m))
                {
                    $hname = strtolower($m[1]);
                    if(strcmp($name,$hname) == 0) {
                        $this->mHeader->offsetUnset($index);
                        break;
                    }
                    $index++;
                }
            }
        }
    }
}

function init_cookie($cookie)
{
    $regxp = '/([^=]+=[^;]*)[;]?/i';
    $val = preg_match_all($regxp,$cookie,$match);

    if($val == false) return false;

    if(count($match) == 2)
    {
        foreach($match[1] as $val)
        {
            $kv = preg_split('/=/',$val);
            if(!empty($kv))
            {
                $k = trim($kv[0]);
                $v = trim($kv[1]);
                if(!empty($k)) {
                    $_COOKIE[$k] = $v;
                }
                //Log::record("cookie {$k} = {$v}",Log::DEBUG);
            }
        }
    }

    return true;
}

function fcgi_setcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null)
{
    return http_header::instance()->setcookie($name,$value,$expire,$path,$domain,$secure,true,$httponly);
}

function fcgi_setrawcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null)
{
    return http_header::instance()->setcookie($name,$value,$expire,$path,$domain,$secure,false,$httponly);
}

function fcgi_header($string, $replace = true, $http_response_code = null)
{
    http_header::instance()->header($string,$replace,$http_response_code);
}

function fcgi_header_remove($name)
{
    return http_header::instance()->remove($name);
}

function fcgi_headers_sent()
{
    return http_header::instance()->sent();
}
