]> git.mxchange.org Git - friendica.git/blob - src/App/BaseURL.php
Merge pull request #7540 from vinzv/patch-1
[friendica.git] / src / App / BaseURL.php
1 <?php
2
3 namespace Friendica\App;
4
5 use Friendica\Core\Config\Configuration;
6 use Friendica\Util\Network;
7 use Friendica\Util\Strings;
8
9 /**
10  * A class which checks and contains the basic
11  * environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
12  */
13 class BaseURL
14 {
15         /**
16          * No SSL necessary
17          */
18         const SSL_POLICY_NONE = 0;
19
20         /**
21          * SSL is necessary
22          */
23         const SSL_POLICY_FULL = 1;
24
25         /**
26          * SSL is optional, but preferred
27          */
28         const SSL_POLICY_SELFSIGN = 2;
29
30         /**
31          * Define the Default SSL scheme
32          */
33         const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN;
34
35         /**
36          * The Friendica Config
37          *
38          * @var Configuration
39          */
40         private $config;
41
42         /**
43          * The server side variables
44          *
45          * @var array
46          */
47         private $server;
48
49         /**
50          * The hostname of the Base URL
51          *
52          * @var string
53          */
54         private $hostname;
55
56         /**
57          * The SSL_POLICY of the Base URL
58          *
59          * @var int
60          */
61         private $sslPolicy;
62
63         /**
64          * The URL sub-path of the Base URL
65          *
66          * @var string
67          */
68         private $urlPath;
69
70         /**
71          * The full URL
72          *
73          * @var string
74          */
75         private $url;
76
77         /**
78          * The current scheme of this call
79          *
80          * @var string
81          */
82         private $scheme;
83
84         /**
85          * Returns the hostname of this node
86          *
87          * @return string
88          */
89         public function getHostname()
90         {
91                 return $this->hostname;
92         }
93
94         /**
95          * Returns the current scheme of this call
96          *
97          * @return string
98          */
99         public function getScheme()
100         {
101                 return $this->scheme;
102         }
103
104         /**
105          * Returns the SSL policy of this node
106          *
107          * @return int
108          */
109         public function getSSLPolicy()
110         {
111                 return $this->sslPolicy;
112         }
113
114         /**
115          * Returns the sub-path of this URL
116          *
117          * @return string
118          */
119         public function getUrlPath()
120         {
121                 return $this->urlPath;
122         }
123
124         /**
125          * Returns the full URL of this call
126          *
127          * Note: $ssl parameter value doesn't directly correlate with the resulting protocol
128          *
129          * @param bool $ssl True, if ssl should get used
130          *
131          * @return string
132          */
133         public function get($ssl = false)
134         {
135                 if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) {
136                         return Network::switchScheme($this->url);
137                 }
138
139                 return $this->url;
140         }
141
142         /**
143          * Save current parts of the base Url
144          *
145          * @param string? $hostname
146          * @param int?    $sslPolicy
147          * @param string? $urlPath
148          *
149          * @return bool true, if successful
150          */
151         public function save($hostname = null, $sslPolicy = null, $urlPath = null)
152         {
153                 $currHostname  = $this->hostname;
154                 $currSSLPolicy = $this->sslPolicy;
155                 $currURLPath   = $this->urlPath;
156
157                 if (!empty($hostname) && $hostname !== $this->hostname) {
158                         if ($this->config->set('config', 'hostname', $hostname)) {
159                                 $this->hostname = $hostname;
160                         } else {
161                                 return false;
162                         }
163                 }
164
165                 if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) {
166                         if ($this->config->set('system', 'ssl_policy', $sslPolicy)) {
167                                 $this->sslPolicy = $sslPolicy;
168                         } else {
169                                 $this->hostname = $currHostname;
170                                 $this->config->set('config', 'hostname', $this->hostname);
171                                 return false;
172                         }
173                 }
174
175                 if (isset($urlPath) && $urlPath !== $this->urlPath) {
176                         if ($this->config->set('system', 'urlpath', $urlPath)) {
177                                 $this->urlPath = $urlPath;
178                         } else {
179                                 $this->hostname  = $currHostname;
180                                 $this->sslPolicy = $currSSLPolicy;
181                                 $this->config->set('config', 'hostname', $this->hostname);
182                                 $this->config->set('system', 'ssl_policy', $this->sslPolicy);
183                                 return false;
184                         }
185                 }
186
187                 $this->determineBaseUrl();
188                 if (!$this->config->set('system', 'url', $this->url)) {
189                         $this->hostname  = $currHostname;
190                         $this->sslPolicy = $currSSLPolicy;
191                         $this->urlPath   = $currURLPath;
192                         $this->determineBaseUrl();
193
194                         $this->config->set('config', 'hostname', $this->hostname);
195                         $this->config->set('system', 'ssl_policy', $this->sslPolicy);
196                         $this->config->set('system', 'urlpath', $this->urlPath);
197                         return false;
198                 }
199
200                 return true;
201         }
202
203         /**
204          * Save the current url as base URL
205          *
206          * @param $url
207          *
208          * @return bool true, if the save was successful
209          */
210         public function saveByURL($url)
211         {
212                 $parsed = @parse_url($url);
213
214                 if (empty($parsed)) {
215                         return false;
216                 }
217
218                 $hostname = $parsed['host'];
219                 if (!empty($hostname) && !empty($parsed['port'])) {
220                         $hostname .= ':' . $parsed['port'];
221                 }
222
223                 $urlPath = null;
224                 if (!empty($parsed['path'])) {
225                         $urlPath = trim($parsed['path'], '\\/');
226                 }
227
228                 $sslPolicy = null;
229                 if (!empty($parsed['scheme'])) {
230                         if ($parsed['scheme'] == 'https') {
231                                 $sslPolicy = BaseURL::SSL_POLICY_FULL;
232                         }
233                 }
234
235                 return $this->save($hostname, $sslPolicy, $urlPath);
236         }
237
238         /**
239          * Checks, if a redirect to the HTTPS site would be necessary
240          *
241          * @return bool
242          */
243         public function checkRedirectHttps()
244         {
245                 return $this->config->get('system', 'force_ssl') &&
246                        ($this->getScheme() == "http") &&
247                        intval($this->getSSLPolicy()) == BaseURL::SSL_POLICY_FULL &&
248                        strpos($this->get(), 'https://') === 0 &&
249                        !empty($this->server['REQUEST_METHOD']) &&
250                        $this->server['REQUEST_METHOD'] === 'GET';
251         }
252
253         /**
254          * @param Configuration $config The Friendica configuration
255          * @param array         $server The $_SERVER array
256          */
257         public function __construct(Configuration $config, array $server)
258         {
259                 $this->config = $config;
260                 $this->server = $server;
261
262                 $this->determineSchema();
263                 $this->checkConfig();
264         }
265
266         /**
267          * Check the current config during loading
268          */
269         public function checkConfig()
270         {
271                 $this->hostname  = $this->config->get('config', 'hostname');
272                 $this->urlPath   = $this->config->get('system', 'urlpath');
273                 $this->sslPolicy = $this->config->get('system', 'ssl_policy');
274                 $this->url       = $this->config->get('system', 'url');
275
276                 if (empty($this->hostname)) {
277                         $this->determineHostname();
278
279                         if (!empty($this->hostname)) {
280                                 $this->config->set('config', 'hostname', $this->hostname);
281                         }
282                 }
283
284                 if (!isset($this->urlPath)) {
285                         $this->determineURLPath();
286                         $this->config->set('system', 'urlpath', $this->urlPath);
287                 }
288
289                 if (!isset($this->sslPolicy)) {
290                         if ($this->scheme == 'https') {
291                                 $this->sslPolicy = self::SSL_POLICY_FULL;
292                         } else {
293                                 $this->sslPolicy = self::DEFAULT_SSL_SCHEME;
294                         }
295                         $this->config->set('system', 'ssl_policy', $this->sslPolicy);
296                 }
297
298                 if (empty($this->url)) {
299                         $this->determineBaseUrl();
300
301                         if (!empty($this->url)) {
302                                 $this->config->set('system', 'url', $this->url);
303                         }
304                 }
305         }
306
307         /**
308          * Determines the hostname of this node if not set already
309          */
310         private function determineHostname()
311         {
312                 $this->hostname = '';
313
314                 if (!empty($this->server['SERVER_NAME'])) {
315                         $this->hostname = $this->server['SERVER_NAME'];
316
317                         if (!empty($this->server['SERVER_PORT']) && $this->server['SERVER_PORT'] != 80 && $this->server['SERVER_PORT'] != 443) {
318                                 $this->hostname .= ':' . $this->server['SERVER_PORT'];
319                         }
320                 }
321         }
322
323         /**
324          * Figure out if we are running at the top of a domain or in a sub-directory
325          */
326         private function determineURLPath()
327         {
328                 $this->urlPath = '';
329
330                 /*
331                  * The automatic path detection in this function is currently deactivated,
332                  * see issue https://github.com/friendica/friendica/issues/6679
333                  *
334                  * The problem is that the function seems to be confused with some url.
335                  * These then confuses the detection which changes the url path.
336                  */
337
338                 /* Relative script path to the web server root
339                  * Not all of those $_SERVER properties can be present, so we do by inverse priority order
340                  */
341                 $relative_script_path = '';
342                 $relative_script_path = defaults($this->server, 'REDIRECT_URL', $relative_script_path);
343                 $relative_script_path = defaults($this->server, 'REDIRECT_URI', $relative_script_path);
344                 $relative_script_path = defaults($this->server, 'REDIRECT_SCRIPT_URL', $relative_script_path);
345                 $relative_script_path = defaults($this->server, 'SCRIPT_URL', $relative_script_path);
346                 $relative_script_path = defaults($this->server, 'REQUEST_URI', $relative_script_path);
347
348                 /* $relative_script_path gives /relative/path/to/friendica/module/parameter
349                  * QUERY_STRING gives pagename=module/parameter
350                  *
351                  * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING
352                  */
353                 if (!empty($relative_script_path)) {
354                         // Module
355                         if (!empty($this->server['QUERY_STRING'])) {
356                                 $this->urlPath = trim(rdirname($relative_script_path, substr_count(trim($this->server['QUERY_STRING'], '/'), '/') + 1), '/');
357                         } else {
358                                 // Root page
359                                 $this->urlPath = trim($relative_script_path, '/');
360                         }
361                 }
362         }
363
364         /**
365          * Determine the full URL based on all parts
366          */
367         private function determineBaseUrl()
368         {
369                 $scheme = 'http';
370
371                 if ($this->sslPolicy == self::SSL_POLICY_FULL) {
372                         $scheme = 'https';
373                 }
374
375                 $this->url = $scheme . '://' . $this->hostname . (!empty($this->urlPath) ? '/' . $this->urlPath : '');
376         }
377
378         /**
379          * Determine the scheme of the current used link
380          */
381         private function determineSchema()
382         {
383                 $this->scheme = 'http';
384
385                 if (!empty($this->server['HTTPS']) ||
386                     !empty($this->server['HTTP_FORWARDED']) && preg_match('/proto=https/', $this->server['HTTP_FORWARDED']) ||
387                     !empty($this->server['HTTP_X_FORWARDED_PROTO']) && $this->server['HTTP_X_FORWARDED_PROTO'] == 'https' ||
388                     !empty($this->server['HTTP_X_FORWARDED_SSL']) && $this->server['HTTP_X_FORWARDED_SSL'] == 'on' ||
389                     !empty($this->server['FRONT_END_HTTPS']) && $this->server['FRONT_END_HTTPS'] == 'on' ||
390                     !empty($this->server['SERVER_PORT']) && (intval($this->server['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much?
391                 ) {
392                         $this->scheme = 'https';
393                 }
394         }
395
396         /**
397          * Removes the base url from an url. This avoids some mixed content problems.
398          *
399          * @param string $origURL
400          *
401          * @return string The cleaned url
402          */
403         public function remove(string $origURL)
404         {
405                 // Remove the hostname from the url if it is an internal link
406                 $nurl = Strings::normaliseLink($origURL);
407                 $base = Strings::normaliseLink($this->get());
408                 $url  = str_replace($base . '/', '', $nurl);
409
410                 // if it is an external link return the orignal value
411                 if ($url == Strings::normaliseLink($origURL)) {
412                         return $origURL;
413                 } else {
414                         return $url;
415                 }
416         }
417 }