]> git.mxchange.org Git - friendica.git/blob - src/App/BaseURL.php
Merge remote-tracking branch 'upstream/develop' into item-notification
[friendica.git] / src / App / BaseURL.php
1 <?php
2
3 namespace Friendica\App;
4
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;
10
11 /**
12  * A class which checks and contains the basic
13  * environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
14  */
15 class BaseURL
16 {
17         /**
18          * No SSL necessary
19          */
20         const SSL_POLICY_NONE = 0;
21
22         /**
23          * SSL is necessary
24          */
25         const SSL_POLICY_FULL = 1;
26
27         /**
28          * SSL is optional, but preferred
29          */
30         const SSL_POLICY_SELFSIGN = 2;
31
32         /**
33          * Define the Default SSL scheme
34          */
35         const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN;
36
37         /**
38          * The Friendica Config
39          *
40          * @var IConfiguration
41          */
42         private $config;
43
44         /**
45          * The server side variables
46          *
47          * @var array
48          */
49         private $server;
50
51         /**
52          * The hostname of the Base URL
53          *
54          * @var string
55          */
56         private $hostname;
57
58         /**
59          * The SSL_POLICY of the Base URL
60          *
61          * @var int
62          */
63         private $sslPolicy;
64
65         /**
66          * The URL sub-path of the Base URL
67          *
68          * @var string
69          */
70         private $urlPath;
71
72         /**
73          * The full URL
74          *
75          * @var string
76          */
77         private $url;
78
79         /**
80          * The current scheme of this call
81          *
82          * @var string
83          */
84         private $scheme;
85
86         /**
87          * Returns the hostname of this node
88          *
89          * @return string
90          */
91         public function getHostname()
92         {
93                 return $this->hostname;
94         }
95
96         /**
97          * Returns the current scheme of this call
98          *
99          * @return string
100          */
101         public function getScheme()
102         {
103                 return $this->scheme;
104         }
105
106         /**
107          * Returns the SSL policy of this node
108          *
109          * @return int
110          */
111         public function getSSLPolicy()
112         {
113                 return $this->sslPolicy;
114         }
115
116         /**
117          * Returns the sub-path of this URL
118          *
119          * @return string
120          */
121         public function getUrlPath()
122         {
123                 return $this->urlPath;
124         }
125
126         /**
127          * Returns the full URL of this call
128          *
129          * Note: $ssl parameter value doesn't directly correlate with the resulting protocol
130          *
131          * @param bool $ssl True, if ssl should get used
132          *
133          * @return string
134          */
135         public function get($ssl = false)
136         {
137                 if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) {
138                         return Network::switchScheme($this->url);
139                 }
140
141                 return $this->url;
142         }
143
144         /**
145          * Save current parts of the base Url
146          *
147          * @param string? $hostname
148          * @param int?    $sslPolicy
149          * @param string? $urlPath
150          *
151          * @return bool true, if successful
152          */
153         public function save($hostname = null, $sslPolicy = null, $urlPath = null)
154         {
155                 $currHostname  = $this->hostname;
156                 $currSSLPolicy = $this->sslPolicy;
157                 $currURLPath   = $this->urlPath;
158
159                 if (!empty($hostname) && $hostname !== $this->hostname) {
160                         if ($this->config->set('config', 'hostname', $hostname)) {
161                                 $this->hostname = $hostname;
162                         } else {
163                                 return false;
164                         }
165                 }
166
167                 if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) {
168                         if ($this->config->set('system', 'ssl_policy', $sslPolicy)) {
169                                 $this->sslPolicy = $sslPolicy;
170                         } else {
171                                 $this->hostname = $currHostname;
172                                 $this->config->set('config', 'hostname', $this->hostname);
173                                 return false;
174                         }
175                 }
176
177                 if (isset($urlPath) && $urlPath !== $this->urlPath) {
178                         if ($this->config->set('system', 'urlpath', $urlPath)) {
179                                 $this->urlPath = $urlPath;
180                         } else {
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);
185                                 return false;
186                         }
187                 }
188
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();
195
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);
199                         return false;
200                 }
201
202                 return true;
203         }
204
205         /**
206          * Save the current url as base URL
207          *
208          * @param $url
209          *
210          * @return bool true, if the save was successful
211          */
212         public function saveByURL($url)
213         {
214                 $parsed = @parse_url($url);
215
216                 if (empty($parsed)) {
217                         return false;
218                 }
219
220                 $hostname = $parsed['host'];
221                 if (!empty($hostname) && !empty($parsed['port'])) {
222                         $hostname .= ':' . $parsed['port'];
223                 }
224
225                 $urlPath = null;
226                 if (!empty($parsed['path'])) {
227                         $urlPath = trim($parsed['path'], '\\/');
228                 }
229
230                 $sslPolicy = null;
231                 if (!empty($parsed['scheme'])) {
232                         if ($parsed['scheme'] == 'https') {
233                                 $sslPolicy = BaseURL::SSL_POLICY_FULL;
234                         }
235                 }
236
237                 return $this->save($hostname, $sslPolicy, $urlPath);
238         }
239
240         /**
241          * Checks, if a redirect to the HTTPS site would be necessary
242          *
243          * @return bool
244          */
245         public function checkRedirectHttps()
246         {
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';
253         }
254
255         /**
256          * @param IConfiguration $config The Friendica IConfiguration
257          * @param array         $server The $_SERVER array
258          */
259         public function __construct(IConfiguration $config, array $server)
260         {
261                 $this->config = $config;
262                 $this->server = $server;
263
264                 $this->determineSchema();
265                 $this->checkConfig();
266         }
267
268         /**
269          * Check the current config during loading
270          */
271         public function checkConfig()
272         {
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');
277
278                 if (empty($this->hostname)) {
279                         $this->determineHostname();
280
281                         if (!empty($this->hostname)) {
282                                 $this->config->set('config', 'hostname', $this->hostname);
283                         }
284                 }
285
286                 if (!isset($this->urlPath)) {
287                         $this->determineURLPath();
288                         $this->config->set('system', 'urlpath', $this->urlPath);
289                 }
290
291                 if (!isset($this->sslPolicy)) {
292                         if ($this->scheme == 'https') {
293                                 $this->sslPolicy = self::SSL_POLICY_FULL;
294                         } else {
295                                 $this->sslPolicy = self::DEFAULT_SSL_SCHEME;
296                         }
297                         $this->config->set('system', 'ssl_policy', $this->sslPolicy);
298                 }
299
300                 if (empty($this->url)) {
301                         $this->determineBaseUrl();
302
303                         if (!empty($this->url)) {
304                                 $this->config->set('system', 'url', $this->url);
305                         }
306                 }
307         }
308
309         /**
310          * Determines the hostname of this node if not set already
311          */
312         private function determineHostname()
313         {
314                 $this->hostname = '';
315
316                 if (!empty($this->server['SERVER_NAME'])) {
317                         $this->hostname = $this->server['SERVER_NAME'];
318
319                         if (!empty($this->server['SERVER_PORT']) && $this->server['SERVER_PORT'] != 80 && $this->server['SERVER_PORT'] != 443) {
320                                 $this->hostname .= ':' . $this->server['SERVER_PORT'];
321                         }
322                 }
323         }
324
325         /**
326          * Figure out if we are running at the top of a domain or in a sub-directory
327          */
328         private function determineURLPath()
329         {
330                 $this->urlPath = '';
331
332                 /*
333                  * The automatic path detection in this function is currently deactivated,
334                  * see issue https://github.com/friendica/friendica/issues/6679
335                  *
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.
338                  */
339
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
342                  */
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']         ?? '';
349
350                 /* $relative_script_path gives /relative/path/to/friendica/module/parameter
351                  * QUERY_STRING gives pagename=module/parameter
352                  *
353                  * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING
354                  */
355                 if (!empty($relative_script_path)) {
356                         // Module
357                         if (!empty($this->server['QUERY_STRING'])) {
358                                 $this->urlPath = trim(dirname($relative_script_path, substr_count(trim($this->server['QUERY_STRING'], '/'), '/') + 1), '/');
359                         } else {
360                                 // Root page
361                                 $this->urlPath = trim($relative_script_path, '/');
362                         }
363                 }
364         }
365
366         /**
367          * Determine the full URL based on all parts
368          */
369         private function determineBaseUrl()
370         {
371                 $scheme = 'http';
372
373                 if ($this->sslPolicy == self::SSL_POLICY_FULL) {
374                         $scheme = 'https';
375                 }
376
377                 $this->url = $scheme . '://' . $this->hostname . (!empty($this->urlPath) ? '/' . $this->urlPath : '');
378         }
379
380         /**
381          * Determine the scheme of the current used link
382          */
383         private function determineSchema()
384         {
385                 $this->scheme = 'http';
386
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?
393                 ) {
394                         $this->scheme = 'https';
395                 }
396         }
397
398         /**
399          * Removes the base url from an url. This avoids some mixed content problems.
400          *
401          * @param string $origURL
402          *
403          * @return string The cleaned url
404          */
405         public function remove(string $origURL)
406         {
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);
411
412                 // if it is an external link return the orignal value
413                 if ($url == Strings::normaliseLink($origURL)) {
414                         return $origURL;
415                 } else {
416                         return $url;
417                 }
418         }
419
420         /**
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()
423          *
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)
426          *
427          * @throws HTTPException\InternalServerErrorException In Case the given URL is not relative to the Friendica node
428          */
429         public function redirect($toUrl = '', $ssl = false)
430         {
431                 if (!empty(parse_url($toUrl, PHP_URL_SCHEME))) {
432                         throw new HTTPException\InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo");
433                 }
434
435                 $redirectTo = $this->get($ssl) . '/' . ltrim($toUrl, '/');
436                 System::externalRedirect($redirectTo);
437         }
438
439         /**
440          * Returns the base url as string
441          */
442         public function __toString()
443         {
444                 return $this->get();
445         }
446 }