diff options
Diffstat (limited to '')
-rw-r--r-- | library/Icinga/Web/Response.php | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/library/Icinga/Web/Response.php b/library/Icinga/Web/Response.php new file mode 100644 index 0000000..555d3fa --- /dev/null +++ b/library/Icinga/Web/Response.php @@ -0,0 +1,460 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Web; + +use Icinga\Application\Config; +use Icinga\Util\Csp; +use Zend_Controller_Response_Http; +use Icinga\Application\Icinga; +use Icinga\Web\Response\JsonResponse; + +/** + * A HTTP response + */ +class Response extends Zend_Controller_Response_Http +{ + /** + * The default content type being used for responses + * + * @var string + */ + const DEFAULT_CONTENT_TYPE = 'text/html; charset=UTF-8'; + + /** + * Auto-refresh interval + * + * @var int + */ + protected $autoRefreshInterval; + + /** + * Set of cookies which are to be sent to the client + * + * @var CookieSet + */ + protected $cookies; + + /** + * Redirect URL + * + * @var Url|null + */ + protected $redirectUrl; + + /** + * Request + * + * @var Request + */ + protected $request; + + /** + * Whether to instruct the client to reload the window + * + * @var bool + */ + protected $reloadWindow; + + /** + * Whether to instruct client side script code to reload CSS + * + * @var bool + */ + protected $reloadCss; + + /** + * Whether to send the rerender layout header on XHR + * + * @var bool + */ + protected $rerenderLayout = false; + + /** + * Whether to send the current window ID to the client + * + * @var bool + */ + protected $overrideWindowId = false; + + /** + * Get the auto-refresh interval + * + * @return int + */ + public function getAutoRefreshInterval() + { + return $this->autoRefreshInterval; + } + + /** + * Set the auto-refresh interval + * + * @param int $autoRefreshInterval + * + * @return $this + */ + public function setAutoRefreshInterval($autoRefreshInterval) + { + $this->autoRefreshInterval = $autoRefreshInterval; + return $this; + } + + /** + * Get the set of cookies which are to be sent to the client + * + * @return CookieSet + */ + public function getCookies() + { + if ($this->cookies === null) { + $this->cookies = new CookieSet(); + } + return $this->cookies; + } + + /** + * Get the cookie with the given name from the set of cookies which are to be sent to the client + * + * @param string $name The name of the cookie + * + * @return Cookie|null The cookie with the given name or null if the cookie does not exist + */ + public function getCookie($name) + { + return $this->getCookies()->get($name); + } + + /** + * Set the given cookie for sending it to the client + * + * @param Cookie $cookie The cookie to send to the client + * + * @return $this + */ + public function setCookie(Cookie $cookie) + { + $this->getCookies()->add($cookie); + return $this; + } + + /** + * Get the redirect URL + * + * @return Url|null + */ + protected function getRedirectUrl() + { + return $this->redirectUrl; + } + + /** + * Set the redirect URL + * + * Unlike {@link setRedirect()} this method only sets a redirect URL on the response for later usage. + * {@link prepare()} will take care of the correct redirect handling and HTTP headers on XHR and "normal" browser + * requests. + * + * @param string|Url $redirectUrl + * + * @return $this + */ + protected function setRedirectUrl($redirectUrl) + { + if (! $redirectUrl instanceof Url) { + $redirectUrl = Url::fromPath((string) $redirectUrl); + } + $redirectUrl->getParams()->setSeparator('&'); + $this->redirectUrl = $redirectUrl; + return $this; + } + + /** + * Get an array of all header values for the given name + * + * @param string $name The name of the header + * @param bool $lastOnly If this is true, the last value will be returned as a string + * + * @return null|array|string + */ + public function getHeader($name, $lastOnly = false) + { + $result = ($lastOnly ? null : array()); + $headers = $this->getHeaders(); + foreach ($headers as $header) { + if ($header['name'] === $name) { + if ($lastOnly) { + $result = $header['value']; + } else { + $result[] = $header['value']; + } + } + } + + return $result; + } + + /** + * Get the request + * + * @return Request + */ + public function getRequest() + { + if ($this->request === null) { + $this->request = Icinga::app()->getRequest(); + } + return $this->request; + } + + /** + * Get whether to instruct the client to reload the window + * + * @return bool + */ + public function isWindowReloaded() + { + return $this->reloadWindow; + } + + /** + * Set whether to instruct the client to reload the window + * + * @param bool $reloadWindow + * + * @return $this + */ + public function setReloadWindow($reloadWindow) + { + $this->reloadWindow = $reloadWindow; + + return $this; + } + + /** + * Get whether to instruct client side script code to reload CSS + * + * @return bool + */ + public function isReloadCss() + { + return $this->reloadCss; + } + + /** + * Set whether to instruct client side script code to reload CSS + * + * @param bool $reloadCss + * + * @return $this + */ + public function setReloadCss($reloadCss) + { + $this->reloadCss = $reloadCss; + return $this; + } + + /** + * Get whether to send the rerender layout header on XHR + * + * @return bool + */ + public function getRerenderLayout() + { + return $this->rerenderLayout; + } + + /** + * Get whether to send the rerender layout header on XHR + * + * @param bool $rerenderLayout + * + * @return $this + */ + public function setRerenderLayout($rerenderLayout = true) + { + $this->rerenderLayout = (bool) $rerenderLayout; + return $this; + } + + /** + * Get whether to send the current window ID to the client + * + * @return bool + */ + public function getOverrideWindowId() + { + return $this->overrideWindowId; + } + + /** + * Set whether to send the current window ID to the client + * + * @param bool $overrideWindowId + * + * @return $this + */ + public function setOverrideWindowId($overrideWindowId = true) + { + $this->overrideWindowId = $overrideWindowId; + return $this; + } + + /** + * Entry point for HTTP responses in JSON format + * + * @return JsonResponse + */ + public function json() + { + $response = new JsonResponse(); + $response->copyMetaDataFrom($this); + return $response; + } + + /** + * Prepare the request before sending + */ + protected function prepare() + { + $request = $this->getRequest(); + $redirectUrl = $this->getRedirectUrl(); + if ($request->isXmlHttpRequest()) { + if ($redirectUrl !== null) { + if ($request->isGet() && Icinga::app()->getViewRenderer()->view->compact) { + if ($redirectUrl->getParam('redirect') !== '__SELF__') { + $redirectUrl->getParams()->set('showCompact', true); + } + } + + $encodedRedirectUrl = rawurlencode($redirectUrl->getAbsoluteUrl()); + + // TODO: Compatibility only. Remove once v2.14 is out. + $targetId = $request->getHeader('X-Icinga-Container'); + $redirectTargetId = $this->getHeader('X-Icinga-Container', true) ?? $targetId; + if ($request->isPost() + && ! $this->getRerenderLayout() + && $targetId === 'col2' + && $redirectTargetId === $targetId + && $request->getHeader('X-Icinga-Col2-State') + ) { + $col1State = Url::fromPath($request->getHeader('X-Icinga-Col1-State')); + $col2State = Url::fromPath($request->getHeader('X-Icinga-Col2-State')); + if ($col2State->getPath() !== $redirectUrl->getPath() + && $col1State->getPath() === $redirectUrl->getPath() + ) { + $encodedRedirectUrl = '__CLOSE__'; + } + } + + $this->setHeader('X-Icinga-Redirect', $encodedRedirectUrl, true); + if ($this->getRerenderLayout()) { + $this->setHeader('X-Icinga-Rerender-Layout', 'yes', true); + } + } + if ($this->getOverrideWindowId()) { + $this->setHeader('X-Icinga-WindowId', Window::getInstance()->getId(), true); + } + if ($this->getRerenderLayout()) { + $this->setHeader('X-Icinga-Container', 'layout', true); + } + if ($this->isWindowReloaded()) { + $this->setHeader('X-Icinga-Reload-Window', 'yes', true); + } + if ($this->isReloadCss()) { + $this->setHeader('X-Icinga-Reload-Css', 'now', true); + } + if (($autoRefreshInterval = $this->getAutoRefreshInterval()) !== null) { + $this->setHeader('X-Icinga-Refresh', $autoRefreshInterval, true); + } + + $notifications = Notification::getInstance(); + if ($notifications->hasMessages()) { + $notificationList = array(); + foreach ($notifications->popMessages() as $m) { + $notificationList[] = rawurlencode($m->type . ' ' . $m->message); + } + $this->setHeader('X-Icinga-Notification', implode('&', $notificationList), true); + } + } else { + if ($redirectUrl !== null) { + $this->setRedirect($redirectUrl->getAbsoluteUrl()); + } + + if (Csp::getStyleNonce() && Config::app()->get('security', 'use_strict_csp', false)) { + Csp::addHeader($this); + } + } + + if (! $this->getHeader('Content-Type', true)) { + $this->setHeader('Content-Type', static::DEFAULT_CONTENT_TYPE); + } + } + + /** + * Redirect to the given URL and exit immediately + * + * @param string|Url $url + * + * @return never + */ + public function redirectAndExit($url) + { + $this->setRedirectUrl($url); + + $session = Session::getSession(); + if ($session->hasChanged()) { + $session->write(); + } + + $this->sendHeaders(); + exit; + } + + /** + * Send the cookies to the client + */ + public function sendCookies() + { + foreach ($this->getCookies() as $cookie) { + /** @var Cookie $cookie */ + setcookie( + $cookie->getName(), + $cookie->getValue() ?? '', + $cookie->getExpire() ?? 0, + $cookie->getPath(), + $cookie->getDomain() ?? '', + $cookie->isSecure(), + $cookie->isHttpOnly() ?? true + ); + } + } + + /** + * {@inheritdoc} + */ + public function sendHeaders() + { + $this->prepare(); + if (! $this->getRequest()->isApiRequest()) { + $this->sendCookies(); + } + return parent::sendHeaders(); + } + + /** + * Copies non-body-related response data from $response + * + * @param Response $response + * + * @return $this + */ + protected function copyMetaDataFrom(self $response) + { + $this->_headers = $response->_headers; + $this->_headersRaw = $response->_headersRaw; + $this->_httpResponseCode = $response->_httpResponseCode; + $this->headersSentThrowsException = $response->headersSentThrowsException; + return $this; + } +} |