summaryrefslogtreecommitdiffstats
path: root/lib/private/legacy/OC_EventSource.php
blob: b8c4389f985e29e31b982d580c99cf6c5333780d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php
/**
 * @copyright Copyright (c) 2016, ownCloud, Inc.
 *
 * @author Bart Visscher <bartv@thisnet.nl>
 * @author Christian Oliff <christianoliff@yahoo.com>
 * @author Felix Moeller <mail@felixmoeller.de>
 * @author Lukas Reschke <lukas@statuscode.ch>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Robin Appelman <robin@icewind.nl>
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 *
 * @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/>
 *
 */

/**
 * wrapper for server side events (https://en.wikipedia.org/wiki/Server-sent_events)
 * includes a fallback for older browsers and IE
 *
 * use server side events with caution, to many open requests can hang the server
 */
class OC_EventSource implements \OCP\IEventSource {
	/**
	 * @var bool
	 */
	private $fallback;

	/**
	 * @var int
	 */
	private $fallBackId = 0;

	/**
	 * @var bool
	 */
	private $started = false;

	protected function init() {
		if ($this->started) {
			return;
		}
		$this->started = true;

		// prevent php output buffering, caching and nginx buffering
		OC_Util::obEnd();
		header('Cache-Control: no-cache');
		header('X-Accel-Buffering: no');
		$this->fallback = isset($_GET['fallback']) and $_GET['fallback'] == 'true';
		if ($this->fallback) {
			$this->fallBackId = (int)$_GET['fallback_id'];
			/**
			 * FIXME: The default content-security-policy of ownCloud forbids inline
			 * JavaScript for security reasons. IE starting on Windows 10 will
			 * however also obey the CSP which will break the event source fallback.
			 *
			 * As a workaround thus we set a custom policy which allows the execution
			 * of inline JavaScript.
			 *
			 * @link https://github.com/owncloud/core/issues/14286
			 */
			header("Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'");
			header("Content-Type: text/html");
			echo str_repeat('<span></span>' . PHP_EOL, 10); //dummy data to keep IE happy
		} else {
			header("Content-Type: text/event-stream");
		}
		if (!\OC::$server->getRequest()->passesStrictCookieCheck()) {
			header('Location: '.\OC::$WEBROOT);
			exit();
		}
		if (!\OC::$server->getRequest()->passesCSRFCheck()) {
			$this->send('error', 'Possible CSRF attack. Connection will be closed.');
			$this->close();
			exit();
		}
		flush();
	}

	/**
	 * send a message to the client
	 *
	 * @param string $type
	 * @param mixed $data
	 *
	 * @throws \BadMethodCallException
	 * if only one parameter is given, a typeless message will be send with that parameter as data
	 * @suppress PhanDeprecatedFunction
	 */
	public function send($type, $data = null) {
		if ($data and !preg_match('/^[A-Za-z0-9_]+$/', $type)) {
			throw new BadMethodCallException('Type needs to be alphanumeric ('. $type .')');
		}
		$this->init();
		if (is_null($data)) {
			$data = $type;
			$type = null;
		}
		if ($this->fallback) {
			$response = '<script type="text/javascript">window.parent.OC.EventSource.fallBackCallBack('
				. $this->fallBackId . ',"' . $type . '",' . OC_JSON::encode($data) . ')</script>' . PHP_EOL;
			echo $response;
		} else {
			if ($type) {
				echo 'event: ' . $type . PHP_EOL;
			}
			echo 'data: ' . OC_JSON::encode($data) . PHP_EOL;
		}
		echo PHP_EOL;
		flush();
	}

	/**
	 * close the connection of the event source
	 */
	public function close() {
		$this->send('__internal__', 'close'); //server side closing can be an issue, let the client do it
	}
}