3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
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.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\App;
24 use Friendica\Core\Config\Capability\IManageConfigValues;
25 use Friendica\Core\System;
26 use Friendica\Util\Network;
27 use Friendica\Util\Strings;
28 use Friendica\Network\HTTPException;
31 * A class which checks and contains the basic
32 * environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
39 const SSL_POLICY_NONE = 0;
44 const SSL_POLICY_FULL = 1;
47 * SSL is optional, but preferred
49 const SSL_POLICY_SELFSIGN = 2;
52 * Define the Default SSL scheme
54 const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN;
57 * The Friendica Config
59 * @var IManageConfigValues
64 * The server side variables
71 * The hostname of the Base URL
78 * The SSL_POLICY of the Base URL
85 * The URL sub-path of the Base URL
99 * The current scheme of this call
106 * Returns the hostname of this node
110 public function getHostname(): string
112 return $this->hostname;
116 * Returns the current scheme of this call
120 public function getScheme(): string
122 return $this->scheme;
126 * Returns the SSL policy of this node
130 public function getSSLPolicy(): int
132 return $this->sslPolicy;
136 * Returns the sub-path of this URL
140 public function getUrlPath(): string
142 return $this->urlPath;
146 * Returns the full URL of this call
148 * Note: $ssl parameter value doesn't directly correlate with the resulting protocol
150 * @param bool $ssl True, if ssl should get used
154 public function get(bool $ssl = false): string
156 if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) {
157 return Network::switchScheme($this->url);
164 * Save current parts of the base Url
166 * @param string? $hostname
167 * @param int? $sslPolicy
168 * @param string? $urlPath
170 * @return bool true, if successful
171 * @TODO Find proper types
173 public function save($hostname = null, $sslPolicy = null, $urlPath = null): bool
175 $currHostname = $this->hostname;
176 $currSSLPolicy = $this->sslPolicy;
177 $currURLPath = $this->urlPath;
179 if (!empty($hostname) && $hostname !== $this->hostname) {
180 if ($this->config->set('config', 'hostname', $hostname)) {
181 $this->hostname = $hostname;
187 if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) {
188 if ($this->config->set('system', 'ssl_policy', $sslPolicy)) {
189 $this->sslPolicy = $sslPolicy;
191 $this->hostname = $currHostname;
192 $this->config->set('config', 'hostname', $this->hostname);
197 if (isset($urlPath) && $urlPath !== $this->urlPath) {
198 if ($this->config->set('system', 'urlpath', $urlPath)) {
199 $this->urlPath = $urlPath;
201 $this->hostname = $currHostname;
202 $this->sslPolicy = $currSSLPolicy;
203 $this->config->set('config', 'hostname', $this->hostname);
204 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
209 $this->determineBaseUrl();
210 if (!$this->config->set('system', 'url', $this->url)) {
211 $this->hostname = $currHostname;
212 $this->sslPolicy = $currSSLPolicy;
213 $this->urlPath = $currURLPath;
214 $this->determineBaseUrl();
216 $this->config->set('config', 'hostname', $this->hostname);
217 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
218 $this->config->set('system', 'urlpath', $this->urlPath);
226 * Save the current url as base URL
230 * @return bool true, if the save was successful
232 public function saveByURL(string $url): bool
234 $parsed = @parse_url($url);
236 if (empty($parsed) || empty($parsed['host'])) {
240 $hostname = $parsed['host'];
241 if (!empty($hostname) && !empty($parsed['port'])) {
242 $hostname .= ':' . $parsed['port'];
246 if (!empty($parsed['path'])) {
247 $urlPath = trim($parsed['path'], '\\/');
251 if (!empty($parsed['scheme'])) {
252 if ($parsed['scheme'] == 'https') {
253 $sslPolicy = BaseURL::SSL_POLICY_FULL;
257 return $this->save($hostname, $sslPolicy, $urlPath);
261 * Checks, if a redirect to the HTTPS site would be necessary
265 public function checkRedirectHttps()
267 return $this->config->get('system', 'force_ssl') &&
268 ($this->getScheme() == "http") &&
269 intval($this->getSSLPolicy()) == BaseURL::SSL_POLICY_FULL &&
270 strpos($this->get(), 'https://') === 0 &&
271 !empty($this->server['REQUEST_METHOD']) &&
272 $this->server['REQUEST_METHOD'] === 'GET';
276 * @param IManageConfigValues $config The Friendica IConfiguration
277 * @param array $server The $_SERVER array
279 public function __construct(IManageConfigValues $config, array $server)
281 $this->config = $config;
282 $this->server = $server;
284 $this->determineSchema();
285 $this->checkConfig();
289 * Check the current config during loading
291 public function checkConfig()
293 $this->hostname = $this->config->get('config', 'hostname');
294 $this->urlPath = $this->config->get('system', 'urlpath');
295 $this->sslPolicy = $this->config->get('system', 'ssl_policy');
296 $this->url = $this->config->get('system', 'url');
298 if (empty($this->hostname)) {
299 $this->determineHostname();
301 if (!empty($this->hostname)) {
302 $this->config->set('config', 'hostname', $this->hostname);
306 if (!isset($this->urlPath)) {
307 $this->determineURLPath();
308 $this->config->set('system', 'urlpath', $this->urlPath);
311 if (!isset($this->sslPolicy)) {
312 if ($this->scheme == 'https') {
313 $this->sslPolicy = self::SSL_POLICY_FULL;
315 $this->sslPolicy = self::DEFAULT_SSL_SCHEME;
317 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
320 if (empty($this->url)) {
321 $this->determineBaseUrl();
323 if (!empty($this->url)) {
324 $this->config->set('system', 'url', $this->url);
330 * Determines the hostname of this node if not set already
332 private function determineHostname()
334 $this->hostname = '';
336 if (!empty($this->server['SERVER_NAME'])) {
337 $this->hostname = $this->server['SERVER_NAME'];
339 if (!empty($this->server['SERVER_PORT']) && $this->server['SERVER_PORT'] != 80 && $this->server['SERVER_PORT'] != 443) {
340 $this->hostname .= ':' . $this->server['SERVER_PORT'];
346 * Figure out if we are running at the top of a domain or in a sub-directory
348 private function determineURLPath()
353 * The automatic path detection in this function is currently deactivated,
354 * see issue https://github.com/friendica/friendica/issues/6679
356 * The problem is that the function seems to be confused with some url.
357 * These then confuses the detection which changes the url path.
360 /* Relative script path to the web server root
361 * Not all of those $_SERVER properties can be present, so we do by inverse priority order
363 $relative_script_path =
364 ($this->server['REDIRECT_URL'] ?? '') ?:
365 ($this->server['REDIRECT_URI'] ?? '') ?:
366 ($this->server['REDIRECT_SCRIPT_URL'] ?? '') ?:
367 ($this->server['SCRIPT_URL'] ?? '') ?:
368 $this->server['REQUEST_URI'] ?? '';
370 /* $relative_script_path gives /relative/path/to/friendica/module/parameter
371 * QUERY_STRING gives pagename=module/parameter
373 * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING
375 if (!empty($relative_script_path)) {
377 if (!empty($this->server['QUERY_STRING'])) {
378 $this->urlPath = trim(dirname($relative_script_path, substr_count(trim($this->server['QUERY_STRING'], '/'), '/') + 1), '/');
381 $this->urlPath = trim($relative_script_path, '/');
387 * Determine the full URL based on all parts
389 private function determineBaseUrl()
393 if ($this->sslPolicy == self::SSL_POLICY_FULL) {
397 $this->url = $scheme . '://' . $this->hostname . (!empty($this->urlPath) ? '/' . $this->urlPath : '');
401 * Determine the scheme of the current used link
403 private function determineSchema()
405 $this->scheme = 'http';
407 if (!empty($this->server['HTTPS']) ||
408 !empty($this->server['HTTP_FORWARDED']) && preg_match('/proto=https/', $this->server['HTTP_FORWARDED']) ||
409 !empty($this->server['HTTP_X_FORWARDED_PROTO']) && $this->server['HTTP_X_FORWARDED_PROTO'] == 'https' ||
410 !empty($this->server['HTTP_X_FORWARDED_SSL']) && $this->server['HTTP_X_FORWARDED_SSL'] == 'on' ||
411 !empty($this->server['FRONT_END_HTTPS']) && $this->server['FRONT_END_HTTPS'] == 'on' ||
412 !empty($this->server['SERVER_PORT']) && (intval($this->server['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much?
414 $this->scheme = 'https';
419 * Removes the base url from an url. This avoids some mixed content problems.
421 * @param string $origURL
423 * @return string The cleaned url
425 public function remove(string $origURL): string
427 // Remove the hostname from the url if it is an internal link
428 $nurl = Strings::normaliseLink($origURL);
429 $base = Strings::normaliseLink($this->get());
430 $url = str_replace($base . '/', '', $nurl);
432 // if it is an external link return the orignal value
433 if ($url == Strings::normaliseLink($origURL)) {
441 * Redirects to another module relative to the current Friendica base URL.
442 * If you want to redirect to a external URL, use System::externalRedirectTo()
444 * @param string $toUrl The destination URL (Default is empty, which is the default page of the Friendica node)
445 * @param bool $ssl if true, base URL will try to get called with https:// (works just for relative paths)
447 * @throws HTTPException\FoundException
448 * @throws HTTPException\MovedPermanentlyException
449 * @throws HTTPException\TemporaryRedirectException
451 * @throws HTTPException\InternalServerErrorException In Case the given URL is not relative to the Friendica node
453 public function redirect(string $toUrl = '', bool $ssl = false)
455 if (!empty(parse_url($toUrl, PHP_URL_SCHEME))) {
456 throw new HTTPException\InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo");
459 $redirectTo = $this->get($ssl) . '/' . ltrim($toUrl, '/');
460 System::externalRedirect($redirectTo);
464 * Returns the base url as string
466 public function __toString(): string
468 return (string) $this->get();