3 namespace Friendica\App;
5 use Friendica\Core\Config\IConfiguration;
6 use Friendica\Core\System;
7 use Friendica\Util\Network;
8 use Friendica\Util\Strings;
9 use Friendica\Network\HTTPException;
12 * A class which checks and contains the basic
13 * environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
20 const SSL_POLICY_NONE = 0;
25 const SSL_POLICY_FULL = 1;
28 * SSL is optional, but preferred
30 const SSL_POLICY_SELFSIGN = 2;
33 * Define the Default SSL scheme
35 const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN;
38 * The Friendica Config
45 * The server side variables
52 * The hostname of the Base URL
59 * The SSL_POLICY of the Base URL
66 * The URL sub-path of the Base URL
80 * The current scheme of this call
87 * Returns the hostname of this node
91 public function getHostname()
93 return $this->hostname;
97 * Returns the current scheme of this call
101 public function getScheme()
103 return $this->scheme;
107 * Returns the SSL policy of this node
111 public function getSSLPolicy()
113 return $this->sslPolicy;
117 * Returns the sub-path of this URL
121 public function getUrlPath()
123 return $this->urlPath;
127 * Returns the full URL of this call
129 * Note: $ssl parameter value doesn't directly correlate with the resulting protocol
131 * @param bool $ssl True, if ssl should get used
135 public function get($ssl = false)
137 if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) {
138 return Network::switchScheme($this->url);
145 * Save current parts of the base Url
147 * @param string? $hostname
148 * @param int? $sslPolicy
149 * @param string? $urlPath
151 * @return bool true, if successful
153 public function save($hostname = null, $sslPolicy = null, $urlPath = null)
155 $currHostname = $this->hostname;
156 $currSSLPolicy = $this->sslPolicy;
157 $currURLPath = $this->urlPath;
159 if (!empty($hostname) && $hostname !== $this->hostname) {
160 if ($this->config->set('config', 'hostname', $hostname)) {
161 $this->hostname = $hostname;
167 if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) {
168 if ($this->config->set('system', 'ssl_policy', $sslPolicy)) {
169 $this->sslPolicy = $sslPolicy;
171 $this->hostname = $currHostname;
172 $this->config->set('config', 'hostname', $this->hostname);
177 if (isset($urlPath) && $urlPath !== $this->urlPath) {
178 if ($this->config->set('system', 'urlpath', $urlPath)) {
179 $this->urlPath = $urlPath;
181 $this->hostname = $currHostname;
182 $this->sslPolicy = $currSSLPolicy;
183 $this->config->set('config', 'hostname', $this->hostname);
184 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
189 $this->determineBaseUrl();
190 if (!$this->config->set('system', 'url', $this->url)) {
191 $this->hostname = $currHostname;
192 $this->sslPolicy = $currSSLPolicy;
193 $this->urlPath = $currURLPath;
194 $this->determineBaseUrl();
196 $this->config->set('config', 'hostname', $this->hostname);
197 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
198 $this->config->set('system', 'urlpath', $this->urlPath);
206 * Save the current url as base URL
210 * @return bool true, if the save was successful
212 public function saveByURL($url)
214 $parsed = @parse_url($url);
216 if (empty($parsed)) {
220 $hostname = $parsed['host'];
221 if (!empty($hostname) && !empty($parsed['port'])) {
222 $hostname .= ':' . $parsed['port'];
226 if (!empty($parsed['path'])) {
227 $urlPath = trim($parsed['path'], '\\/');
231 if (!empty($parsed['scheme'])) {
232 if ($parsed['scheme'] == 'https') {
233 $sslPolicy = BaseURL::SSL_POLICY_FULL;
237 return $this->save($hostname, $sslPolicy, $urlPath);
241 * Checks, if a redirect to the HTTPS site would be necessary
245 public function checkRedirectHttps()
247 return $this->config->get('system', 'force_ssl') &&
248 ($this->getScheme() == "http") &&
249 intval($this->getSSLPolicy()) == BaseURL::SSL_POLICY_FULL &&
250 strpos($this->get(), 'https://') === 0 &&
251 !empty($this->server['REQUEST_METHOD']) &&
252 $this->server['REQUEST_METHOD'] === 'GET';
256 * @param IConfiguration $config The Friendica IConfiguration
257 * @param array $server The $_SERVER array
259 public function __construct(IConfiguration $config, array $server)
261 $this->config = $config;
262 $this->server = $server;
264 $this->determineSchema();
265 $this->checkConfig();
269 * Check the current config during loading
271 public function checkConfig()
273 $this->hostname = $this->config->get('config', 'hostname');
274 $this->urlPath = $this->config->get('system', 'urlpath');
275 $this->sslPolicy = $this->config->get('system', 'ssl_policy');
276 $this->url = $this->config->get('system', 'url');
278 if (empty($this->hostname)) {
279 $this->determineHostname();
281 if (!empty($this->hostname)) {
282 $this->config->set('config', 'hostname', $this->hostname);
286 if (!isset($this->urlPath)) {
287 $this->determineURLPath();
288 $this->config->set('system', 'urlpath', $this->urlPath);
291 if (!isset($this->sslPolicy)) {
292 if ($this->scheme == 'https') {
293 $this->sslPolicy = self::SSL_POLICY_FULL;
295 $this->sslPolicy = self::DEFAULT_SSL_SCHEME;
297 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
300 if (empty($this->url)) {
301 $this->determineBaseUrl();
303 if (!empty($this->url)) {
304 $this->config->set('system', 'url', $this->url);
310 * Determines the hostname of this node if not set already
312 private function determineHostname()
314 $this->hostname = '';
316 if (!empty($this->server['SERVER_NAME'])) {
317 $this->hostname = $this->server['SERVER_NAME'];
319 if (!empty($this->server['SERVER_PORT']) && $this->server['SERVER_PORT'] != 80 && $this->server['SERVER_PORT'] != 443) {
320 $this->hostname .= ':' . $this->server['SERVER_PORT'];
326 * Figure out if we are running at the top of a domain or in a sub-directory
328 private function determineURLPath()
333 * The automatic path detection in this function is currently deactivated,
334 * see issue https://github.com/friendica/friendica/issues/6679
336 * The problem is that the function seems to be confused with some url.
337 * These then confuses the detection which changes the url path.
340 /* Relative script path to the web server root
341 * Not all of those $_SERVER properties can be present, so we do by inverse priority order
343 $relative_script_path =
344 ($this->server['REDIRECT_URL'] ?? '') ?:
345 ($this->server['REDIRECT_URI'] ?? '') ?:
346 ($this->server['REDIRECT_SCRIPT_URL'] ?? '') ?:
347 ($this->server['SCRIPT_URL'] ?? '') ?:
348 $this->server['REQUEST_URI'] ?? '';
350 /* $relative_script_path gives /relative/path/to/friendica/module/parameter
351 * QUERY_STRING gives pagename=module/parameter
353 * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING
355 if (!empty($relative_script_path)) {
357 if (!empty($this->server['QUERY_STRING'])) {
358 $this->urlPath = trim(rdirname($relative_script_path, substr_count(trim($this->server['QUERY_STRING'], '/'), '/') + 1), '/');
361 $this->urlPath = trim($relative_script_path, '/');
367 * Determine the full URL based on all parts
369 private function determineBaseUrl()
373 if ($this->sslPolicy == self::SSL_POLICY_FULL) {
377 $this->url = $scheme . '://' . $this->hostname . (!empty($this->urlPath) ? '/' . $this->urlPath : '');
381 * Determine the scheme of the current used link
383 private function determineSchema()
385 $this->scheme = 'http';
387 if (!empty($this->server['HTTPS']) ||
388 !empty($this->server['HTTP_FORWARDED']) && preg_match('/proto=https/', $this->server['HTTP_FORWARDED']) ||
389 !empty($this->server['HTTP_X_FORWARDED_PROTO']) && $this->server['HTTP_X_FORWARDED_PROTO'] == 'https' ||
390 !empty($this->server['HTTP_X_FORWARDED_SSL']) && $this->server['HTTP_X_FORWARDED_SSL'] == 'on' ||
391 !empty($this->server['FRONT_END_HTTPS']) && $this->server['FRONT_END_HTTPS'] == 'on' ||
392 !empty($this->server['SERVER_PORT']) && (intval($this->server['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much?
394 $this->scheme = 'https';
399 * Removes the base url from an url. This avoids some mixed content problems.
401 * @param string $origURL
403 * @return string The cleaned url
405 public function remove(string $origURL)
407 // Remove the hostname from the url if it is an internal link
408 $nurl = Strings::normaliseLink($origURL);
409 $base = Strings::normaliseLink($this->get());
410 $url = str_replace($base . '/', '', $nurl);
412 // if it is an external link return the orignal value
413 if ($url == Strings::normaliseLink($origURL)) {
421 * Redirects to another module relative to the current Friendica base URL.
422 * If you want to redirect to a external URL, use System::externalRedirectTo()
424 * @param string $toUrl The destination URL (Default is empty, which is the default page of the Friendica node)
425 * @param bool $ssl if true, base URL will try to get called with https:// (works just for relative paths)
427 * @throws HTTPException\InternalServerErrorException In Case the given URL is not relative to the Friendica node
429 public function redirect($toUrl = '', $ssl = false)
431 if (!empty(parse_url($toUrl, PHP_URL_SCHEME))) {
432 throw new HTTPException\InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo");
435 $redirectTo = $this->get($ssl) . '/' . ltrim($toUrl, '/');
436 System::externalRedirect($redirectTo);