dev/core#2663 - Setup - Consistently handle special characters per URL conventions
authorTim Otten <totten@civicrm.org>
Fri, 25 Jun 2021 22:12:14 +0000 (15:12 -0700)
committerTim Otten <totten@civicrm.org>
Fri, 25 Jun 2021 23:24:41 +0000 (16:24 -0700)
Overview
--------

This addresses a problem with the setup UI when installing CiviCRM with certain database credentials.
Specifically, it handles special characters more correctly.

https://lab.civicrm.org/dev/core/-/issues/2663

Before
------

If the database password requires any special characters (e.g. "#" or "&"), then they cannot correctly
entered.

There are two ways you might try to enter a password (e.g.  plain
`mysql://myuser:top#secret...`or URL-encoded `mysql://myuser:top%23secret...`). Neither
of these work. (The plain notation might pass the validator, but it won't be written correctly to disk.
The URL-encoded notation won't pass the validator.)

After
-----

You may use special characters, as long as the URL is properly encoded, e.g. `mysql://myuser:top%23secret...`

setup/plugins/blocks/advanced.tpl.php
setup/plugins/installFiles/InstallSettingsFile.civi-setup.php
setup/src/Setup/DbUtil.php

index 52665827de2d3e4af241825552df2a4a2009705b..12531bed4b810b0a1a347b87f3a2965b6ecd61b9 100644 (file)
@@ -12,14 +12,14 @@ endif; ?>
     <tr>
       <th><?php echo ts('CMS Database'); ?></th>
       <td>
-        <code><?php echo htmlentities('mysql://' . $model->cmsDb['username'] . ':HIDDEN@' . $model->cmsDb['server'] . '/' . $model->cmsDb['database']); ?></code>
+        <code><?php echo htmlentities(\Civi\Setup\DbUtil::encodeDsn(array_merge($model->cmsDb, ['password' => 'HIDDEN']))); ?></code>
       </td>
     </tr>
     <tr>
       <th><?php echo ts('CiviCRM Database'); ?></th>
       <td class="advanced-db">
         <div class="ro">
-          <code><?php echo htmlentities('mysql://' . $model->db['username'] . ':HIDDEN@' . $model->db['server'] . '/' . $model->db['database']); ?></code>
+          <code><?php echo htmlentities(\Civi\Setup\DbUtil::encodeDsn(array_merge($model->db, ['password' => 'HIDDEN']))); ?></code>
           <a href="" onclick="csj$('.advanced-db .ro').hide(); csj$('.advanced-db .rw').show(); return false;" title="<?php echo htmlentities(ts('Edit')) ?>"><i class="fa fa-pencil"></i></a>
         </div>
         <div class="rw" style="display: none;">
@@ -40,6 +40,7 @@ endif; ?>
           <p><?php echo ts('<strong>Example</strong>: <code>%1</code>', array(1 => 'mysql://admin:secret@localhost/civicrm')); ?></p>
           <p><?php echo ts('<strong>Example</strong>: <code>%1</code>', array(1 => 'mysql://admin:secret@127.0.0.1:3306/otherdb')); ?></p>
           <p><?php echo ts('<strong>Example</strong>: <code>%1</code>', array(1 => 'mysql://admin:secret@unix(/var/lib/mysql/mysql.sock)/otherdb')); ?></p>
+          <p><?php echo ts('Tip: This uses URL notation. If the credentials require any special characters (e.g. "&" or "#"), then apply URL encoding (e.g. "%26" or "%23").'); ?></p>
         </div>
       </td>
     </tr>
index 447889a873210f837e73b0df7d5292a7abe0371f..8140f5d48db7000c657368170799555c696c90d0 100644 (file)
@@ -74,10 +74,10 @@ if (!defined('CIVI_SETUP')) {
     // ??why is frontEnd=0??
     $params['frontEnd'] = 0;
     $params['baseURL'] = addslashes(rtrim($m->cmsBaseUrl, '/'));
-    $params['dbUser'] = addslashes($m->db['username']);
-    $params['dbPass'] = addslashes($m->db['password']);
-    $params['dbHost'] = addslashes($m->db['server']);
-    $params['dbName'] = addslashes($m->db['database']);
+    $params['dbUser'] = addslashes(urlencode($m->db['username']));
+    $params['dbPass'] = addslashes(urlencode($m->db['password']));
+    $params['dbHost'] = addslashes(implode(':', array_map('urlencode', explode(':', $m->db['server']))));
+    $params['dbName'] = addslashes(urlencode($m->db['database']));
     // The '&' prefix is awkward, but we don't know what's already in the file.
     // At the time of writing, it has ?new_link=true. If that is removed,
     // then need to update this.
@@ -86,10 +86,10 @@ if (!defined('CIVI_SETUP')) {
     // need to use %20 for spaces.
     $params['dbSSL'] = empty($m->db['ssl_params']) ? '' : addslashes('&' . http_build_query($m->db['ssl_params'], '', '&', PHP_QUERY_RFC3986));
     $params['cms'] = addslashes($m->cms);
-    $params['CMSdbUser'] = addslashes($m->cmsDb['username']);
-    $params['CMSdbPass'] = addslashes($m->cmsDb['password']);
-    $params['CMSdbHost'] = addslashes($m->cmsDb['server']);
-    $params['CMSdbName'] = addslashes($m->cmsDb['database']);
+    $params['CMSdbUser'] = addslashes(urlencode($m->cmsDb['username']));
+    $params['CMSdbPass'] = addslashes(urlencode($m->cmsDb['password']));
+    $params['CMSdbHost'] = addslashes(implode(':', array_map('urlencode', explode(':', $m->cmsDb['server']))));
+    $params['CMSdbName'] = addslashes(urlencode($m->cmsDb['database']));
     // The '&' prefix is awkward, but we don't know what's already in the file.
     // At the time of writing, it has ?new_link=true. If that is removed,
     // then need to update this.
index 6c23c251658c7ade05f8ae4cbbc96a8cf2ea2cf6..438fb708c96ace2c5e2cbc0b2e8bd0367cf1f097 100644 (file)
@@ -10,7 +10,7 @@ class DbUtil {
    * @return array
    */
   public static function parseDsn($dsn) {
-    $parsed = parse_url($dsn);
+    $parsed = array_map('urldecode', parse_url($dsn));
     // parse_url parses 'mysql://admin:secret@unix(/var/lib/mysql/mysql.sock)/otherdb' like:
     // [
     //   'host'   => 'unix(',
@@ -37,18 +37,20 @@ class DbUtil {
   }
 
   /**
-   * @todo Is this used anywhere? It doesn't support SSL as-is.
-   * Convert an datasource from array notation to URL notation.
+   * Convert a datasource from array notation to URL notation.
+   *
+   * FIXME: Doesn't support SSL
    *
    * @param array $db
    * @return string
    */
   public static function encodeDsn($db) {
+    $escapedHostPort = implode(':', array_map('urlencode', explode(':', $db['server'])));
     return sprintf('mysql://%s:%s@%s/%s',
-      $db['username'],
-      $db['password'],
-      $db['server'],
-      $db['database']
+      urlencode($db['username']),
+      urlencode($db['password']),
+      $escapedHostPort,
+      urlencode($db['database'])
     );
   }