http_header.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: stanley-king
  5. * Date: 16/1/30
  6. * Time: 下午3:46
  7. */
  8. class http_header
  9. {
  10. const COOKIE_EXPIRES = "; expires=";
  11. const COOKIE_MAX_AGE = "; Max-Age=";
  12. const COOKIE_DOMAIN = "; domain=";
  13. const COOKIE_PATH = "; path=";
  14. const COOKIE_SECURE = "; secure";
  15. const COOKIE_HTTPONLY = "; HttpOnly";
  16. private static $stHeader = NULL;
  17. private static $http_status_map = array(100 =>"Continue",
  18. 101 =>"Switching Protocols" ,
  19. 200 =>"OK" ,
  20. 201 =>"Created" ,
  21. 202 =>"Accepted" ,
  22. 203 =>"Non-Authoritative Information" ,
  23. 204 =>"No Content" ,
  24. 205 =>"Reset Content" ,
  25. 206 =>"Partial Content" ,
  26. 300 =>"Multiple Choices" ,
  27. 301 =>"Moved Permanently" ,
  28. 302 =>"Found" ,
  29. 303 =>"See Other" ,
  30. 304 =>"Not Modified" ,
  31. 305 =>"Use Proxy" ,
  32. 307 =>"Temporary Redirect" ,
  33. 308 =>"Permanent Redirect" ,
  34. 400 =>"Bad Request" ,
  35. 401 =>"Unauthorized" ,
  36. 402 =>"Payment Required" ,
  37. 403 =>"Forbidden" ,
  38. 404 =>"Not Found" ,
  39. 405 =>"Method Not Allowed" ,
  40. 406 =>"Not Acceptable" ,
  41. 407 =>"Proxy Authentication Required" ,
  42. 408 =>"Request Timeout" ,
  43. 409 =>"Conflict" ,
  44. 410 =>"Gone" ,
  45. 411 =>"Length Required" ,
  46. 412 =>"Precondition Failed" ,
  47. 413 =>"Request Entity Too Large" ,
  48. 414 =>"Request-URI Too Long" ,
  49. 415 =>"Unsupported Media Type" ,
  50. 416 =>"Requested Range Not Satisfiable" ,
  51. 417 =>"Expectation Failed" ,
  52. 426 =>"Upgrade Required" ,
  53. 428 =>"Precondition Required" ,
  54. 429 =>"Too Many Requests" ,
  55. 431 =>"Request Header Fields Too Large" ,
  56. 500 =>"Internal Server Error" ,
  57. 501 =>"Not Implemented" ,
  58. 502 =>"Bad Gateway" ,
  59. 503 =>"Service Unavailable" ,
  60. 504 =>"Gateway Timeout" ,
  61. 505 =>"HTTP Version Not Supported" ,
  62. 506 =>"Variant Also Negotiates" ,
  63. 511 =>"Network Authentication Required");
  64. private $mHeader = NULL;
  65. private $mStatusLine = '';
  66. private $mStatusCode = 200;
  67. private $mSended = false;
  68. public function start()
  69. {
  70. $this->mHeader = new SplDoublyLinkedList();
  71. $this->mStatusCode = 200;
  72. $this->mSended = false;
  73. }
  74. public function sent()
  75. {
  76. if($this->mSended == false) {
  77. $this->mSended = true;
  78. $sHeader = $this->to_string();
  79. fcgi_echo($sHeader);
  80. }
  81. return $this->mSended;
  82. }
  83. private function to_string()
  84. {
  85. $sheader = '';
  86. $status_line = sprintf("HTTP/1.1 %d %s\r\n",$this->mStatusCode,self::$http_status_map[$this->mStatusCode]);
  87. $sheader .= $status_line;
  88. foreach($this->mHeader as $val)
  89. {
  90. $sheader .= $val . "\r\n";
  91. }
  92. $sheader .= "\r\n";
  93. return $sheader;
  94. }
  95. static public function instance()
  96. {
  97. if(self::$stHeader == NULL) {
  98. self::$stHeader = new http_header();
  99. }
  100. return self::$stHeader;
  101. }
  102. public function setcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null,$url_encode = true, $httponly = null)
  103. {
  104. if(empty($name)) {
  105. return false;
  106. } else if(strpbrk($name, "=,; \t\r\n\013\014") != false) {
  107. Log::record('Cookie names cannot contain any of the following \'=,; \\t\\r\\n\\013\\014\'');
  108. return false;
  109. }
  110. if(empty($value)) {
  111. $cookie = sprintf("Set-Cookie: %s=deleted; expires=%s; Max-Age=0", $name, gmdate("D, d-M-Y H:i:s T",1));
  112. }
  113. else
  114. {
  115. if(!$url_encode && strpbrk($value,"=,; \t\r\n\013\014") != false) {
  116. Log::record('Cookie valuse cannot contain any of the following \'=,; \\t\\r\\n\\013\\014\'');
  117. return false;
  118. }
  119. if($url_encode) {
  120. $cookie = sprintf("Set-Cookie: %s=%s", $name, !empty($value) ? urlencode($value) : "");
  121. } else {
  122. $cookie = sprintf("Set-Cookie: %s=%s", $name, !empty($value) ? $value : "");
  123. }
  124. if($expire > 0) {
  125. $tmp = self::COOKIE_EXPIRES . gmdate("D, d-M-Y H:i:s T",$expire);
  126. $cookie .= $tmp;
  127. }
  128. $tsdelta = sprintf("%d", $expire - time());
  129. $cookie .= self::COOKIE_MAX_AGE . $tsdelta;
  130. }
  131. $cookie .= '; SameSite=Lax';
  132. if(!empty($path)) {
  133. $cookie .= self::COOKIE_PATH . $path;
  134. }
  135. if(!empty($domain)) {
  136. $cookie .= self::COOKIE_DOMAIN . $domain;
  137. }
  138. if(!empty($secure)) {
  139. $cookie .= self::COOKIE_SECURE . $secure;
  140. }
  141. if(!empty($httponly)) {
  142. $cookie .= self::COOKIE_HTTPONLY . $httponly;
  143. }
  144. $this->mHeader->push($cookie);
  145. return true;
  146. }
  147. private function replace($string)
  148. {
  149. $reg = '/^([^:]*): (.*)/i';
  150. $string = trim($string);
  151. if(preg_match($reg,$string,$m)) {
  152. $sname =strtolower($m[1]);
  153. }
  154. $index = 0;
  155. for($this->mHeader->rewind();$this->mHeader->valid();$this->mHeader->next())
  156. {
  157. $val = $this->mHeader->current();
  158. if(preg_match($reg,$val,$m))
  159. {
  160. $hname = strtolower($m[1]);
  161. if(strcmp($sname,$hname) == 0) {
  162. $this->mHeader->offsetUnset($index);
  163. break;
  164. }
  165. $index++;
  166. }
  167. }
  168. $this->mHeader->push($string);
  169. }
  170. private function newline_check($string)
  171. {
  172. $datas = str_split($string);
  173. foreach($datas as $val)
  174. {
  175. if($val == '\r' || $val == '\n') {
  176. Log::record("Header may not contain more than a single header, new line detected",Log::ERR);
  177. return false;
  178. }
  179. }
  180. return true;
  181. }
  182. private function extract_response_code($header_line)
  183. {
  184. $code = 200;
  185. $nodes = explode($header_line);
  186. if(count($nodes) > 1)
  187. {
  188. $datas = str_split($nodes[1]);
  189. $max = intval('0') + 9;
  190. $min = intval('0');
  191. foreach($datas as $ch)
  192. {
  193. if($ch >$max || $ch < $min) {
  194. return false;
  195. }
  196. }
  197. $code = intval($nodes[1]);
  198. }
  199. return $code;
  200. }
  201. private function special_line($header_line,$http_response_code)
  202. {
  203. $len = strlen($header_line);
  204. if($len >= 5 && strncasecmp($header_line,"HTTP/", 5) == 0)
  205. {
  206. $this->mStatusCode = $this->extract_response_code($header_line);
  207. $this->mStatusLine = $header_line;
  208. return 0;
  209. }
  210. else
  211. {
  212. $colon_offset = strchr($header_line, ':');
  213. if($colon_offset)
  214. {
  215. if (!strncasecmp($header_line, "Location",strlen("Location")))
  216. {
  217. if( ($this->mStatusCode < 300 || $this->mStatusCode > 399) && $this->mStatusCode != 201)
  218. {
  219. $method = strtolower(request_helper::method());
  220. if ($http_response_code) { /* user specified redirect code */
  221. $this->mStatusCode = $http_response_code;
  222. } else if ($method == 'get') {
  223. $this->mStatusCode = 303;
  224. } else {
  225. $this->mStatusCode = 302;
  226. }
  227. }
  228. }
  229. else if(!strncasecmp($header_line, "WWW-Authenticate",strlen("WWW-Authenticate"))) {
  230. $this->mStatusCode = 401;
  231. }
  232. }
  233. }
  234. return 1;
  235. }
  236. public function header ($string, $replace = true, $http_response_code = null)
  237. {
  238. if(empty($string)) return;
  239. if($this->newline_check($string) == false) return;
  240. if($this->special_line($string,$http_response_code) == 0) {
  241. return false;
  242. }
  243. if($http_response_code) {
  244. $this->mStatusCode = $http_response_code;
  245. }
  246. if($replace) {
  247. $this->replace($string);
  248. } else {
  249. $this->mHeader->push($string);
  250. }
  251. }
  252. public function remove($name)
  253. {
  254. if(empty($name)) {
  255. $this->mHeader->clear();
  256. }
  257. else
  258. {
  259. $reg = '/^([^:]*): (.*)/i';
  260. $index = 0;
  261. for($this->mHeader->rewind();$this->mHeader->valid();$this->mHeader->next())
  262. {
  263. $val = $this->mHeader->current();
  264. if(preg_match($reg,$val,$m))
  265. {
  266. $hname = strtolower($m[1]);
  267. if(strcmp($name,$hname) == 0) {
  268. $this->mHeader->offsetUnset($index);
  269. break;
  270. }
  271. $index++;
  272. }
  273. }
  274. }
  275. return true;
  276. }
  277. }
  278. function init_cookie($cookie)
  279. {
  280. $regxp = '/([^=]+=[^;]*)[;]?/i';
  281. $val = preg_match_all($regxp,$cookie,$match);
  282. if($val == false) return false;
  283. if(count($match) == 2)
  284. {
  285. foreach($match[1] as $val)
  286. {
  287. $kv = preg_split('/=/',$val);
  288. if(!empty($kv))
  289. {
  290. $k = trim($kv[0]);
  291. $v = trim($kv[1]);
  292. if(!empty($k)) {
  293. $_COOKIE[$k] = $v;
  294. }
  295. //Log::record("cookie {$k} = {$v}",Log::DEBUG);
  296. }
  297. }
  298. }
  299. return true;
  300. }
  301. function fcgi_setcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null)
  302. {
  303. return http_header::instance()->setcookie($name,$value,$expire,$path,$domain,$secure,true,$httponly);
  304. }
  305. function fcgi_setrawcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null)
  306. {
  307. return http_header::instance()->setcookie($name,$value,$expire,$path,$domain,$secure,false,$httponly);
  308. }
  309. function fcgi_header($string, $replace = true, $http_response_code = null)
  310. {
  311. http_header::instance()->header($string,$replace,$http_response_code);
  312. }
  313. function fcgi_header_remove($name)
  314. {
  315. return http_header::instance()->remove($name);
  316. }
  317. function fcgi_headers_sent()
  318. {
  319. return http_header::instance()->sent();
  320. }