response.php 7.57 KB
Newer Older
1
2
<?php
/**
Jenkins for ownCloud's avatar
Jenkins for ownCloud committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 * @author Bart Visscher <bartv@thisnet.nl>
 * @author Jörn Friedrich Dreyer <jfd@butonic.de>
 * @author Lukas Reschke <lukas@owncloud.com>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 * @author Vincent Petry <pvince81@owncloud.com>
 *
 * @copyright Copyright (c) 2015, ownCloud, Inc.
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
26
 */
27

28
class OC_Response {
29
	const STATUS_FOUND = 304;
30
	const STATUS_NOT_MODIFIED = 304;
31
	const STATUS_TEMPORARY_REDIRECT = 307;
Lukas Reschke's avatar
Lukas Reschke committed
32
	const STATUS_BAD_REQUEST = 400;
33
	const STATUS_NOT_FOUND = 404;
34
	const STATUS_INTERNAL_SERVER_ERROR = 500;
35
	const STATUS_SERVICE_UNAVAILABLE = 503;
36

Bart Visscher's avatar
Bart Visscher committed
37
	/**
38
	* Enable response caching by sending correct HTTP headers
39
	* @param integer $cache_time time to cache the response
Bart Visscher's avatar
Bart Visscher committed
40
41
42
43
	*  >0		cache time in seconds
	*  0 and <0	enable default browser caching
	*  null		cache indefinitly
	*/
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
	static public function enableCaching($cache_time = null) {
		if (is_numeric($cache_time)) {
			header('Pragma: public');// enable caching in IE
			if ($cache_time > 0) {
				self::setExpiresHeader('PT'.$cache_time.'S');
				header('Cache-Control: max-age='.$cache_time.', must-revalidate');
			}
			else {
				self::setExpiresHeader(0);
				header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
			}
		}
		else {
			header('Cache-Control: cache');
			header('Pragma: cache');
		}

61
	}
Bart Visscher's avatar
Bart Visscher committed
62
63

	/**
64
	* disable browser caching
Bart Visscher's avatar
Bart Visscher committed
65
66
	* @see enableCaching with cache_time = 0
	*/
67
68
69
	static public function disableCaching() {
		self::enableCaching(0);
	}
70

Bart Visscher's avatar
Bart Visscher committed
71
	/**
72
	* Set response status
Lukas Reschke's avatar
Lukas Reschke committed
73
	* @param int $status a HTTP status code, see also the STATUS constants
Bart Visscher's avatar
Bart Visscher committed
74
	*/
75
	static public function setStatus($status) {
76
		$protocol = $_SERVER['SERVER_PROTOCOL'];
77
78
79
80
		switch($status) {
			case self::STATUS_NOT_MODIFIED:
				$status = $status . ' Not Modified';
				break;
81
82
83
84
85
86
87
88
89
90
91
			case self::STATUS_TEMPORARY_REDIRECT:
				if ($protocol == 'HTTP/1.1') {
					$status = $status . ' Temporary Redirect';
					break;
				} else {
					$status = self::STATUS_FOUND;
					// fallthrough
				}
			case self::STATUS_FOUND;
				$status = $status . ' Found';
				break;
92
93
94
			case self::STATUS_NOT_FOUND;
				$status = $status . ' Not Found';
				break;
95
96
97
			case self::STATUS_INTERNAL_SERVER_ERROR;
				$status = $status . ' Internal Server Error';
				break;
98
99
100
			case self::STATUS_SERVICE_UNAVAILABLE;
				$status = $status . ' Service Unavailable';
				break;
101
		}
102
103
104
		header($protocol.' '.$status);
	}

Bart Visscher's avatar
Bart Visscher committed
105
	/**
106
	* Send redirect response
107
	* @param string $location to redirect to
Bart Visscher's avatar
Bart Visscher committed
108
	*/
109
110
111
	static public function redirect($location) {
		self::setStatus(self::STATUS_TEMPORARY_REDIRECT);
		header('Location: '.$location);
112
113
	}

Bart Visscher's avatar
Bart Visscher committed
114
	/**
115
	* Set reponse expire time
116
	* @param string|DateTime $expires date-time when the response expires
Bart Visscher's avatar
Bart Visscher committed
117
118
119
	*  string for DateInterval from now
	*  DateTime object when to expire response
	*/
120
121
122
123
	static public function setExpiresHeader($expires) {
		if (is_string($expires) && $expires[0] == 'P') {
			$interval = $expires;
			$expires = new DateTime('now');
Bart Visscher's avatar
Bart Visscher committed
124
			$expires->add(new DateInterval($interval));
125
126
		}
		if ($expires instanceof DateTime) {
127
			$expires->setTimezone(new DateTimeZone('GMT'));
128
129
			$expires = $expires->format(DateTime::RFC2822);
		}
Bart Visscher's avatar
Bart Visscher committed
130
		header('Expires: '.$expires);
131
132
	}

Bart Visscher's avatar
Bart Visscher committed
133
134
135
	/**
	* Checks and set ETag header, when the request matches sends a
	* 'not modified' response
136
	* @param string $etag token to use for modification check
Bart Visscher's avatar
Bart Visscher committed
137
	*/
138
139
140
141
	static public function setETagHeader($etag) {
		if (empty($etag)) {
			return;
		}
Bart Visscher's avatar
Bart Visscher committed
142
		$etag = '"'.$etag.'"';
143
144
145
146
147
		if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
		    trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) {
			self::setStatus(self::STATUS_NOT_MODIFIED);
			exit;
		}
Bart Visscher's avatar
Bart Visscher committed
148
		header('ETag: '.$etag);
149
150
	}

Bart Visscher's avatar
Bart Visscher committed
151
152
153
	/**
	* Checks and set Last-Modified header, when the request matches sends a
	* 'not modified' response
154
	* @param int|DateTime|string $lastModified time when the reponse was last modified
Bart Visscher's avatar
Bart Visscher committed
155
	*/
156
157
158
159
	static public function setLastModifiedHeader($lastModified) {
		if (empty($lastModified)) {
			return;
		}
160
161
162
		if (is_int($lastModified)) {
			$lastModified = gmdate(DateTime::RFC2822, $lastModified);
		}
163
164
165
166
167
168
169
170
171
172
		if ($lastModified instanceof DateTime) {
			$lastModified = $lastModified->format(DateTime::RFC2822);
		}
		if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
		    trim($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) {
			self::setStatus(self::STATUS_NOT_MODIFIED);
			exit;
		}
		header('Last-Modified: '.$lastModified);
	}
173

174
175
176
177
178
179
	/**
	 * Sets the content disposition header (with possible workarounds)
	 * @param string $filename file name
	 * @param string $type disposition type, either 'attachment' or 'inline'
	 */
	static public function setContentDispositionHeader( $filename, $type = 'attachment' ) {
180
181
182
183
184
185
		if (\OC::$server->getRequest()->isUserAgent(
			[
				\OC\AppFramework\Http\Request::USER_AGENT_IE,
				\OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME,
				\OC\AppFramework\Http\Request::USER_AGENT_FREEBOX,
			])) {
186
187
188
189
190
191
192
			header( 'Content-Disposition: ' . rawurlencode($type) . '; filename="' . rawurlencode( $filename ) . '"' );
		} else {
			header( 'Content-Disposition: ' . rawurlencode($type) . '; filename*=UTF-8\'\'' . rawurlencode( $filename )
												 . '; filename="' . rawurlencode( $filename ) . '"' );
		}
	}

Bart Visscher's avatar
Bart Visscher committed
193
	/**
194
	* Send file as response, checking and setting caching headers
195
	* @param string $filepath of file to send
Bart Visscher's avatar
Bart Visscher committed
196
	*/
197
	static public function sendFile($filepath) {
198
199
200
201
202
203
204
205
206
207
208
209
		$fp = fopen($filepath, 'rb');
		if ($fp) {
			self::setLastModifiedHeader(filemtime($filepath));
			self::setETagHeader(md5_file($filepath));

			header('Content-Length: '.filesize($filepath));
			fpassthru($fp);
		}
		else {
			self::setStatus(self::STATUS_NOT_FOUND);
		}
	}
210

211
	/**
212
213
214
215
216
	 * This function adds some security related headers to all requests served via base.php
	 * The implementation of this function has to happen here to ensure that all third-party
	 * components (e.g. SabreDAV) also benefit from this headers.
	 */
	public static function addSecurityHeaders() {
217
218
219
220
221
222
223
		/**
		 * FIXME: Content Security Policy for legacy ownCloud components. This
		 * can be removed once \OCP\AppFramework\Http\Response from the AppFramework
		 * is used everywhere.
		 * @see \OCP\AppFramework\Http\Response::getHeaders
		 */
		$policy = 'default-src \'self\'; '
224
225
226
227
228
			. 'script-src \'self\' \'unsafe-eval\'; '
			. 'style-src \'self\' \'unsafe-inline\'; '
			. 'frame-src *; '
			. 'img-src *; '
			. 'font-src \'self\' data:; '
229
			. 'media-src *; ' 
230
			. 'connect-src *';
231
		header('Content-Security-Policy:' . $policy);
232
233
234
235
236
237
238
239
240

		// Send fallback headers for installations that don't have the possibility to send
		// custom headers on the webserver side
		if(getenv('modHeadersAvailable') !== 'true') {
			header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters
			header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
			header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains
			header('X-Robots-Tag: none'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
		}
241
242
	}

243
}