Completed
Push — master ( 5224a2...b8ca55 )
by Thomas
30:13 queued 16:39
created

CorsPlugin::getExtraHeaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Noveen Sachdeva <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OCA\DAV\Connector\Sabre;
23
24
use OCP\IUserSession;
25
use OCP\Util;
26
use Sabre\DAV\ServerPlugin;
27
use Sabre\HTTP\RequestInterface;
28
use Sabre\HTTP\ResponseInterface;
29
30
/**
31
 * Class CorsPlugin is a plugin which adds CORS headers to the responses
32
 */
33
class CorsPlugin extends ServerPlugin {
34
35
	/**
36
	 * Reference to main server object
37
	 *
38
	 * @var \Sabre\DAV\Server
39
	 */
40
	private $server;
41
42
	/**
43
	 * Reference to logged in user's session
44
	 *
45
	 * @var IUserSession
46
	 */
47
	private $userSession;
48
49
	/**
50
	 * @var string[]
51
	 */
52
	private $extraHeaders;
53
54
	/**
55
	 * @param IUserSession $userSession
56
	 */
57
	public function __construct(IUserSession $userSession) {
58
		$this->userSession = $userSession;
59
	}
60
61
	private function getExtraHeaders(RequestInterface $request) {
62
		if ($this->extraHeaders === null) {
63
			// TODO: design a way to have plugins provide these
64
			$this->extraHeaders['Access-Control-Allow-Headers'] = ['X-OC-Mtime', 'OC-Checksum', 'OC-Total-Length', 'Depth', 'Destination', 'Overwrite'];
65
			if ($this->userSession->getUser() === null) {
66
				$this->extraHeaders['Access-Control-Allow-Methods'] = [
67
					'OPTIONS',
68
					'GET',
69
					'HEAD',
70
					'DELETE',
71
					'PROPFIND',
72
					'PUT',
73
					'PROPPATCH',
74
					'COPY',
75
					'MOVE',
76
					'REPORT'
77
				];
78
			} else {
79
				$this->extraHeaders['Access-Control-Allow-Methods'] = $this->server->getAllowedMethods($request->getPath());
80
			}
81
		}
82
		return $this->extraHeaders;
83
	}
84
85
	/**
86
	 * This initializes the plugin.
87
	 *
88
	 * This function is called by \Sabre\DAV\Server, after
89
	 * addPlugin is called.
90
	 *
91
	 * This method should set up the required event subscriptions.
92
	 *
93
	 * @param \Sabre\DAV\Server $server
94
	 * @return void
95
	 */
96
	public function initialize(\Sabre\DAV\Server $server) {
97
		$this->server = $server;
98
99
		$request = $this->server->httpRequest;
100
		if (!$request->hasHeader('Origin')) {
101
			return;
102
		}
103
		$originHeader = $request->getHeader('Origin');
104
		if ($this->ignoreOriginHeader($originHeader)) {
105
			return;
106
		}
107
		if (Util::isSameDomain($originHeader, $request->getAbsoluteUrl())) {
108
			return;
109
		}
110
111
		$this->server->on('beforeMethod', [$this, 'setCorsHeaders']);
112
		$this->server->on('beforeMethod:OPTIONS', [$this, 'setOptionsRequestHeaders']);
113
	}
114
115
	/**
116
	 * This method sets the cors headers for all requests
117
	 *
118
	 * @param RequestInterface $request
119
	 * @param ResponseInterface $response
120
	 * @return void
121
	 */
122
	public function setCorsHeaders(RequestInterface $request, ResponseInterface $response) {
123
		if ($request->getHeader('origin') !== null && $this->userSession->getUser() !== null) {
124
			$requesterDomain = $request->getHeader('origin');
125
			$userId = $this->userSession->getUser()->getUID();
126
			$headers = \OC_Response::setCorsHeaders($userId, $requesterDomain, null, $this->getExtraHeaders($request));
127
			foreach ($headers as $key => $value) {
128
				$response->addHeader($key, \implode(',', $value));
129
			}
130
		}
131
	}
132
133
	/**
134
	 * Handles the OPTIONS request
135
	 *
136
	 * @param RequestInterface $request
137
	 * @param ResponseInterface $response
138
	 *
139
	 * @return false
140
	 * @throws \InvalidArgumentException
141
	 */
142
	public function setOptionsRequestHeaders(RequestInterface $request, ResponseInterface $response) {
143
		$authorization = $request->getHeader('Authorization');
144
		if ($authorization === null || $authorization === '') {
145
			// Set the proper response
146
			$response->setStatus(200);
147
			$response = \OC_Response::setOptionsRequestHeaders($response, $this->getExtraHeaders($request));
148
149
			// Since All OPTIONS requests are unauthorized, we will have to return false from here
150
			// If we don't return false, due to no authorization, a 401-Unauthorized will be thrown
151
			// Which we don't want here
152
			// Hence this sendResponse
153
			$this->server->sapi->sendResponse($response);
154
			return false;
155
		}
156
	}
157
158
	/**
159
	 * in addition to schemas used by extensions we ignore empty origin header
160
	 * values as well as 'null' which is not valid by the specification but used
161
	 * by some clients.
162
	 * @link https://github.com/owncloud/core/pull/32120#issuecomment-407008243
163
	 *
164
	 * @param string $originHeader
165
	 * @return bool
166
	 */
167
	public function ignoreOriginHeader($originHeader) {
168
		if (\in_array($originHeader, ['', null, 'null'], true)) {
169
			return true;
170
		}
171
		$schema = \parse_url($originHeader, PHP_URL_SCHEME);
172
		return \in_array(\strtolower($schema), ['moz-extension', 'chrome-extension']);
173
	}
174
}
175