1 <?php
2 3 4 5 6 7
8
9 namespace Cross\Http;
10
11 12 13 14 15
16 class Response
17 {
18 19 20 21 22
23 protected $header;
24
25 26 27 28 29
30 protected $cookie;
31
32 33 34 35 36
37 protected $cookie_config;
38
39 40 41 42 43
44 protected $content_type;
45
46 47 48 49 50
51 protected $response_status;
52
53 54 55 56 57
58 protected $is_end_flush = false;
59
60 61 62 63 64
65 static $instance;
66
67 private function __construct()
68 {
69
70 }
71
72 73 74 75 76
77 static function getInstance()
78 {
79 if (!self::$instance) {
80 self::$instance = new Response();
81 }
82
83 return self::$instance;
84 }
85
86 87 88 89
90 function setResponseStatus($status = 200)
91 {
92 $this->response_status = $status;
93 return $this;
94 }
95
96 97 98
99 function getResponseStatus()
100 {
101 if (!$this->response_status) {
102 $this->setResponseStatus();
103 }
104
105 return $this->response_status;
106 }
107
108 109 110 111 112 113
114 function setHeader($header)
115 {
116 if (is_array($header)) {
117 foreach ($header as $key => $value) {
118 $this->header[] = "{$key}: {$value}";
119 }
120 } else {
121 $this->header[] = $header;
122 }
123
124 return $this;
125 }
126
127 128 129
130 function getHeader()
131 {
132 return $this->header;
133 }
134
135 136 137 138 139 140 141 142
143 function setCookie($name, $value = '', $expire = 0)
144 {
145 $this->cookie[$name] = array(
146 $name,
147 $value,
148 $expire,
149 $this->getCookieConfig('path'),
150 $this->getCookieConfig('domain'),
151 $this->getCookieConfig('secure'),
152 $this->getCookieConfig('httponly')
153 );
154
155 return $this;
156 }
157
158 159 160 161 162
163 function getCookie()
164 {
165 return $this->cookie;
166 }
167
168 169 170 171 172 173
174 function deleteCookie($name)
175 {
176 $this->setCookie($name, null, -1);
177 return $this;
178 }
179
180 181 182 183 184 185 186 187 188 189 190 191 192 193
194 function cookieConfig(array $config)
195 {
196 $this->cookie_config = $config;
197 return $this;
198 }
199
200 201 202 203 204 205
206 function getCookieConfig($key = null)
207 {
208 $default = array('path' => '/', 'domain' => '', 'secure' => false, 'httponly' => true);
209 if ($key && isset($default[$key])) {
210 if (isset($this->cookie_config[$key])) {
211 return $this->cookie_config[$key];
212 }
213
214 return $default[$key];
215 }
216
217 return null;
218 }
219
220 221 222 223 224 225
226 function setContentType($content_type = 'html')
227 {
228 $this->content_type = strtolower($content_type);
229 return $this;
230 }
231
232 233 234 235 236
237 function getContentType()
238 {
239 if (!$this->content_type) {
240 $this->setContentType();
241 }
242 return $this->content_type;
243 }
244
245 246 247 248 249 250
251 function basicAuth(array $users, $options = array())
252 {
253 $user = &$_SERVER['PHP_AUTH_USER'];
254 $password = &$_SERVER['PHP_AUTH_PW'];
255 if (isset($users[$user]) && (0 === strcmp($password, $users[$user]))) {
256 $_SERVER['CP_AUTH_USER'] = $user;
257 return;
258 }
259
260 $realm = &$options['realm'];
261 if (null === $realm) {
262 $realm = 'CP Login Required';
263 }
264
265 $message = &$options['fail_msg'];
266 if (null === $message) {
267 $message = self::$statusDescriptions[401];
268 }
269
270 $this->setResponseStatus(401)
271 ->setHeader('WWW-Authenticate: Basic realm="' . $realm . '"')
272 ->displayOver($message);
273 }
274
275 276 277 278 279 280
281 function digestAuth(array $users, array $options = array())
282 {
283 $realm = &$options['realm'];
284 if (null === $realm) {
285 $realm = 'CP Login Required';
286 }
287
288 $digest = &$_SERVER['PHP_AUTH_DIGEST'];
289 if ($digest) {
290 $data = $this->httpDigestParse($digest);
291 if (isset($data['username']) && isset($users[$data['username']])) {
292 $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
293 $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']);
294 $valid_response = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2);
295 if (0 === strcmp($valid_response, $data['response'])) {
296 $_SERVER['CP_AUTH_USER'] = $data['username'];
297 return;
298 }
299 }
300 }
301
302 $message = &$options['fail_msg'];
303 if (null === $message) {
304 $message = self::$statusDescriptions[401];
305 }
306
307 $nonce = &$options['nonce'];
308 if (null === $nonce) {
309 $nonce = uniqid();
310 }
311
312 $this->setResponseStatus(401)
313 ->setHeader('WWW-Authenticate: Digest realm="' . $realm .
314 '",qop="auth",nonce="' . $nonce . '",opaque="' . md5($realm) . '"')
315 ->displayOver($message);
316 }
317
318 319 320 321 322 323
324 function sendResponseStatus($code = 200, $descriptions = '')
325 {
326 if ($descriptions == '' && isset(self::$statusDescriptions[$code])) {
327 $descriptions = self::$statusDescriptions[$code];
328 }
329 header("HTTP/1.1 {$code} {$descriptions}");
330 }
331
332 333 334
335 function sendContentType()
336 {
337 $content_type_name = $this->getContentType();
338 if (isset(self::$mime_types [$content_type_name])) {
339 $content_type = self::$mime_types [$content_type_name];
340 } elseif ($content_type_name) {
341 $content_type = $content_type_name;
342 } else {
343 $content_type = self::$mime_types ['html'];
344 }
345
346 header("Content-Type: {$content_type}; charset=utf-8");
347 }
348
349 350 351 352 353
354 private function sendHeader()
355 {
356 $contents = $this->getHeader();
357 if (!empty($contents)) {
358 if (!is_array($contents)) {
359 $contents = array($contents);
360 }
361
362 foreach ($contents as $content) {
363 header($content);
364 }
365 }
366
367 return $this;
368 }
369
370 371 372 373 374
375 private function sendCookie()
376 {
377 if (!empty($this->cookie)) {
378 foreach ($this->cookie as $cookie) {
379 call_user_func_array('setcookie', $cookie);
380 }
381 }
382
383 return $this;
384 }
385
386 387 388
389 private function sendResponseHeader()
390 {
391 $this->sendContentType();
392 $this->sendHeader();
393 }
394
395 396 397 398 399 400 401
402 private function flushContent($message, $tpl = '')
403 {
404 if (null !== $tpl && is_file($tpl)) {
405 require $tpl;
406 } else {
407 echo $message;
408 }
409
410 return true;
411 }
412
413 414 415
416 function setEndFlush()
417 {
418 $this->is_end_flush = true;
419 return $this;
420 }
421
422 423 424 425 426
427 function isEndFlush()
428 {
429 return $this->is_end_flush;
430 }
431
432 433 434 435 436 437
438 function redirect($url, $status = 302)
439 {
440 $this->setResponseStatus($status)->setHeader("Location: {$url}")->displayOver();
441 exit(0);
442 }
443
444 445 446 447 448 449
450 function display($content = '', $tpl = '')
451 {
452 if (!headers_sent() && PHP_SAPI != 'cli') {
453 $code = $this->getResponseStatus();
454 $this->sendResponseStatus($code);
455 $this->sendResponseHeader();
456 $this->sendCookie();
457 }
458
459 $this->flushContent($content, $tpl);
460 }
461
462 463 464 465 466 467
468 function displayOver($content = '', $tpl = '')
469 {
470 $this->setEndFlush();
471 $this->display($content, $tpl);
472 }
473
474 475 476 477 478 479
480 private function httpDigestParse($txt)
481 {
482 $data = array();
483 $needed_parts = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);
484 $keys = implode('|', array_keys($needed_parts));
485
486 preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
487 foreach ($matches as $m) {
488 $data[$m[1]] = $m[3] ? $m[3] : $m[4];
489 unset($needed_parts[$m[1]]);
490 }
491
492 return $needed_parts ? false : $data;
493 }
494
495 496 497
498 static public $statusDescriptions = array(
499 100 => 'Continue',
500 101 => 'Switching Protocols',
501 200 => 'OK',
502 201 => 'Created',
503 202 => 'Accepted',
504 203 => 'Non-Authoritative Information',
505 204 => 'No Content',
506 205 => 'Reset Content',
507 206 => 'Partial Content',
508 300 => 'Multiple Choices',
509 301 => 'Moved Permanently',
510 302 => 'Found',
511 303 => 'See Other',
512 304 => 'Not Modified',
513 305 => 'Use Proxy',
514 307 => 'Temporary Redirect',
515 400 => 'Bad Request',
516 401 => 'Unauthorized',
517 403 => 'Forbidden',
518 404 => 'Not Found',
519 405 => 'Method Not Allowed',
520 406 => 'Not Acceptable',
521 407 => 'Proxy Authentication Required',
522 408 => 'Request Timeout',
523 409 => 'Conflict',
524 410 => 'Gone',
525 411 => 'Length Required',
526 412 => 'Precondition Failed',
527 413 => 'Request Entity Too Large',
528 414 => 'Request-URI Too Long',
529 415 => 'Unsupported Media Type',
530 416 => 'Requested Range Not Satisfiable',
531 417 => 'Expectation Failed',
532 500 => 'Internal Server Error',
533 501 => 'Not Implemented',
534 502 => 'Bad Gateway',
535 503 => 'Service Unavailable',
536 504 => 'Gateway Timeout',
537 505 => 'HTTP Version Not Supported'
538 );
539
540 541 542
543 static public $mime_types = array(
544 'ez' => 'application/andrew-inset',
545 'hqx' => 'application/mac-binhex40',
546 'cpt' => 'application/mac-compactpro',
547 'doc' => 'application/msword',
548 'bin' => 'application/octet-stream',
549 'dms' => 'application/octet-stream',
550 'lha' => 'application/octet-stream',
551 'lzh' => 'application/octet-stream',
552 'exe' => 'application/octet-stream',
553 'class' => 'application/octet-stream',
554 'so' => 'application/octet-stream',
555 'dll' => 'application/octet-stream',
556 'oda' => 'application/oda',
557 'pdf' => 'application/pdf',
558 'ai' => 'application/postscript',
559 'eps' => 'application/postscript',
560 'ps' => 'application/postscript',
561 'smi' => 'application/smil',
562 'smil' => 'application/smil',
563 'mif' => 'application/vnd.mif',
564 'xls' => 'application/vnd.ms-excel',
565 'ppt' => 'application/vnd.ms-powerpoint',
566 'wbxml' => 'application/vnd.wap.wbxml',
567 'wmlc' => 'application/vnd.wap.wmlc',
568 'wmlsc' => 'application/vnd.wap.wmlscriptc',
569 'bcpio' => 'application/x-bcpio',
570 'vcd' => 'application/x-cdlink',
571 'pgn' => 'application/x-chess-pgn',
572 'cpio' => 'application/x-cpio',
573 'csh' => 'application/x-csh',
574 'dcr' => 'application/x-director',
575 'dir' => 'application/x-director',
576 'dxr' => 'application/x-director',
577 'dvi' => 'application/x-dvi',
578 'spl' => 'application/x-futuresplash',
579 'gtar' => 'application/x-gtar',
580 'hdf' => 'application/x-hdf',
581 'js' => 'application/x-javascript',
582 'json' => 'application/json',
583 'skp' => 'application/x-koan',
584 'skd' => 'application/x-koan',
585 'skt' => 'application/x-koan',
586 'skm' => 'application/x-koan',
587 'latex' => 'application/x-latex',
588 'nc' => 'application/x-netcdf',
589 'cdf' => 'application/x-netcdf',
590 'sh' => 'application/x-sh',
591 'shar' => 'application/x-shar',
592 'swf' => 'application/x-shockwave-flash',
593 'sit' => 'application/x-stuffit',
594 'sv4cpio' => 'application/x-sv4cpio',
595 'sv4crc' => 'application/x-sv4crc',
596 'tar' => 'application/x-tar',
597 'tcl' => 'application/x-tcl',
598 'tex' => 'application/x-tex',
599 'texinfo' => 'application/x-texinfo',
600 'texi' => 'application/x-texinfo',
601 't' => 'application/x-troff',
602 'tr' => 'application/x-troff',
603 'roff' => 'application/x-troff',
604 'man' => 'application/x-troff-man',
605 'me' => 'application/x-troff-me',
606 'ms' => 'application/x-troff-ms',
607 'ustar' => 'application/x-ustar',
608 'src' => 'application/x-wais-source',
609 'xhtml' => 'application/xhtml+xml',
610 'xht' => 'application/xhtml+xml',
611 'zip' => 'application/zip',
612 'au' => 'audio/basic',
613 'snd' => 'audio/basic',
614 'mid' => 'audio/midi',
615 'midi' => 'audio/midi',
616 'kar' => 'audio/midi',
617 'mpga' => 'audio/mpeg',
618 'mp2' => 'audio/mpeg',
619 'mp3' => 'audio/mpeg',
620 'aif' => 'audio/x-aiff',
621 'aiff' => 'audio/x-aiff',
622 'aifc' => 'audio/x-aiff',
623 'm3u' => 'audio/x-mpegurl',
624 'ram' => 'audio/x-pn-realaudio',
625 'rm' => 'audio/x-pn-realaudio',
626 'rpm' => 'audio/x-pn-realaudio-plugin',
627 'ra' => 'audio/x-realaudio',
628 'wav' => 'audio/x-wav',
629 'pdb' => 'chemical/x-pdb',
630 'xyz' => 'chemical/x-xyz',
631 'bmp' => 'image/bmp',
632 'gif' => 'image/gif',
633 'ief' => 'image/ief',
634 'jpeg' => 'image/jpeg',
635 'jpg' => 'image/jpeg',
636 'jpe' => 'image/jpeg',
637 'png' => 'image/png',
638 'tiff' => 'image/tiff',
639 'tif' => 'image/tiff',
640 'djvu' => 'image/vnd.djvu',
641 'djv' => 'image/vnd.djvu',
642 'wbmp' => 'image/vnd.wap.wbmp',
643 'ras' => 'image/x-cmu-raster',
644 'pnm' => 'image/x-portable-anymap',
645 'pbm' => 'image/x-portable-bitmap',
646 'pgm' => 'image/x-portable-graymap',
647 'ppm' => 'image/x-portable-pixmap',
648 'rgb' => 'image/x-rgb',
649 'xbm' => 'image/x-xbitmap',
650 'xpm' => 'image/x-xpixmap',
651 'xwd' => 'image/x-xwindowdump',
652 'igs' => 'model/iges',
653 'iges' => 'model/iges',
654 'msh' => 'model/mesh',
655 'mesh' => 'model/mesh',
656 'silo' => 'model/mesh',
657 'wrl' => 'model/vrml',
658 'vrml' => 'model/vrml',
659 'css' => 'text/css',
660 'html' => 'text/html',
661 'htm' => 'text/html',
662 'asc' => 'text/plain',
663 'txt' => 'text/plain',
664 'rtx' => 'text/richtext',
665 'rtf' => 'text/rtf',
666 'sgml' => 'text/sgml',
667 'sgm' => 'text/sgml',
668 'tsv' => 'text/tab-separated-values',
669 'wml' => 'text/vnd.wap.wml',
670 'wmls' => 'text/vnd.wap.wmlscript',
671 'etx' => 'text/x-setext',
672 'xsl' => 'text/xml',
673 'xml' => 'text/xml',
674 'mpeg' => 'video/mpeg',
675 'mpg' => 'video/mpeg',
676 'mpe' => 'video/mpeg',
677 'qt' => 'video/quicktime',
678 'mov' => 'video/quicktime',
679 'mxu' => 'video/vnd.mpegurl',
680 'avi' => 'video/x-msvideo',
681 'movie' => 'video/x-sgi-movie',
682 'ice' => 'x-conference/x-cooltalk',
683 );
684 }
685
686