]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/Web/THttpResponse.php
baculum: New Baculum API and Baculum Web
[bacula/bacula] / gui / baculum / framework / Web / THttpResponse.php
1 <?php
2 /**
3  * THttpResponse class
4  *
5  * @author Qiang Xue <qiang.xue@gmail.com>
6  * @link https://github.com/pradosoft/prado
7  * @copyright Copyright &copy; 2005-2016 The PRADO Group
8  * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
9  * @package System.Web
10  */
11
12 /**
13  * Includes the THttpResponse adapter.
14  */
15 Prado::using('System.Web.THttpResponseAdapter');
16
17 /**
18  * THttpResponse class
19  *
20  * THttpResponse implements the mechanism for sending output to client users.
21  *
22  * To output a string to client, use {@link write()}. By default, the output is
23  * buffered until {@link flush()} is called or the application ends. The output in
24  * the buffer can also be cleaned by {@link clear()}. To disable output buffering,
25  * set BufferOutput property to false.
26  *
27  * To send cookies to client, use {@link getCookies()}.
28  * To redirect client browser to a new URL, use {@link redirect()}.
29  * To send a file to client, use {@link writeFile()}.
30  *
31  * By default, THttpResponse is registered with {@link TApplication} as the
32  * response module. It can be accessed via {@link TApplication::getResponse()}.
33  *
34  * THttpResponse may be configured in application configuration file as follows
35  *
36  * <module id="response" class="System.Web.THttpResponse" CacheExpire="20" CacheControl="nocache" BufferOutput="true" />
37  *
38  * where {@link getCacheExpire CacheExpire}, {@link getCacheControl CacheControl}
39  * and {@link getBufferOutput BufferOutput} are optional properties of THttpResponse.
40  *
41  * THttpResponse sends charset header if either {@link setCharset() Charset}
42  * or {@link TGlobalization::setCharset() TGlobalization.Charset} is set.
43  *
44  * Since 3.1.2, HTTP status code can be set with the {@link setStatusCode StatusCode} property.
45  *
46  * Note: Some HTTP Status codes can require additional header or body information. So, if you use {@link setStatusCode StatusCode}
47  * in your application, be sure to add theses informations.
48  * E.g : to make an http authentication :
49  * <code>
50  *  public function clickAuth ($sender, $param)
51  *  {
52  *     $response=$this->getResponse();
53  *     $response->setStatusCode(401);
54  *     $response->appendHeader('WWW-Authenticate: Basic realm="Test"');
55  *  }
56  * </code>
57  *
58  * This event handler will sent the 401 status code (Unauthorized) to the browser, with the WWW-Authenticate header field. This
59  * will force the browser to ask for a username and a password.
60  *
61  * @author Qiang Xue <qiang.xue@gmail.com>
62  * @package System.Web
63  * @since 3.0
64  */
65 class THttpResponse extends TModule implements ITextWriter
66 {
67         const DEFAULT_CONTENTTYPE       = 'text/html';
68         const DEFAULT_CHARSET           = 'UTF-8';
69
70         /**
71          * @var The differents defined status code by RFC 2616 {@link http://www.faqs.org/rfcs/rfc2616}
72          */
73         private static $HTTP_STATUS_CODES = array(
74                 100 => 'Continue', 101 => 'Switching Protocols',
75                 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
76                 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
77                 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 Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
78                 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
79         );
80
81         /**
82          * @var boolean whether to buffer output
83          */
84         private $_bufferOutput=true;
85         /**
86          * @var boolean if the application is initialized
87          */
88         private $_initialized=false;
89         /**
90          * @var THttpCookieCollection list of cookies to return
91          */
92         private $_cookies=null;
93         /**
94          * @var integer response status code
95          */
96         private $_status=200;
97         /**
98          * @var string reason correspond to status code
99          */
100         private $_reason='OK';
101         /**
102          * @var string HTML writer type
103          */
104         private $_htmlWriterType='System.Web.UI.THtmlWriter';
105         /**
106          * @var string content type
107          */
108         private $_contentType=null;
109         /**
110          * @var string|boolean character set, e.g. UTF-8 or false if no character set should be send to client
111          */
112         private $_charset='';
113         /**
114          * @var THttpResponseAdapter adapter.
115          */
116         private $_adapter;
117         /**
118          * @var boolean whether http response header has been sent
119          */
120         private $_httpHeaderSent;
121         /**
122          * @var boolean whether content-type header has been sent
123          */
124         private $_contentTypeHeaderSent;
125
126         /**
127          * Destructor.
128          * Flushes any existing content in buffer.
129          */
130         public function __destruct()
131         {
132                 //if($this->_bufferOutput)
133                 //      @ob_end_flush();
134         }
135
136         /**
137          * @param THttpResponseAdapter response adapter
138          */
139         public function setAdapter(THttpResponseAdapter $adapter)
140         {
141                 $this->_adapter=$adapter;
142         }
143
144         /**
145          * @return THttpResponseAdapter response adapter, null if not exist.
146          */
147         public function getAdapter()
148         {
149                 return $this->_adapter;
150         }
151
152         /**
153          * @return boolean true if adapter exists, false otherwise.
154          */
155         public function getHasAdapter()
156         {
157                 return $this->_adapter!==null;
158         }
159
160         /**
161          * Initializes the module.
162          * This method is required by IModule and is invoked by application.
163          * It starts output buffer if it is enabled.
164          * @param TXmlElement module configuration
165          */
166         public function init($config)
167         {
168                 if($this->_bufferOutput)
169                         ob_start();
170                 $this->_initialized=true;
171                 $this->getApplication()->setResponse($this);
172         }
173
174         /**
175          * @return integer time-to-live for cached session pages in minutes, this has no effect for nocache limiter. Defaults to 180.
176          */
177         public function getCacheExpire()
178         {
179                 return session_cache_expire();
180         }
181
182         /**
183          * @param integer time-to-live for cached session pages in minutes, this has no effect for nocache limiter.
184          */
185         public function setCacheExpire($value)
186         {
187                 session_cache_expire(TPropertyValue::ensureInteger($value));
188         }
189
190         /**
191          * @return string cache control method to use for session pages
192          */
193         public function getCacheControl()
194         {
195                 return session_cache_limiter();
196         }
197
198         /**
199          * @param string cache control method to use for session pages. Valid values
200          *               include none/nocache/private/private_no_expire/public
201          */
202         public function setCacheControl($value)
203         {
204                 session_cache_limiter(TPropertyValue::ensureEnum($value,array('none','nocache','private','private_no_expire','public')));
205         }
206
207         /**
208          * @return string content type, default is text/html
209          */
210         public function setContentType($type)
211         {
212                 if ($this->_contentTypeHeaderSent)
213                         throw new Exception('Unable to alter content-type as it has been already sent');
214                 $this->_contentType = $type;
215         }
216
217         /**
218          * @return string current content type
219          */
220         public function getContentType()
221         {
222                 return $this->_contentType;
223         }
224
225         /**
226          * @return string|boolean output charset.
227          */
228         public function getCharset()
229         {
230                 return $this->_charset;
231         }
232
233         /**
234          * @param string|boolean output charset.
235          */
236         public function setCharset($charset)
237         {
238                 $this->_charset = (strToLower($charset) === 'false') ? false : (string)$charset;
239         }
240
241         /**
242          * @return boolean whether to enable output buffer
243          */
244         public function getBufferOutput()
245         {
246                 return $this->_bufferOutput;
247         }
248
249         /**
250          * @param boolean whether to enable output buffer
251          * @throws TInvalidOperationException if session is started already
252          */
253         public function setBufferOutput($value)
254         {
255                 if($this->_initialized)
256                         throw new TInvalidOperationException('httpresponse_bufferoutput_unchangeable');
257                 else
258                         $this->_bufferOutput=TPropertyValue::ensureBoolean($value);
259         }
260
261         /**
262          * @return integer HTTP status code, defaults to 200
263          */
264         public function getStatusCode()
265         {
266                 return $this->_status;
267         }
268
269         /**
270          * Set the HTTP status code for the response.
271          * The code and its reason will be sent to client using the currently requested http protocol version (see {@link THttpRequest::getHttpProtocolVersion})
272          * Keep in mind that HTTP/1.0 clients might not understand all status codes from HTTP/1.1
273          *
274          * @param integer HTTP status code
275          * @param string HTTP status reason, defaults to standard HTTP reasons
276          */
277         public function setStatusCode($status, $reason=null)
278         {
279                 if ($this->_httpHeaderSent)
280                         throw new Exception('Unable to alter response as HTTP header already sent');
281                 $status=TPropertyValue::ensureInteger($status);
282                 if(isset(self::$HTTP_STATUS_CODES[$status])) {
283                         $this->_reason=self::$HTTP_STATUS_CODES[$status];
284                 }else{
285                         if($reason===null || $reason==='') {
286                                 throw new TInvalidDataValueException("response_status_reason_missing");
287                         }
288                         $reason=TPropertyValue::ensureString($reason);
289                         if(strpos($reason, "\r")!=false || strpos($reason, "\n")!=false) {
290                                 throw new TInvalidDataValueException("response_status_reason_barchars");
291                         }
292                         $this->_reason=$reason;
293                 }
294                 $this->_status=$status;
295         }
296
297         /**
298          * @param string HTTP status reason
299          */
300         public function getStatusReason() {
301                 return $this->_reason;
302         }
303
304         /**
305          * @return THttpCookieCollection list of output cookies
306          */
307         public function getCookies()
308         {
309                 if($this->_cookies===null)
310                         $this->_cookies=new THttpCookieCollection($this);
311                 return $this->_cookies;
312         }
313
314         /**
315          * Outputs a string.
316          * It may not be sent back to user immediately if output buffer is enabled.
317          * @param string string to be output
318          */
319         public function write($str)
320         {
321                 // when starting output make sure we send the headers first
322                 if (!$this->_bufferOutput and !$this->_httpHeaderSent)
323                         $this->ensureHeadersSent();
324                 echo $str;
325         }
326
327         /**
328          * Sends a file back to user.
329          * Make sure not to output anything else after calling this method.
330          * @param string file name
331          * @param string content to be set. If null, the content will be read from the server file pointed to by $fileName.
332          * @param string mime type of the content.
333          * @param array list of headers to be sent. Each array element represents a header string (e.g. 'Content-Type: text/plain').
334          * @param boolean force download of file, even if browser able to display inline. Defaults to 'true'.
335          * @param string force a specific file name on client side. Defaults to 'null' means auto-detect.
336          * @param integer size of file or content in bytes if already known. Defaults to 'null' means auto-detect.
337          * @throws TInvalidDataValueException if the file cannot be found
338          */
339         public function writeFile($fileName,$content=null,$mimeType=null,$headers=null,$forceDownload=true,$clientFileName=null,$fileSize=null)
340         {
341                 static $defaultMimeTypes=array(
342                         'css'=>'text/css',
343                         'gif'=>'image/gif',
344                         'png'=>'image/png',
345                         'jpg'=>'image/jpeg',
346                         'jpeg'=>'image/jpeg',
347                         'htm'=>'text/html',
348                         'html'=>'text/html',
349                         'js'=>'javascript/js',
350                         'pdf'=>'application/pdf',
351                         'xls'=>'application/vnd.ms-excel',
352                 );
353
354                 if($mimeType===null)
355                 {
356                         $mimeType='text/plain';
357                         if(function_exists('mime_content_type'))
358                                 $mimeType=mime_content_type($fileName);
359                         else if(($ext=strrchr($fileName,'.'))!==false)
360                         {
361                                 $ext=substr($ext,1);
362                                 if(isset($defaultMimeTypes[$ext]))
363                                         $mimeType=$defaultMimeTypes[$ext];
364                         }
365                 }
366
367                 if($clientFileName===null)
368                         $clientFileName=basename($fileName);
369                 else
370                         $clientFileName=basename($clientFileName);
371
372                 if($fileSize===null || $fileSize < 0)
373                         $fileSize = ($content===null?filesize($fileName):strlen($content));
374
375                 $this->sendHttpHeader();
376                 if(is_array($headers))
377                 {
378                         foreach($headers as $h)
379                                 header($h);
380                 }
381                 else
382                 {
383                         header('Pragma: public');
384                         header('Expires: 0');
385                         header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
386                         header("Content-Type: $mimeType");
387                         $this->_contentTypeHeaderSent = true;
388                 }
389
390                 header('Content-Length: '.$fileSize);
391                 header("Content-Disposition: " . ($forceDownload ? 'attachment' : 'inline') . "; filename=\"$clientFileName\"");
392                 header('Content-Transfer-Encoding: binary');
393                 if($content===null)
394                         readfile($fileName);
395                 else
396                         echo $content;
397         }
398
399         /**
400          * Redirects the browser to the specified URL.
401          * The current application will be terminated after this method is invoked.
402          * @param string URL to be redirected to. If the URL is a relative one, the base URL of
403          * the current request will be inserted at the beginning.
404          */
405         public function redirect($url)
406         {
407                 if($this->getHasAdapter())
408                         $this->_adapter->httpRedirect($url);
409                 else
410                         $this->httpRedirect($url);
411         }
412
413         /**
414          * Redirect the browser to another URL and exists the current application.
415          * This method is used internally. Please use {@link redirect} instead.
416          *
417          * @since 3.1.5
418          * You can set the set {@link setStatusCode StatusCode} to a value between 300 and 399 before
419          * calling this function to change the type of redirection.
420          * If not specified, StatusCode will be 302 (Found) by default
421          *
422          * @param string URL to be redirected to. If the URL is a relative one, the base URL of
423          * the current request will be inserted at the beginning.
424          */
425         public function httpRedirect($url)
426         {
427                 $this->ensureHeadersSent();
428
429                 if($url[0]==='/')
430                         $url=$this->getRequest()->getBaseUrl().$url;
431                 if ($this->_status >= 300 && $this->_status < 400)
432                         // The status code has been modified to a valid redirection status, send it
433                         header('Location: '.str_replace('&amp;','&',$url), true, $this->_status);
434                 else
435                         header('Location: '.str_replace('&amp;','&',$url));
436
437                 if(!$this->getApplication()->getRequestCompleted())
438                         $this->getApplication()->onEndRequest();
439
440                 exit();
441         }
442
443         /**
444          * Reloads the current page.
445          * The effect of this method call is the same as user pressing the
446          * refresh button on his browser (without post data).
447          **/
448         public function reload()
449         {
450                 $this->redirect($this->getRequest()->getRequestUri());
451         }
452
453         /**
454          * Flush the response contents and headers.
455          */
456         public function flush($continueBuffering = true)
457         {
458                 if($this->getHasAdapter())
459                         $this->_adapter->flushContent($continueBuffering);
460                 else
461                         $this->flushContent($continueBuffering);
462         }
463
464         /**
465          * Ensures that HTTP response and content-type headers are sent
466          */
467         public function ensureHeadersSent()
468         {
469                 $this->ensureHttpHeaderSent();
470                 $this->ensureContentTypeHeaderSent();
471         }
472
473         /**
474          * Outputs the buffered content, sends content-type and charset header.
475          * This method is used internally. Please use {@link flush} instead.
476          * @param boolean whether to continue buffering after flush if buffering was active
477          */
478         public function flushContent($continueBuffering = true)
479         {
480                 Prado::trace("Flushing output",'System.Web.THttpResponse');
481                 $this->ensureHeadersSent();
482                 if($this->_bufferOutput)
483                 {
484                         // avoid forced send of http headers (ob_flush() does that) if there's no output yet
485                         if (ob_get_length()>0)
486                         {
487                                 if (!$continueBuffering)
488                                 {
489                                         $this->_bufferOutput = false;
490                                         ob_end_flush();
491                                 }
492                                 else
493                                         ob_flush();
494                                 flush();
495                         }
496                 }
497                 else
498                         flush();
499         }
500
501         /**
502          * Ensures that the HTTP header with the status code and status reason are sent
503          */
504         protected function ensureHttpHeaderSent()
505         {
506                 if (!$this->_httpHeaderSent)
507                         $this->sendHttpHeader();
508         }
509
510         /**
511          * Send the HTTP header with the status code (defaults to 200) and status reason (defaults to OK)
512          */
513         protected function sendHttpHeader()
514         {
515                 $protocol=$this->getRequest()->getHttpProtocolVersion();
516                 if($this->getRequest()->getHttpProtocolVersion() === null)
517                         $protocol='HTTP/1.1';
518
519                 $phpSapiName = substr(php_sapi_name(), 0, 3);
520                 $cgi = $phpSapiName == 'cgi' || $phpSapiName == 'fpm';
521
522                 header(($cgi ? 'Status:' : $protocol).' '.$this->_status.' '.$this->_reason, true, TPropertyValue::ensureInteger($this->_status));
523
524                 $this->_httpHeaderSent = true;
525         }
526
527         /**
528          * Ensures that the HTTP header with the status code and status reason are sent
529          */
530         protected function ensureContentTypeHeaderSent()
531         {
532                 if (!$this->_contentTypeHeaderSent)
533                         $this->sendContentTypeHeader();
534         }
535
536         /**
537          * Sends content type header with optional charset.
538          */
539         protected function sendContentTypeHeader()
540         {
541                 $contentType=$this->_contentType===null?self::DEFAULT_CONTENTTYPE:$this->_contentType;
542                 $charset=$this->getCharset();
543                 if($charset === false) {
544                         $this->appendHeader('Content-Type: '.$contentType);
545                         return;
546                 }
547
548                 if($charset==='' && ($globalization=$this->getApplication()->getGlobalization(false))!==null)
549                         $charset=$globalization->getCharset();
550
551                 if($charset==='') $charset = self::DEFAULT_CHARSET;
552                 $this->appendHeader('Content-Type: '.$contentType.';charset='.$charset);
553
554                 $this->_contentTypeHeaderSent = true;
555         }
556
557         /**
558          * Returns the content in the output buffer.
559          * The buffer will NOT be cleared after calling this method.
560          * Use {@link clear()} is you want to clear the buffer.
561          * @return string output that is in the buffer.
562          */
563         public function getContents()
564         {
565                 Prado::trace("Retrieving output",'System.Web.THttpResponse');
566                 return $this->_bufferOutput?ob_get_contents():'';
567         }
568
569         /**
570          * Clears any existing buffered content.
571          */
572         public function clear()
573         {
574                 if($this->_bufferOutput)
575                         ob_clean();
576                 Prado::trace("Clearing output",'System.Web.THttpResponse');
577         }
578
579         /**
580          * @param integer|null Either {@link CASE_UPPER} or {@link CASE_LOWER} or as is null (default)
581          * @return array
582          */
583         public function getHeaders($case=null)
584         {
585                 $result = array();
586                 $headers = headers_list();
587                 foreach($headers as $header) {
588                         $tmp = explode(':', $header);
589                         $key = trim(array_shift($tmp));
590                         $value = trim(implode(':', $tmp));
591                         if(isset($result[$key]))
592                                 $result[$key] .= ', ' . $value;
593                         else
594                                 $result[$key] = $value;
595                 }
596
597                 if($case !== null)
598                         return array_change_key_case($result, $case);
599
600                 return $result;
601         }
602
603         /**
604          * Sends a header.
605          * @param string header
606          * @param boolean whether the header should replace a previous similar header, or add a second header of the same type
607          */
608         public function appendHeader($value, $replace=true)
609         {
610                 Prado::trace("Sending header '$value'",'System.Web.THttpResponse');
611                 header($value, $replace);
612         }
613
614         /**
615          * Writes a log message into error log.
616          * This method is simple wrapper of PHP function error_log.
617          * @param string The error message that should be logged
618          * @param integer where the error should go
619          * @param string The destination. Its meaning depends on the message parameter as described above
620          * @param string The extra headers. It's used when the message parameter is set to 1. This message type uses the same internal function as mail() does.
621          * @see http://us2.php.net/manual/en/function.error-log.php
622          */
623         public function appendLog($message,$messageType=0,$destination='',$extraHeaders='')
624         {
625                 error_log($message,$messageType,$destination,$extraHeaders);
626         }
627
628         /**
629          * Sends a cookie.
630          * Do not call this method directly. Operate with the result of {@link getCookies} instead.
631          * @param THttpCookie cook to be sent
632          */
633         public function addCookie($cookie)
634         {
635                 $request=$this->getRequest();
636                 if($request->getEnableCookieValidation())
637                 {
638                         $value=$this->getApplication()->getSecurityManager()->hashData($cookie->getValue());
639                         setcookie(
640                                 $cookie->getName(),
641                                 $value,
642                                 $cookie->getExpire(),
643                                 $cookie->getPath(),
644                                 $cookie->getDomain(),
645                                 $cookie->getSecure(),
646                                 $cookie->getHttpOnly()
647                         );
648                 }
649                 else {
650                         setcookie(
651                                 $cookie->getName(),
652                                 $cookie->getValue(),
653                                 $cookie->getExpire(),
654                                 $cookie->getPath(),
655                                 $cookie->getDomain(),
656                                 $cookie->getSecure(),
657                                 $cookie->getHttpOnly()
658                         );
659                 }
660         }
661
662         /**
663          * Deletes a cookie.
664          * Do not call this method directly. Operate with the result of {@link getCookies} instead.
665          * @param THttpCookie cook to be deleted
666          */
667         public function removeCookie($cookie)
668         {
669                 setcookie(
670                         $cookie->getName(),
671                         null,
672                         0,
673                         $cookie->getPath(),
674                         $cookie->getDomain(),
675                         $cookie->getSecure(),
676                         $cookie->getHttpOnly()
677                 );
678         }
679
680         /**
681          * @return string the type of HTML writer to be used, defaults to THtmlWriter
682          */
683         public function getHtmlWriterType()
684         {
685                 return $this->_htmlWriterType;
686         }
687
688         /**
689          * @param string the type of HTML writer to be used, may be the class name or the namespace
690          */
691         public function setHtmlWriterType($value)
692         {
693                 $this->_htmlWriterType=$value;
694         }
695
696         /**
697          * Creates a new instance of HTML writer.
698          * If the type of the HTML writer is not supplied, {@link getHtmlWriterType HtmlWriterType} will be assumed.
699          * @param string type of the HTML writer to be created. If null, {@link getHtmlWriterType HtmlWriterType} will be assumed.
700          */
701         public function createHtmlWriter($type=null)
702         {
703                 if($type===null)
704                         $type=$this->getHtmlWriterType();
705                 if($this->getHasAdapter())
706                         return $this->_adapter->createNewHtmlWriter($type, $this);
707                 else
708                         return $this->createNewHtmlWriter($type, $this);
709         }
710
711         /**
712          * Create a new html writer instance.
713          * This method is used internally. Please use {@link createHtmlWriter} instead.
714          * @param string type of HTML writer to be created.
715          * @param ITextWriter text writer holding the contents.
716          */
717         public function createNewHtmlWriter($type, $writer)
718         {
719                 return Prado::createComponent($type, $writer);
720         }
721 }
722