Passed
Push — master ( b976cd...0bca48 )
by Roeland
27:20 queued 15:23
created
apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php 1 patch
Indentation   +345 added lines, -345 removed lines patch added patch discarded remove patch
@@ -55,349 +55,349 @@
 block discarded – undo
55 55
 
56 56
 class RefreshWebcalService {
57 57
 
58
-	/** @var CalDavBackend */
59
-	private $calDavBackend;
60
-
61
-	/** @var IClientService */
62
-	private $clientService;
63
-
64
-	/** @var IConfig */
65
-	private $config;
66
-
67
-	/** @var ILogger */
68
-	private $logger;
69
-
70
-	public const REFRESH_RATE = '{http://apple.com/ns/ical/}refreshrate';
71
-	public const STRIP_ALARMS = '{http://calendarserver.org/ns/}subscribed-strip-alarms';
72
-	public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments';
73
-	public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos';
74
-
75
-	/**
76
-	 * RefreshWebcalJob constructor.
77
-	 *
78
-	 * @param CalDavBackend $calDavBackend
79
-	 * @param IClientService $clientService
80
-	 * @param IConfig $config
81
-	 * @param ILogger $logger
82
-	 */
83
-	public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, ILogger $logger) {
84
-		$this->calDavBackend = $calDavBackend;
85
-		$this->clientService = $clientService;
86
-		$this->config = $config;
87
-		$this->logger = $logger;
88
-	}
89
-
90
-	/**
91
-	 * @param string $principalUri
92
-	 * @param string $uri
93
-	 */
94
-	public function refreshSubscription(string $principalUri, string $uri) {
95
-		$subscription = $this->getSubscription($principalUri, $uri);
96
-		$mutations = [];
97
-		if (!$subscription) {
98
-			return;
99
-		}
100
-
101
-		$webcalData = $this->queryWebcalFeed($subscription, $mutations);
102
-		if (!$webcalData) {
103
-			return;
104
-		}
105
-
106
-		$stripTodos = ($subscription[self::STRIP_TODOS] ?? 1) === 1;
107
-		$stripAlarms = ($subscription[self::STRIP_ALARMS] ?? 1) === 1;
108
-		$stripAttachments = ($subscription[self::STRIP_ATTACHMENTS] ?? 1) === 1;
109
-
110
-		try {
111
-			$splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
112
-
113
-			// we wait with deleting all outdated events till we parsed the new ones
114
-			// in case the new calendar is broken and `new ICalendar` throws a ParseException
115
-			// the user will still see the old data
116
-			$this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']);
117
-
118
-			while ($vObject = $splitter->getNext()) {
119
-				/** @var Component $vObject */
120
-				$compName = null;
121
-
122
-				foreach ($vObject->getComponents() as $component) {
123
-					if ($component->name === 'VTIMEZONE') {
124
-						continue;
125
-					}
126
-
127
-					$compName = $component->name;
128
-
129
-					if ($stripAlarms) {
130
-						unset($component->{'VALARM'});
131
-					}
132
-					if ($stripAttachments) {
133
-						unset($component->{'ATTACH'});
134
-					}
135
-				}
136
-
137
-				if ($stripTodos && $compName === 'VTODO') {
138
-					continue;
139
-				}
140
-
141
-				$uri = $this->getRandomCalendarObjectUri();
142
-				$calendarData = $vObject->serialize();
143
-				try {
144
-					$this->calDavBackend->createCalendarObject($subscription['id'], $uri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
145
-				} catch (NoInstancesException | BadRequest $ex) {
146
-					$this->logger->logException($ex);
147
-				}
148
-			}
149
-
150
-			$newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
151
-			if ($newRefreshRate) {
152
-				$mutations[self::REFRESH_RATE] = $newRefreshRate;
153
-			}
154
-
155
-			$this->updateSubscription($subscription, $mutations);
156
-		} catch (ParseException $ex) {
157
-			$subscriptionId = $subscription['id'];
158
-
159
-			$this->logger->logException($ex);
160
-			$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a parsing error");
161
-		}
162
-	}
163
-
164
-	/**
165
-	 * loads subscription from backend
166
-	 *
167
-	 * @param string $principalUri
168
-	 * @param string $uri
169
-	 * @return array|null
170
-	 */
171
-	public function getSubscription(string $principalUri, string $uri) {
172
-		$subscriptions = array_values(array_filter(
173
-			$this->calDavBackend->getSubscriptionsForUser($principalUri),
174
-			function ($sub) use ($uri) {
175
-				return $sub['uri'] === $uri;
176
-			}
177
-		));
178
-
179
-		if (count($subscriptions) === 0) {
180
-			return null;
181
-		}
182
-
183
-		return $subscriptions[0];
184
-	}
185
-
186
-	/**
187
-	 * gets webcal feed from remote server
188
-	 *
189
-	 * @param array $subscription
190
-	 * @param array &$mutations
191
-	 * @return null|string
192
-	 */
193
-	private function queryWebcalFeed(array $subscription, array &$mutations) {
194
-		$client = $this->clientService->newClient();
195
-
196
-		$didBreak301Chain = false;
197
-		$latestLocation = null;
198
-
199
-		$handlerStack = HandlerStack::create();
200
-		$handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
201
-			return $request
202
-				->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
203
-				->withHeader('User-Agent', 'Nextcloud Webcal Crawler');
204
-		}));
205
-		$handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
206
-			if (!$didBreak301Chain) {
207
-				if ($response->getStatusCode() !== 301) {
208
-					$didBreak301Chain = true;
209
-				} else {
210
-					$latestLocation = $response->getHeader('Location');
211
-				}
212
-			}
213
-			return $response;
214
-		}));
215
-
216
-		$allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no');
217
-		$subscriptionId = $subscription['id'];
218
-		$url = $this->cleanURL($subscription['source']);
219
-		if ($url === null) {
220
-			return null;
221
-		}
222
-
223
-		try {
224
-			$params = [
225
-				'allow_redirects' => [
226
-					'redirects' => 10
227
-				],
228
-				'handler' => $handlerStack,
229
-				'nextcloud' => [
230
-					'allow_local_address' => $allowLocalAccess === 'yes',
231
-				]
232
-			];
233
-
234
-			$user = parse_url($subscription['source'], PHP_URL_USER);
235
-			$pass = parse_url($subscription['source'], PHP_URL_PASS);
236
-			if ($user !== null && $pass !== null) {
237
-				$params['auth'] = [$user, $pass];
238
-			}
239
-
240
-			$response = $client->get($url, $params);
241
-			$body = $response->getBody();
242
-
243
-			if ($latestLocation) {
244
-				$mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
245
-			}
246
-
247
-			$contentType = $response->getHeader('Content-Type');
248
-			$contentType = explode(';', $contentType, 2)[0];
249
-			switch ($contentType) {
250
-				case 'application/calendar+json':
251
-					try {
252
-						$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
253
-					} catch (Exception $ex) {
254
-						// In case of a parsing error return null
255
-						$this->logger->debug("Subscription $subscriptionId could not be parsed");
256
-						return null;
257
-					}
258
-					return $jCalendar->serialize();
259
-
260
-				case 'application/calendar+xml':
261
-					try {
262
-						$xCalendar = Reader::readXML($body);
263
-					} catch (Exception $ex) {
264
-						// In case of a parsing error return null
265
-						$this->logger->debug("Subscription $subscriptionId could not be parsed");
266
-						return null;
267
-					}
268
-					return $xCalendar->serialize();
269
-
270
-				case 'text/calendar':
271
-				default:
272
-					try {
273
-						$vCalendar = Reader::read($body);
274
-					} catch (Exception $ex) {
275
-						// In case of a parsing error return null
276
-						$this->logger->debug("Subscription $subscriptionId could not be parsed");
277
-						return null;
278
-					}
279
-					return $vCalendar->serialize();
280
-			}
281
-		} catch (LocalServerException $ex) {
282
-			$this->logger->logException($ex, [
283
-				'message' => "Subscription $subscriptionId was not refreshed because it violates local access rules",
284
-				'level' => ILogger::WARN,
285
-			]);
286
-
287
-			return null;
288
-		} catch (Exception $ex) {
289
-			$this->logger->logException($ex, [
290
-				'message' => "Subscription $subscriptionId could not be refreshed due to a network error",
291
-				'level' => ILogger::WARN,
292
-			]);
293
-
294
-			return null;
295
-		}
296
-	}
297
-
298
-	/**
299
-	 * check if:
300
-	 *  - current subscription stores a refreshrate
301
-	 *  - the webcal feed suggests a refreshrate
302
-	 *  - return suggested refreshrate if user didn't set a custom one
303
-	 *
304
-	 * @param array $subscription
305
-	 * @param string $webcalData
306
-	 * @return string|null
307
-	 */
308
-	private function checkWebcalDataForRefreshRate($subscription, $webcalData) {
309
-		// if there is no refreshrate stored in the database, check the webcal feed
310
-		// whether it suggests any refresh rate and store that in the database
311
-		if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
312
-			return null;
313
-		}
314
-
315
-		/** @var Component\VCalendar $vCalendar */
316
-		$vCalendar = Reader::read($webcalData);
317
-
318
-		$newRefreshRate = null;
319
-		if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
320
-			$newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
321
-		}
322
-		if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
323
-			$newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
324
-		}
325
-
326
-		if (!$newRefreshRate) {
327
-			return null;
328
-		}
329
-
330
-		// check if new refresh rate is even valid
331
-		try {
332
-			DateTimeParser::parseDuration($newRefreshRate);
333
-		} catch (InvalidDataException $ex) {
334
-			return null;
335
-		}
336
-
337
-		return $newRefreshRate;
338
-	}
339
-
340
-	/**
341
-	 * update subscription stored in database
342
-	 * used to set:
343
-	 *  - refreshrate
344
-	 *  - source
345
-	 *
346
-	 * @param array $subscription
347
-	 * @param array $mutations
348
-	 */
349
-	private function updateSubscription(array $subscription, array $mutations) {
350
-		if (empty($mutations)) {
351
-			return;
352
-		}
353
-
354
-		$propPatch = new PropPatch($mutations);
355
-		$this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
356
-		$propPatch->commit();
357
-	}
358
-
359
-	/**
360
-	 * This method will strip authentication information and replace the
361
-	 * 'webcal' or 'webcals' protocol scheme
362
-	 *
363
-	 * @param string $url
364
-	 * @return string|null
365
-	 */
366
-	private function cleanURL(string $url) {
367
-		$parsed = parse_url($url);
368
-		if ($parsed === false) {
369
-			return null;
370
-		}
371
-
372
-		if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
373
-			$scheme = 'http';
374
-		} else {
375
-			$scheme = 'https';
376
-		}
377
-
378
-		$host = $parsed['host'] ?? '';
379
-		$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
380
-		$path = $parsed['path'] ?? '';
381
-		$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
382
-		$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
383
-
384
-		$cleanURL = "$scheme://$host$port$path$query$fragment";
385
-		// parse_url is giving some weird results if no url and no :// is given,
386
-		// so let's test the url again
387
-		$parsedClean = parse_url($cleanURL);
388
-		if ($parsedClean === false || !isset($parsedClean['host'])) {
389
-			return null;
390
-		}
391
-
392
-		return $cleanURL;
393
-	}
394
-
395
-	/**
396
-	 * Returns a random uri for a calendar-object
397
-	 *
398
-	 * @return string
399
-	 */
400
-	public function getRandomCalendarObjectUri():string {
401
-		return UUIDUtil::getUUID() . '.ics';
402
-	}
58
+    /** @var CalDavBackend */
59
+    private $calDavBackend;
60
+
61
+    /** @var IClientService */
62
+    private $clientService;
63
+
64
+    /** @var IConfig */
65
+    private $config;
66
+
67
+    /** @var ILogger */
68
+    private $logger;
69
+
70
+    public const REFRESH_RATE = '{http://apple.com/ns/ical/}refreshrate';
71
+    public const STRIP_ALARMS = '{http://calendarserver.org/ns/}subscribed-strip-alarms';
72
+    public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments';
73
+    public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos';
74
+
75
+    /**
76
+     * RefreshWebcalJob constructor.
77
+     *
78
+     * @param CalDavBackend $calDavBackend
79
+     * @param IClientService $clientService
80
+     * @param IConfig $config
81
+     * @param ILogger $logger
82
+     */
83
+    public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, ILogger $logger) {
84
+        $this->calDavBackend = $calDavBackend;
85
+        $this->clientService = $clientService;
86
+        $this->config = $config;
87
+        $this->logger = $logger;
88
+    }
89
+
90
+    /**
91
+     * @param string $principalUri
92
+     * @param string $uri
93
+     */
94
+    public function refreshSubscription(string $principalUri, string $uri) {
95
+        $subscription = $this->getSubscription($principalUri, $uri);
96
+        $mutations = [];
97
+        if (!$subscription) {
98
+            return;
99
+        }
100
+
101
+        $webcalData = $this->queryWebcalFeed($subscription, $mutations);
102
+        if (!$webcalData) {
103
+            return;
104
+        }
105
+
106
+        $stripTodos = ($subscription[self::STRIP_TODOS] ?? 1) === 1;
107
+        $stripAlarms = ($subscription[self::STRIP_ALARMS] ?? 1) === 1;
108
+        $stripAttachments = ($subscription[self::STRIP_ATTACHMENTS] ?? 1) === 1;
109
+
110
+        try {
111
+            $splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
112
+
113
+            // we wait with deleting all outdated events till we parsed the new ones
114
+            // in case the new calendar is broken and `new ICalendar` throws a ParseException
115
+            // the user will still see the old data
116
+            $this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']);
117
+
118
+            while ($vObject = $splitter->getNext()) {
119
+                /** @var Component $vObject */
120
+                $compName = null;
121
+
122
+                foreach ($vObject->getComponents() as $component) {
123
+                    if ($component->name === 'VTIMEZONE') {
124
+                        continue;
125
+                    }
126
+
127
+                    $compName = $component->name;
128
+
129
+                    if ($stripAlarms) {
130
+                        unset($component->{'VALARM'});
131
+                    }
132
+                    if ($stripAttachments) {
133
+                        unset($component->{'ATTACH'});
134
+                    }
135
+                }
136
+
137
+                if ($stripTodos && $compName === 'VTODO') {
138
+                    continue;
139
+                }
140
+
141
+                $uri = $this->getRandomCalendarObjectUri();
142
+                $calendarData = $vObject->serialize();
143
+                try {
144
+                    $this->calDavBackend->createCalendarObject($subscription['id'], $uri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
145
+                } catch (NoInstancesException | BadRequest $ex) {
146
+                    $this->logger->logException($ex);
147
+                }
148
+            }
149
+
150
+            $newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
151
+            if ($newRefreshRate) {
152
+                $mutations[self::REFRESH_RATE] = $newRefreshRate;
153
+            }
154
+
155
+            $this->updateSubscription($subscription, $mutations);
156
+        } catch (ParseException $ex) {
157
+            $subscriptionId = $subscription['id'];
158
+
159
+            $this->logger->logException($ex);
160
+            $this->logger->warning("Subscription $subscriptionId could not be refreshed due to a parsing error");
161
+        }
162
+    }
163
+
164
+    /**
165
+     * loads subscription from backend
166
+     *
167
+     * @param string $principalUri
168
+     * @param string $uri
169
+     * @return array|null
170
+     */
171
+    public function getSubscription(string $principalUri, string $uri) {
172
+        $subscriptions = array_values(array_filter(
173
+            $this->calDavBackend->getSubscriptionsForUser($principalUri),
174
+            function ($sub) use ($uri) {
175
+                return $sub['uri'] === $uri;
176
+            }
177
+        ));
178
+
179
+        if (count($subscriptions) === 0) {
180
+            return null;
181
+        }
182
+
183
+        return $subscriptions[0];
184
+    }
185
+
186
+    /**
187
+     * gets webcal feed from remote server
188
+     *
189
+     * @param array $subscription
190
+     * @param array &$mutations
191
+     * @return null|string
192
+     */
193
+    private function queryWebcalFeed(array $subscription, array &$mutations) {
194
+        $client = $this->clientService->newClient();
195
+
196
+        $didBreak301Chain = false;
197
+        $latestLocation = null;
198
+
199
+        $handlerStack = HandlerStack::create();
200
+        $handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
201
+            return $request
202
+                ->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
203
+                ->withHeader('User-Agent', 'Nextcloud Webcal Crawler');
204
+        }));
205
+        $handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
206
+            if (!$didBreak301Chain) {
207
+                if ($response->getStatusCode() !== 301) {
208
+                    $didBreak301Chain = true;
209
+                } else {
210
+                    $latestLocation = $response->getHeader('Location');
211
+                }
212
+            }
213
+            return $response;
214
+        }));
215
+
216
+        $allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no');
217
+        $subscriptionId = $subscription['id'];
218
+        $url = $this->cleanURL($subscription['source']);
219
+        if ($url === null) {
220
+            return null;
221
+        }
222
+
223
+        try {
224
+            $params = [
225
+                'allow_redirects' => [
226
+                    'redirects' => 10
227
+                ],
228
+                'handler' => $handlerStack,
229
+                'nextcloud' => [
230
+                    'allow_local_address' => $allowLocalAccess === 'yes',
231
+                ]
232
+            ];
233
+
234
+            $user = parse_url($subscription['source'], PHP_URL_USER);
235
+            $pass = parse_url($subscription['source'], PHP_URL_PASS);
236
+            if ($user !== null && $pass !== null) {
237
+                $params['auth'] = [$user, $pass];
238
+            }
239
+
240
+            $response = $client->get($url, $params);
241
+            $body = $response->getBody();
242
+
243
+            if ($latestLocation) {
244
+                $mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
245
+            }
246
+
247
+            $contentType = $response->getHeader('Content-Type');
248
+            $contentType = explode(';', $contentType, 2)[0];
249
+            switch ($contentType) {
250
+                case 'application/calendar+json':
251
+                    try {
252
+                        $jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
253
+                    } catch (Exception $ex) {
254
+                        // In case of a parsing error return null
255
+                        $this->logger->debug("Subscription $subscriptionId could not be parsed");
256
+                        return null;
257
+                    }
258
+                    return $jCalendar->serialize();
259
+
260
+                case 'application/calendar+xml':
261
+                    try {
262
+                        $xCalendar = Reader::readXML($body);
263
+                    } catch (Exception $ex) {
264
+                        // In case of a parsing error return null
265
+                        $this->logger->debug("Subscription $subscriptionId could not be parsed");
266
+                        return null;
267
+                    }
268
+                    return $xCalendar->serialize();
269
+
270
+                case 'text/calendar':
271
+                default:
272
+                    try {
273
+                        $vCalendar = Reader::read($body);
274
+                    } catch (Exception $ex) {
275
+                        // In case of a parsing error return null
276
+                        $this->logger->debug("Subscription $subscriptionId could not be parsed");
277
+                        return null;
278
+                    }
279
+                    return $vCalendar->serialize();
280
+            }
281
+        } catch (LocalServerException $ex) {
282
+            $this->logger->logException($ex, [
283
+                'message' => "Subscription $subscriptionId was not refreshed because it violates local access rules",
284
+                'level' => ILogger::WARN,
285
+            ]);
286
+
287
+            return null;
288
+        } catch (Exception $ex) {
289
+            $this->logger->logException($ex, [
290
+                'message' => "Subscription $subscriptionId could not be refreshed due to a network error",
291
+                'level' => ILogger::WARN,
292
+            ]);
293
+
294
+            return null;
295
+        }
296
+    }
297
+
298
+    /**
299
+     * check if:
300
+     *  - current subscription stores a refreshrate
301
+     *  - the webcal feed suggests a refreshrate
302
+     *  - return suggested refreshrate if user didn't set a custom one
303
+     *
304
+     * @param array $subscription
305
+     * @param string $webcalData
306
+     * @return string|null
307
+     */
308
+    private function checkWebcalDataForRefreshRate($subscription, $webcalData) {
309
+        // if there is no refreshrate stored in the database, check the webcal feed
310
+        // whether it suggests any refresh rate and store that in the database
311
+        if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
312
+            return null;
313
+        }
314
+
315
+        /** @var Component\VCalendar $vCalendar */
316
+        $vCalendar = Reader::read($webcalData);
317
+
318
+        $newRefreshRate = null;
319
+        if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
320
+            $newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
321
+        }
322
+        if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
323
+            $newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
324
+        }
325
+
326
+        if (!$newRefreshRate) {
327
+            return null;
328
+        }
329
+
330
+        // check if new refresh rate is even valid
331
+        try {
332
+            DateTimeParser::parseDuration($newRefreshRate);
333
+        } catch (InvalidDataException $ex) {
334
+            return null;
335
+        }
336
+
337
+        return $newRefreshRate;
338
+    }
339
+
340
+    /**
341
+     * update subscription stored in database
342
+     * used to set:
343
+     *  - refreshrate
344
+     *  - source
345
+     *
346
+     * @param array $subscription
347
+     * @param array $mutations
348
+     */
349
+    private function updateSubscription(array $subscription, array $mutations) {
350
+        if (empty($mutations)) {
351
+            return;
352
+        }
353
+
354
+        $propPatch = new PropPatch($mutations);
355
+        $this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
356
+        $propPatch->commit();
357
+    }
358
+
359
+    /**
360
+     * This method will strip authentication information and replace the
361
+     * 'webcal' or 'webcals' protocol scheme
362
+     *
363
+     * @param string $url
364
+     * @return string|null
365
+     */
366
+    private function cleanURL(string $url) {
367
+        $parsed = parse_url($url);
368
+        if ($parsed === false) {
369
+            return null;
370
+        }
371
+
372
+        if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
373
+            $scheme = 'http';
374
+        } else {
375
+            $scheme = 'https';
376
+        }
377
+
378
+        $host = $parsed['host'] ?? '';
379
+        $port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
380
+        $path = $parsed['path'] ?? '';
381
+        $query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
382
+        $fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
383
+
384
+        $cleanURL = "$scheme://$host$port$path$query$fragment";
385
+        // parse_url is giving some weird results if no url and no :// is given,
386
+        // so let's test the url again
387
+        $parsedClean = parse_url($cleanURL);
388
+        if ($parsedClean === false || !isset($parsedClean['host'])) {
389
+            return null;
390
+        }
391
+
392
+        return $cleanURL;
393
+    }
394
+
395
+    /**
396
+     * Returns a random uri for a calendar-object
397
+     *
398
+     * @return string
399
+     */
400
+    public function getRandomCalendarObjectUri():string {
401
+        return UUIDUtil::getUUID() . '.ics';
402
+    }
403 403
 }
Please login to merge, or discard this patch.