diff --git a/include/class_URL.inc b/include/class_URL.inc
new file mode 100644
index 0000000000000000000000000000000000000000..9788c1820117d9b48403430d64f8e36a27ec8a40
--- /dev/null
+++ b/include/class_URL.inc
@@ -0,0 +1,127 @@
+<?php
+/*
+  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
+
+  Copyright (C) 2018-2019  FusionDirectory
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+/*!
+ * \brief Class URL
+ * Static methods to get/build URLs
+ */
+
+class URL
+{
+  /*! \brief Returns TRUE if SSL was used to contact FD, whether directly or through a proxy
+   */
+  public static function sslOn (): bool
+  {
+    if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
+      return (strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') == 0);
+    }
+    if (isset($_SERVER['HTTPS'])) {
+      return (strcasecmp($_SERVER['HTTPS'], 'on') == 0);
+    }
+    return FALSE;
+  }
+
+  protected static function gatherInfos (): array
+  {
+    $protocol = 'http';
+    if (static::sslOn()) {
+      $protocol .= 's';
+    }
+
+    $port = '80';
+    if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
+      $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
+      if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) {
+        $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
+      }
+      if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
+        $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO'];
+      }
+    } else {
+      if (!empty($_SERVER['HTTP_HOST'])) {
+        $host = $_SERVER['HTTP_HOST'];
+      } else {
+        $host = $_SERVER['SERVER_NAME'];
+      }
+      $port = $_SERVER['SERVER_PORT'];
+    }
+
+    return [$protocol, $host, $port];
+  }
+
+  /*! \brief Returns SSL URL to redirect to
+   */
+  public static function getSslUrl (): string
+  {
+    list(, $host, $port) = static::gatherInfos();
+    $ssl = 'https://'.$host;
+
+    if (!empty($_SERVER['REQUEST_URI'])) {
+      $ssl .= $_SERVER['REQUEST_URI'];
+    } elseif (!empty($_SERVER['PATH_INFO'])) {
+      $ssl .= $_SERVER['PATH_INFO'];
+    } else {
+      $ssl .= $_SERVER['PHP_SELF'];
+    }
+    return $ssl;
+  }
+
+  /*! \brief Returns current page URL
+   */
+  public static function getPageURL ($queryString = FALSE): string
+  {
+    list($protocol, $host, $port) = static::gatherInfos();
+
+    $pageURL = $protocol.'://'.$host;
+    if ((($protocol == 'http') && ($port != '80')) || (($protocol == 'https') && ($port != '443'))) {
+      $pageURL .= ':'.$port;
+    }
+    if (!empty($_SERVER['REQUEST_URI']) && $queryString) {
+      $pageURL .= $_SERVER['REQUEST_URI'];
+    } elseif (!empty($_SERVER['PATH_INFO'])) {
+      $pageURL .= $_SERVER['PATH_INFO'];
+    } else {
+      $pageURL .= $_SERVER['PHP_SELF'];
+    }
+
+    return $pageURL;
+  }
+
+  /*! \brief Returns hostname to identify this website
+   */
+  public static function getHostName (): string
+  {
+    list($protocol, $host, $port) = static::gatherInfos();
+
+    if ((($protocol == 'http') && ($port != '80')) || (($protocol == 'https') && ($port != '443'))) {
+      $host .= ':'.$port;
+    }
+
+    return $host;
+  }
+
+  /*! \brief Returns absolute URL from relative URL
+   */
+  public static function buildAbsoluteUrl (string $path): string
+  {
+    return preg_replace('|/[^/]*$|', $path, static::getPageURL(FALSE));
+  }
+}
diff --git a/include/class_passwordRecovery.inc b/include/class_passwordRecovery.inc
index 385d3fec75bf626e35e2446d476ccb91081c90bb..887b9d73a731ecb64282cda68b5ae2192620aea4 100644
--- a/include/class_passwordRecovery.inc
+++ b/include/class_passwordRecovery.inc
@@ -336,7 +336,7 @@ class passwordRecovery extends standAlonePage
       return;
     }
 
-    $reinit_link = $this->getPageURL();
+    $reinit_link = URL::getPageURL();
     $reinit_link .= '?uniq='.urlencode($token);
     $reinit_link .= '&login='.urlencode($this->login);
     $reinit_link .= '&email_address='.urlencode($this->email_address);
diff --git a/include/class_standAlonePage.inc b/include/class_standAlonePage.inc
index 3aa4551c3eab2a33835453ab76cd7468bf66f649..a1b597437f1696a4105411b737ac266c8f257ffa 100644
--- a/include/class_standAlonePage.inc
+++ b/include/class_standAlonePage.inc
@@ -198,8 +198,8 @@ class standAlonePage
     /* Check for SSL connection */
     $ssl = '';
     $smarty->assign('ssl', '');
-    if (!sslOn()) {
-      $ssl = sslUrl();
+    if (!URL::sslOn()) {
+      $ssl = URL::getSslUrl();
 
       /* If SSL is forced, just forward to the SSL enabled site */
       if ($config->get_cfg_value('forcessl') == 'TRUE') {
@@ -214,40 +214,6 @@ class standAlonePage
     return $ssl;
   }
 
-  function getPageURL ()
-  {
-    $protocol = 'http';
-    if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) {
-      $protocol .= 's';
-    }
-    $port = '80';
-    if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
-      $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
-      if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) {
-        $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
-      }
-      if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
-        $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO'];
-      }
-    } else {
-      $host = $_SERVER['SERVER_NAME'];
-      $port = $_SERVER['SERVER_PORT'];
-    }
-
-    $pageURL = $protocol.'://';
-    $pageURL .= $host;
-    if ((($protocol == 'http') && ($port != '80')) || (($protocol == 'https') && ($port != '443'))) {
-      $pageURL .= ':'.$port;
-    }
-    if (empty($_SERVER['PATH_INFO'])) {
-      $pageURL .= $_SERVER['PHP_SELF'];
-    } else {
-      $pageURL .= $_SERVER['PATH_INFO'];
-    }
-
-    return $pageURL;
-  }
-
   function encodeParams ($keys)
   {
     $params = '';
diff --git a/include/php_setup.inc b/include/php_setup.inc
index 3d327e15de38a80247a55621dae292575217d2ff..296fd7c12e33f1ab275497cf3c56aad4d1a78523 100644
--- a/include/php_setup.inc
+++ b/include/php_setup.inc
@@ -275,37 +275,6 @@ function dummy_error_handler ()
 {
 }
 
-/*! \brief Returns TRUE if SSL was used to contact FD, whether directly or through a proxy
- */
-function sslOn ()
-{
-  if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
-    return (strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') == 0);
-  }
-  if (isset($_SERVER['HTTPS'])) {
-    return (strcasecmp($_SERVER['HTTPS'], 'on') == 0);
-  }
-  return FALSE;
-}
-
-/*! \brief Returns SSL URL to redirect to
- */
-function sslUrl ()
-{
-  $ssl = 'https://';
-  if (empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
-    $ssl .= $_SERVER['HTTP_HOST'];
-  } else {
-    $ssl .= $_SERVER['HTTP_X_FORWARDED_HOST'];
-  }
-  if (empty($_SERVER['REQUEST_URI'])) {
-    $ssl .= $_SERVER['PATH_INFO'];
-  } else {
-    $ssl .= $_SERVER['REQUEST_URI'];
-  }
-  return $ssl;
-}
-
 /* Bail out for incompatible/old PHP versions */
 if (!version_compare(phpversion(), PHP_MIN_VERSION, ">=")) {
   echo "PHP version needs to be ".PHP_MIN_VERSION." or above to run FusionDirectory. Aborted.";
@@ -346,6 +315,6 @@ $smarty->php_handling = Smarty::PHP_REMOVE;
 
 /* Check for SSL connection */
 $ssl = '';
-if (!sslOn()) {
-  $ssl = sslUrl();
+if (!URL::sslOn()) {
+  $ssl = URL::getSslUrl();
 }