Response.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Clement Wong <git@clement.hk>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  10. * @author Lukas Reschke <lukas@statuscode.ch>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author Roeland Jago Douma <roeland@famdouma.nl>
  13. * @author Thomas Müller <thomas.mueller@tmit.eu>
  14. * @author Thomas Tanghus <thomas@tanghus.net>
  15. *
  16. * @license AGPL-3.0
  17. *
  18. * This code is free software: you can redistribute it and/or modify
  19. * it under the terms of the GNU Affero General Public License, version 3,
  20. * as published by the Free Software Foundation.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU Affero General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU Affero General Public License, version 3,
  28. * along with this program. If not, see <http://www.gnu.org/licenses/>
  29. *
  30. */
  31. namespace OCP\AppFramework\Http;
  32. use OCP\AppFramework\Http;
  33. use OCP\AppFramework\Utility\ITimeFactory;
  34. use OCP\IConfig;
  35. use OCP\IRequest;
  36. use Psr\Log\LoggerInterface;
  37. /**
  38. * Base class for responses. Also used to just send headers.
  39. *
  40. * It handles headers, HTTP status code, last modified and ETag.
  41. * @since 6.0.0
  42. */
  43. class Response {
  44. /**
  45. * Headers - defaults to ['Cache-Control' => 'no-cache, no-store, must-revalidate']
  46. * @var array
  47. */
  48. private $headers = [
  49. 'Cache-Control' => 'no-cache, no-store, must-revalidate'
  50. ];
  51. /**
  52. * Cookies that will be need to be constructed as header
  53. * @var array
  54. */
  55. private $cookies = [];
  56. /**
  57. * HTTP status code - defaults to STATUS OK
  58. * @var int
  59. */
  60. private $status = Http::STATUS_OK;
  61. /**
  62. * Last modified date
  63. * @var \DateTime
  64. */
  65. private $lastModified;
  66. /**
  67. * ETag
  68. * @var string
  69. */
  70. private $ETag;
  71. /** @var ContentSecurityPolicy|null Used Content-Security-Policy */
  72. private $contentSecurityPolicy = null;
  73. /** @var FeaturePolicy */
  74. private $featurePolicy;
  75. /** @var bool */
  76. private $throttled = false;
  77. /** @var array */
  78. private $throttleMetadata = [];
  79. /**
  80. * @since 17.0.0
  81. */
  82. public function __construct() {
  83. /** @var IRequest $request */
  84. /**
  85. * @psalm-suppress UndefinedClass
  86. */
  87. $request = \OC::$server->get(IRequest::class);
  88. $this->addHeader("X-Request-Id", $request->getId());
  89. }
  90. /**
  91. * Caches the response
  92. *
  93. * @param int $cacheSeconds amount of seconds the response is fresh, 0 to disable cache.
  94. * @param bool $public whether the page should be cached by public proxy. Usually should be false, unless this is a static resources.
  95. * @param bool $immutable whether browser should treat the resource as immutable and not ask the server for each page load if the resource changed.
  96. * @return $this
  97. * @since 6.0.0 - return value was added in 7.0.0
  98. */
  99. public function cacheFor(int $cacheSeconds, bool $public = false, bool $immutable = false) {
  100. if ($cacheSeconds > 0) {
  101. $pragma = $public ? 'public' : 'private';
  102. $this->addHeader('Cache-Control', sprintf('%s, max-age=%s, %s', $pragma, $cacheSeconds, ($immutable ? 'immutable' : 'must-revalidate')));
  103. $this->addHeader('Pragma', $pragma);
  104. // Set expires header
  105. $expires = new \DateTime();
  106. /** @var ITimeFactory $time */
  107. $time = \OC::$server->query(ITimeFactory::class);
  108. $expires->setTimestamp($time->getTime());
  109. $expires->add(new \DateInterval('PT'.$cacheSeconds.'S'));
  110. $this->addHeader('Expires', $expires->format(\DateTimeInterface::RFC2822));
  111. } else {
  112. $this->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
  113. unset($this->headers['Expires'], $this->headers['Pragma']);
  114. }
  115. return $this;
  116. }
  117. /**
  118. * Adds a new cookie to the response
  119. * @param string $name The name of the cookie
  120. * @param string $value The value of the cookie
  121. * @param \DateTime|null $expireDate Date on that the cookie should expire, if set
  122. * to null cookie will be considered as session
  123. * cookie.
  124. * @param string $sameSite The samesite value of the cookie. Defaults to Lax. Other possibilities are Strict or None
  125. * @return $this
  126. * @since 8.0.0
  127. */
  128. public function addCookie($name, $value, \DateTime $expireDate = null, $sameSite = 'Lax') {
  129. $this->cookies[$name] = ['value' => $value, 'expireDate' => $expireDate, 'sameSite' => $sameSite];
  130. return $this;
  131. }
  132. /**
  133. * Set the specified cookies
  134. * @param array $cookies array('foo' => array('value' => 'bar', 'expire' => null))
  135. * @return $this
  136. * @since 8.0.0
  137. */
  138. public function setCookies(array $cookies) {
  139. $this->cookies = $cookies;
  140. return $this;
  141. }
  142. /**
  143. * Invalidates the specified cookie
  144. * @param string $name
  145. * @return $this
  146. * @since 8.0.0
  147. */
  148. public function invalidateCookie($name) {
  149. $this->addCookie($name, 'expired', new \DateTime('1971-01-01 00:00'));
  150. return $this;
  151. }
  152. /**
  153. * Invalidates the specified cookies
  154. * @param array $cookieNames array('foo', 'bar')
  155. * @return $this
  156. * @since 8.0.0
  157. */
  158. public function invalidateCookies(array $cookieNames) {
  159. foreach ($cookieNames as $cookieName) {
  160. $this->invalidateCookie($cookieName);
  161. }
  162. return $this;
  163. }
  164. /**
  165. * Returns the cookies
  166. * @return array
  167. * @since 8.0.0
  168. */
  169. public function getCookies() {
  170. return $this->cookies;
  171. }
  172. /**
  173. * Adds a new header to the response that will be called before the render
  174. * function
  175. * @param string $name The name of the HTTP header
  176. * @param string $value The value, null will delete it
  177. * @return $this
  178. * @since 6.0.0 - return value was added in 7.0.0
  179. */
  180. public function addHeader($name, $value) {
  181. $name = trim($name); // always remove leading and trailing whitespace
  182. // to be able to reliably check for security
  183. // headers
  184. if ($this->status === Http::STATUS_NOT_MODIFIED
  185. && stripos($name, 'x-') === 0) {
  186. /** @var IConfig $config */
  187. $config = \OC::$server->get(IConfig::class);
  188. if ($config->getSystemValueBool('debug', false)) {
  189. \OC::$server->get(LoggerInterface::class)->error('Setting custom header on a 204 or 304 is not supported (Header: {header})', [
  190. 'header' => $name,
  191. ]);
  192. }
  193. }
  194. if (is_null($value)) {
  195. unset($this->headers[$name]);
  196. } else {
  197. $this->headers[$name] = $value;
  198. }
  199. return $this;
  200. }
  201. /**
  202. * Set the headers
  203. * @param array $headers value header pairs
  204. * @return $this
  205. * @since 8.0.0
  206. */
  207. public function setHeaders(array $headers) {
  208. $this->headers = $headers;
  209. return $this;
  210. }
  211. /**
  212. * Returns the set headers
  213. * @return array the headers
  214. * @since 6.0.0
  215. */
  216. public function getHeaders() {
  217. $mergeWith = [];
  218. if ($this->lastModified) {
  219. $mergeWith['Last-Modified'] =
  220. $this->lastModified->format(\DateTimeInterface::RFC2822);
  221. }
  222. $this->headers['Content-Security-Policy'] = $this->getContentSecurityPolicy()->buildPolicy();
  223. $this->headers['Feature-Policy'] = $this->getFeaturePolicy()->buildPolicy();
  224. $this->headers['X-Robots-Tag'] = 'noindex, nofollow';
  225. if ($this->ETag) {
  226. $mergeWith['ETag'] = '"' . $this->ETag . '"';
  227. }
  228. return array_merge($mergeWith, $this->headers);
  229. }
  230. /**
  231. * By default renders no output
  232. * @return string
  233. * @since 6.0.0
  234. */
  235. public function render() {
  236. return '';
  237. }
  238. /**
  239. * Set response status
  240. * @param int $status a HTTP status code, see also the STATUS constants
  241. * @return Response Reference to this object
  242. * @since 6.0.0 - return value was added in 7.0.0
  243. */
  244. public function setStatus($status) {
  245. $this->status = $status;
  246. return $this;
  247. }
  248. /**
  249. * Set a Content-Security-Policy
  250. * @param EmptyContentSecurityPolicy $csp Policy to set for the response object
  251. * @return $this
  252. * @since 8.1.0
  253. */
  254. public function setContentSecurityPolicy(EmptyContentSecurityPolicy $csp) {
  255. $this->contentSecurityPolicy = $csp;
  256. return $this;
  257. }
  258. /**
  259. * Get the currently used Content-Security-Policy
  260. * @return EmptyContentSecurityPolicy|null Used Content-Security-Policy or null if
  261. * none specified.
  262. * @since 8.1.0
  263. */
  264. public function getContentSecurityPolicy() {
  265. if ($this->contentSecurityPolicy === null) {
  266. $this->setContentSecurityPolicy(new EmptyContentSecurityPolicy());
  267. }
  268. return $this->contentSecurityPolicy;
  269. }
  270. /**
  271. * @since 17.0.0
  272. */
  273. public function getFeaturePolicy(): EmptyFeaturePolicy {
  274. if ($this->featurePolicy === null) {
  275. $this->setFeaturePolicy(new EmptyFeaturePolicy());
  276. }
  277. return $this->featurePolicy;
  278. }
  279. /**
  280. * @since 17.0.0
  281. */
  282. public function setFeaturePolicy(EmptyFeaturePolicy $featurePolicy): self {
  283. $this->featurePolicy = $featurePolicy;
  284. return $this;
  285. }
  286. /**
  287. * Get response status
  288. * @since 6.0.0
  289. */
  290. public function getStatus() {
  291. return $this->status;
  292. }
  293. /**
  294. * Get the ETag
  295. * @return string the etag
  296. * @since 6.0.0
  297. */
  298. public function getETag() {
  299. return $this->ETag;
  300. }
  301. /**
  302. * Get "last modified" date
  303. * @return \DateTime RFC2822 formatted last modified date
  304. * @since 6.0.0
  305. */
  306. public function getLastModified() {
  307. return $this->lastModified;
  308. }
  309. /**
  310. * Set the ETag
  311. * @param string $ETag
  312. * @return Response Reference to this object
  313. * @since 6.0.0 - return value was added in 7.0.0
  314. */
  315. public function setETag($ETag) {
  316. $this->ETag = $ETag;
  317. return $this;
  318. }
  319. /**
  320. * Set "last modified" date
  321. * @param \DateTime $lastModified
  322. * @return Response Reference to this object
  323. * @since 6.0.0 - return value was added in 7.0.0
  324. */
  325. public function setLastModified($lastModified) {
  326. $this->lastModified = $lastModified;
  327. return $this;
  328. }
  329. /**
  330. * Marks the response as to throttle. Will be throttled when the
  331. * @BruteForceProtection annotation is added.
  332. *
  333. * @param array $metadata
  334. * @since 12.0.0
  335. */
  336. public function throttle(array $metadata = []) {
  337. $this->throttled = true;
  338. $this->throttleMetadata = $metadata;
  339. }
  340. /**
  341. * Returns the throttle metadata, defaults to empty array
  342. *
  343. * @return array
  344. * @since 13.0.0
  345. */
  346. public function getThrottleMetadata() {
  347. return $this->throttleMetadata;
  348. }
  349. /**
  350. * Whether the current response is throttled.
  351. *
  352. * @since 12.0.0
  353. */
  354. public function isThrottled() {
  355. return $this->throttled;
  356. }
  357. }