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