Index: branches/1.1dev/lib/App.inc.php
===================================================================
--- branches/1.1dev/lib/App.inc.php	(revision 755)
+++ branches/1.1dev/lib/App.inc.php	(revision 756)
@@ -413,5 +413,5 @@
  *                                   -false  <-- To not carry any queries. If URL already has queries those will be retained.
  */
-function printHiddenSession($carry_args=null)
+function printHiddenSession($carry_args=null, $include_csrf_token=false)
 {
     static $_using_trans_sid;
@@ -474,4 +474,10 @@
     if (!isset($_COOKIE[session_name()]) && !$_using_trans_sid) {
         echo '<input type="hidden" name="' . session_name() . '" value="' . session_id() . '" />';
+    }
+
+    // Include the csrf_token in the form.
+    // This token can be validated upon form submission with $app->verifyCSRFToken() or $app->requireValidCSRFToken()
+    if ($include_csrf_token) {
+        printf('<input type="hidden" name="csrf_token" value="%s" />', getCSRFToken());
     }
 }
@@ -620,36 +626,108 @@
 }
 
-/**
- * Force the user to connect via https (port 443) by redirecting them to
- * the same page but with https.
+/*
+* Generate a csrf_token if it doesn't exist or is expired, save it to the session and return its value.
+* Otherwise just return the current token.
+* Details on the synchronizer token pattern:
+* https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern
+*
+* @access   public
+* @param    bool    $force_new_token    Generate a new token, replacing any existing token in the session (used by $app->resetCSRFToken())
+* @return   string The new or current csrf_token
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @version  1.0
+* @since    15 Nov 2014 17:57:17
+*/
+function getCSRFToken($force_new_token=false)
+{
+    if ($force_new_token || !isset($_SESSION['csrf_token']) || (removeSignature($_SESSION['csrf_token']) + 86400 < time())) {
+        // No token, or token is expired; generate one and return it.
+        return $_SESSION['csrf_token'] = addSignature(time(), null, 64);
+    }
+    // Current token is not expired; return it.
+    return $_SESSION['csrf_token'];
+}
+
+/*
+* Generate a new token, replacing any existing token in the session. Call this function after $app->requireValidCSRFToken() for a new token to be required for each request.
+*
+* @access   public
+* @return   void
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @since    14 Oct 2021 17:35:19
+*/
+function resetCSRFToken()
+{
+    getCSRFToken(true);
+}
+
+/*
+* Compares the given csrf_token with the current or previous one saved in the session.
+*
+* @access   public
+* @param    string  $user_submitted_csrf_token The user-submitted token to compare with the session token.
+* @return   bool    True if the tokens match, false otherwise.
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @version  1.0
+* @since    15 Nov 2014 18:06:55
+*/
+function verifyCSRFToken($user_submitted_csrf_token)
+{
+
+    if ('' == trim($user_submitted_csrf_token)) {
+        logMsg(sprintf('Empty string failed CSRF verification.', null), LOG_NOTICE, __FILE__, __LINE__);
+        return false;
+    }
+    if (!verifySignature($user_submitted_csrf_token, null, 64)) {
+        logMsg(sprintf('Input failed CSRF verification (invalid signature in %s).', $user_submitted_csrf_token), LOG_WARNING, __FILE__, __LINE__);
+        return false;
+    }
+    $csrf_token = getCSRFToken();
+    if ($user_submitted_csrf_token != $csrf_token) {
+        logMsg(sprintf('Input failed CSRF verification (%s not in %s).', $user_submitted_csrf_token, $csrf_token), LOG_WARNING, __FILE__, __LINE__);
+        return false;
+    }
+    logMsg(sprintf('Verified CSRF token %s', $user_submitted_csrf_token), LOG_DEBUG, __FILE__, __LINE__);
+    return true;
+}
+
+/*
+* Bounce user if they submit a token that doesn't match the one saved in the session.
+* Because this function calls dieURL() it must be called before any other HTTP header output.
+*
+* @access   public
+* @param    string  $message    Optional message to display to the user (otherwise default message will display). Set to an empty string to display no message.
+* @param    int    $type    The type of message: MSG_NOTICE,
+*                           MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
+* @param    string $file    __FILE__.
+* @param    string $line    __LINE__.
+* @return   void
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @version  1.0
+* @since    15 Nov 2014 18:10:17
+*/
+function requireValidCSRFToken($message=null, $type=MSG_NOTICE, $file=null, $line=null)
+{
+    if (!verifyCSRFToken(getFormData('csrf_token'))) {
+        $message = isset($message) ? $message : _("Sorry, the form token expired. Please try again.");
+        raiseMsg($message, $type, $file, $line);
+        dieBoomerangURL();
+    }
+}
+
+/**
+ * This function has changed to do nothing. SSL redirection should happen at the server layer, doing so here may result in a redirect loop.
  */
 function sslOn()
 {
-    global $CFG;
-
-    if (function_exists('apache_get_modules')) {
-        $modules = apache_get_modules();
-    } else {
-        // It's safe to assume we have mod_ssl if we can't determine otherwise.
-        $modules = array('mod_ssl');
-    }
-
-    if ('on' != getenv('HTTPS') && $CFG->ssl_enabled && in_array('mod_ssl', $modules)) {
-        raiseMsg(sprintf(_("Secure SSL connection made to %s"), $CFG->ssl_domain), MSG_NOTICE, __FILE__, __LINE__);
-        // Always append session because some browsers do not send cookie when crossing to SSL URL.
-        dieURL('https://' . $CFG->ssl_domain . getenv('REQUEST_URI'), null, true);
-    }
-}
-
-
-/**
- * to enforce the user to connect via http (port 80) by redirecting them to
- * a http version of the current url.
+    logMsg(sprintf('sslOn was called and ignored.', null), LOG_DEBUG, __FILE__, __LINE__);
+}
+
+/**
+ * This function has changed to do nothing. There is no reason to prefer a non-SSL connection, and doing so may result in a redirect loop.
  */
 function sslOff()
 {
-    if ('on' == getenv('HTTPS')) {
-        dieURL('http://' . getenv('HTTP_HOST') . getenv('REQUEST_URI'), null, true);
-    }
+    logMsg(sprintf('sslOff was called and ignored.', null), LOG_DEBUG, __FILE__, __LINE__);
 }
 
Index: branches/1.1dev/lib/Utilities.inc.php
===================================================================
--- branches/1.1dev/lib/Utilities.inc.php	(revision 755)
+++ branches/1.1dev/lib/Utilities.inc.php	(revision 756)
@@ -739,35 +739,32 @@
 function hash64($string, $length=18)
 {
-    $app =& App::getInstance();
-
-    return mb_substr(preg_replace('/[^\w]/' . $app->getParam('preg_u'), '', base64_encode(hash('sha512', $string, true))), 0, $length);
+    return mb_substr(preg_replace('/[^\w]/', '', base64_encode(hash('sha512', $string, true))), 0, $length);
 }
 
 /**
  * Signs a value using md5 and a simple text key. In order for this
- * function to be useful (i.e. secure) the key must be kept secret, which
+ * function to be useful (i.e. secure) the salt must be kept secret, which
  * means keeping it as safe as database credentials. Putting it into an
  * environment variable set in httpd.conf is a good place.
  *
  * @access  public
- *
  * @param   string  $val    The string to sign.
- * @param   string  $key    (Optional) A text key to use for computing the signature.
- *
+ * @param   string  $salt   (Optional) A text key to use for computing the signature.
+ * @param   string  $length (Optional) The length of the added signature. Longer signatures are safer. Must match the length passed to verifySignature() for the signatures to match.
  * @return  string  The original value with a signature appended.
  */
-function addSignature($val, $key=null)
-{
-    global $CFG;
-
-    if ('' == $val) {
-        logMsg(sprintf('Adding signature to empty string.', null), LOG_NOTICE, __FILE__, __LINE__);
-    }
-
-    if (!isset($key)) {
-    	$key = $CFG->signing_key;
-    }
-
-    return $val . '-' . substr(md5($val . $key), 0, 18);
+function addSignature($val, $salt=null, $length=18)
+{
+    if ('' == trim($val)) {
+        logMsg(sprintf('Cannot add signature to an empty string.', null), LOG_INFO, __FILE__, __LINE__);
+        return '';
+    }
+
+    if (!isset($salt)) {
+        global $CFG;
+    	$salt = $CFG->signing_key;
+    }
+
+    return $val . '-' . mb_substr(preg_replace('/[^\w]/', '', base64_encode(hash('sha512', $val . $salt, true))), 0, $length);
 }
 
@@ -776,33 +773,34 @@
  *
  * @access  public
- *
  * @param   string  $signed_val     The string to sign.
- *
  * @return  string  The original value with a signature removed.
  */
 function removeSignature($signed_val)
 {
-    return substr($signed_val, 0, strrpos($signed_val, '-'));
-}
-
-/**
- * Verifies a signature appened to a value by addSignature().
+    if (empty($signed_val) || mb_strpos($signed_val, '-') === false) {
+        return '';
+    }
+    return mb_substr($signed_val, 0, mb_strrpos($signed_val, '-'));
+}
+
+/**
+ * Verifies a signature appended to a value by addSignature().
  *
  * @access  public
- *
  * @param   string  $signed_val A value with appended signature.
- * @param   string  $key        (Optional) A text key to use for computing the signature.
- *
+ * @param   string  $salt       (Optional) A text key to use for computing the signature.
+ * @param   string  $length (Optional) The length of the added signature.
  * @return  bool    True if the signature matches the var.
  */
-function verifySignature($signed_val, $key=null)
+function verifySignature($signed_val, $salt=null, $length=18)
 {
     // Strip the value from the signed value.
-    $val = substr($signed_val, 0, strrpos($signed_val, '-'));
+    $val = removeSignature($signed_val);
     // If the signed value matches the original signed value we consider the value safe.
-    if ($signed_val == addSignature($val, $key)) {
+    if ('' != $signed_val && $signed_val == addSignature($val, $salt, $length)) {
         // Signature verified.
-    	return true;
+        return true;
     } else {
+        logMsg(sprintf('Failed signature (%s should be %s)', $signed_val, addSignature($val, $salt, $length)), LOG_DEBUG, __FILE__, __LINE__);
         return false;
     }
