From a21b40d7068632614314902ff0192b0f9af5794d Mon Sep 17 00:00:00 2001 From: Benjamin Renard Date: Mon, 6 Jul 2020 17:53:54 +0200 Subject: [PATCH] LScli autocompleter: fix handling quoted arguments --- debian/ldapsaisie.bash-completion | 4 ++ src/includes/class/class.LScli.php | 83 ++++++++++++++++++----- src/includes/class/class.LSldapObject.php | 55 ++++++++------- 3 files changed, 102 insertions(+), 40 deletions(-) diff --git a/debian/ldapsaisie.bash-completion b/debian/ldapsaisie.bash-completion index e60a4ce8..f350ee7c 100644 --- a/debian/ldapsaisie.bash-completion +++ b/debian/ldapsaisie.bash-completion @@ -18,5 +18,9 @@ _ldapsaisie() COMPREPLY[i]=${COMPREPLY[i]#"$equal_word"} done fi + # If only one answer and it ending by "=", do not add space + if [[ ${#COMPREPLY[@]} -eq 1 ]] && [[ ${COMPREPLY[0]} == *= || ${COMPREPLY[0]} == *=\' || ${COMPREPLY[0]} == *=\" ]]; then + compopt -o nospace + fi } complete -o default -F _ldapsaisie ldapsaisie diff --git a/src/includes/class/class.LScli.php b/src/includes/class/class.LScli.php index 33259a24..65a847c2 100644 --- a/src/includes/class/class.LScli.php +++ b/src/includes/class/class.LScli.php @@ -387,16 +387,19 @@ class LScli extends LSlog_staticLoggerClass { $command_arg_num = null; $command_args = array(); for ($i=1; $i < count($comp_words); $i++) { - if (array_key_exists($comp_words[$i], self :: $commands)) { + $unescaped_comp_word = $comp_words[$i]; + self :: unquote_word($unescaped_comp_word); + if (array_key_exists($unescaped_comp_word, self :: $commands)) { if (!$command) { $command = $comp_words[$i]; + self :: unquote_word($command); $command_arg_num = $i; } else $command_args[] = $comp_words[$i]; } else { - switch($comp_words[$i]) { + switch($unescaped_comp_word) { case '-S': case '--ldap-server': $i++; @@ -409,6 +412,7 @@ class LScli extends LSlog_staticLoggerClass { break; if (isset($comp_words[$i])) { $ldap_server_id = intval($comp_words[$i]); + self :: unquote_word($ldap_server_id); if(!LSsession :: setLdapServer($ldap_server_id)) self :: usage("Fail to select LDAP server #$ldap_server_id."); } @@ -424,6 +428,7 @@ class LScli extends LSlog_staticLoggerClass { if (!isset($comp_words[$i])) break; $class = $comp_words[$i]; + self :: unquote_word($class); if(!LSsession :: loadLSclass($class)) self :: usage("Fail to load class '$class'."); break; @@ -438,6 +443,7 @@ class LScli extends LSlog_staticLoggerClass { if (!isset($comp_words[$i])) break; $addon = $comp_words[$i]; + self :: unquote_word($addon); if(!LSsession :: loadLSaddon($addon)) self :: usage("Fail to load addon '$addon'."); break; @@ -497,12 +503,13 @@ class LScli extends LSlog_staticLoggerClass { **/ public static function autocomplete_class_name($prefix='') { $classes = array(); + $quote_char = self :: unquote_word($prefix); $regex = "/^class\.($prefix.*)\.php$/"; foreach(array(LS_ROOT_DIR."/".LS_CLASS_DIR, LS_ROOT_DIR."/".LS_LOCAL_DIR."/".LS_CLASS_DIR) as $dir_path) { foreach (listFiles($dir_path, $regex) as $file) { $class = $file[1]; if (!in_array($class, $classes)) - $classes[] = $class; + $classes[] = self :: quote_word($class, $quote_char); } } return $classes; @@ -517,12 +524,13 @@ class LScli extends LSlog_staticLoggerClass { **/ public static function autocomplete_addon_name($prefix='') { $addons = array(); + $quote_char = self :: unquote_word($prefix); $regex = "/^LSaddons\.($prefix.*)\.php$/"; foreach(array(LS_ROOT_DIR."/".LS_ADDONS_DIR, LS_ROOT_DIR."/".LS_LOCAL_DIR."/".LS_ADDONS_DIR) as $dir_path) { foreach (listFiles($dir_path, $regex) as $file) { $addon = $file[1]; if (!in_array($addon, $addons)) - $addons[] = $addon; + $addons[] = self :: quote_word($addon, $quote_char); } } return $addons; @@ -534,12 +542,16 @@ class LScli extends LSlog_staticLoggerClass { * @param[in] $opts array Available options * @param[in] $prefix string Option name prefix (optional, default=empty string) * @param[in] $case_sensitive boolean Set to false if options are case insensitive (optional, default=true) + * @param[in] $quote_char boolean Quote character (optional, if not set, $prefix will be unquoted and its + * quote char (if detected) will be used to quote options) * * @retval array List of matched options **/ - public static function autocomplete_opts($opts, $prefix='', $case_sensitive=true) { + public static function autocomplete_opts($opts, $prefix='', $case_sensitive=true, $quote_char='') { if (!is_string($prefix) || strlen($prefix)==0) return $opts; + if (!$quote_char) + $quote_char = self :: unquote_word($prefix); if (!$case_sensitive) $prefix = strtolower($prefix); @@ -547,8 +559,9 @@ class LScli extends LSlog_staticLoggerClass { foreach($opts as $key => $opt) { if (!$case_sensitive) $opt = strtolower($opt); + self :: unquote_word($opt); if (substr($opt, 0, strlen($prefix)) == $prefix) - $matched_opts[] = $opts[$key]; + $matched_opts[] = LScli :: quote_word($opt, $quote_char); } self :: log_debug("autocomplete_opts(".implode('|', $opts).", $prefix, case ".($case_sensitive?"sensitive":"insensitive").") : matched opts: ".print_r($matched_opts, true)); return $matched_opts; @@ -573,10 +586,13 @@ class LScli extends LSlog_staticLoggerClass { * Autocomplete LSobject type option * * @param[in] $prefix string Option prefix (optional, default=empty string) + * @param[in] $case_sensitive boolean Set to false if options are case insensitive (optional, default=true) + * @param[in] $quote_char boolean Quote character (optional, if not set, $prefix will be unquoted and its + * quote char (if detected) will be used to quote options) * * @retval array List of available options **/ - public static function autocomplete_LSobject_types($prefix='') { + public static function autocomplete_LSobject_types($prefix='', $case_sensitive=true, $quote_char='') { $types = LSconfig :: get('LSaccess', array(), null, LSsession :: $ldapServer); $subdn_config = LSconfig :: get('subDn', null, null, LSsession :: $ldapServer); if (is_array($subdn_config)) { @@ -597,7 +613,7 @@ class LScli extends LSlog_staticLoggerClass { } } } - return self :: autocomplete_opts($types, $prefix, false); + return self :: autocomplete_opts($types, $prefix, $case_sensitive, $quote_char); } /** @@ -605,16 +621,23 @@ class LScli extends LSlog_staticLoggerClass { * * @param[in] $objType string LSobject type * @param[in] $prefix string Option prefix (optional, default=empty string) + * @param[in] $case_sensitive boolean Set to false if options are case insensitive (optional, default=true) + * @param[in] $quote_char boolean Quote character (optional, if not set, $prefix will be unquoted and its + * quote char (if detected) will be used to quote options) * * @retval array List of available options **/ - public static function autocomplete_LSobject_dn($objType, $prefix='') { + public static function autocomplete_LSobject_dn($objType, $prefix='', $case_sensitive=true, $quote_char='') { if (!LSsession ::loadLSobject($objType, false)) return array(); + // Make sure to unquote prefix + if (!$quote_char && $prefix) + $quote_char = self :: unquote_word($prefix); + $rdn_attr = LSconfig :: get("LSobjects.$objType.rdn"); if (!$rdn_attr || strlen($prefix) < (strlen($rdn_attr)+2) || substr($prefix, 0, (strlen($rdn_attr)+1)) != "$rdn_attr=") - return array("$rdn_attr=a", "$rdn_attr=*"); + return array(LScli :: quote_word("$rdn_attr=", $quote_char)); // Split prefix by comma to keep only RDN $prefix_parts = explode(',', $prefix); @@ -627,13 +650,41 @@ class LScli extends LSlog_staticLoggerClass { if (is_array($objs)) { $dns = array_keys($objs); self :: log_debug("Matching $objType DNs with prefix '$prefix_rdn': ".implode(', ', $dns)); - // If prefix have been reduced for the search, use self :: autocomplete_opts() to keep only - // full match - if ($prefix_rdn != $prefix) - return self :: autocomplete_opts($dns, $prefix); - return $dns; + return self :: autocomplete_opts($dns, $prefix, $case_sensitive, $quote_char); } - return array("$rdn_attr=a", "$rdn_attr=*"); + return array(LScli :: quote_word("$rdn_attr=", $quote_char)); + } + + /** + * Unquote a word + * + * @param[in] &$word string Reference of the word to unquote + * + * @retval string The quote character or an empty string if word if not quoted + */ + public static function unquote_word(&$word) { + if (in_array($word[0], array('"', "'"))) { + $quote_char = $word[0]; + $word = substr($word, 1); + if ($word[strlen($word)-1] == $quote_char) + $word = substr($word, 0, -1); + return $quote_char; + } + return ''; + } + + /** + * Quote a word + * + * @param[in] $word string The word to quote + * @param[in] $quote_char string The quote character. If not defined or empty, the input word + * will be returned unmodified. + * + * @retval string The quoted word + */ + public static function quote_word($word, $quote_char) { + if (!$quote_char) return $word; + return $quote_char . str_replace($quote_char, "\\$quote_char", $word) . $quote_char; } } diff --git a/src/includes/class/class.LSldapObject.php b/src/includes/class/class.LSldapObject.php index af67b63d..24953a60 100644 --- a/src/includes/class/class.LSldapObject.php +++ b/src/includes/class/class.LSldapObject.php @@ -1967,20 +1967,22 @@ class LSldapObject extends LSlog_staticLoggerClass { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { - // Check object type exists - $objTypes = LScli :: autocomplete_LSobject_types($command_args[$i]); - - // Load it if exist and not trying to complete it - if (in_array($command_args[$i], $objTypes) && $i != $comp_word_num) { - LSsession :: loadLSobject($command_args[$i], false); - } - // Defined it $objType = $command_args[$i]; + LScli :: unquote_word($objType); $objType_arg_num = $i; + + // Check object type exists + $objTypes = LScli :: autocomplete_LSobject_types($objType); + + // Load it if exist and not trying to complete it + if (in_array($objType, $objTypes) && $i != $comp_word_num) { + LSsession :: loadLSobject($objType, false); + } } elseif (is_null($dn)) { $dn = $command_args[$i]; + LScli :: unquote_word($dn); $dn_arg_num = $i; } } @@ -2183,20 +2185,22 @@ class LSldapObject extends LSlog_staticLoggerClass { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { - // Check object type exists - $objTypes = LScli :: autocomplete_LSobject_types($command_args[$i]); - - // Load it if exist and not trying to complete it - if (in_array($command_args[$i], $objTypes) && $i != $comp_word_num) { - LSsession :: loadLSobject($command_args[$i], false); - } - // Defined it $objType = $command_args[$i]; + LScli :: unquote_word($objType); $objType_arg_num = $i; + + // Check object type exists + $objTypes = LScli :: autocomplete_LSobject_types($objType); + + // Load it if exist and not trying to complete it + if (in_array($objType, $objTypes) && $i != $comp_word_num) { + LSsession :: loadLSobject($objType, false); + } } elseif (is_null($dn)) { $dn = $command_args[$i]; + LScli :: unquote_word($dn); $dn_arg_num = $i; } } @@ -2605,24 +2609,27 @@ class LSldapObject extends LSlog_staticLoggerClass { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { - // Check object type exists - $objTypes = LScli :: autocomplete_LSobject_types($command_args[$i]); - - // Load it if exist and not trying to complete it - if (in_array($command_args[$i], $objTypes) && $i != $comp_word_num) { - LSsession :: loadLSobject($command_args[$i], false); - } - // Defined it $objType = $command_args[$i]; + LScli :: unquote_word($objType); $objType_arg_num = $i; + + // Check object type exists + $objTypes = LScli :: autocomplete_LSobject_types($objType); + + // Load it if exist and not trying to complete it + if (in_array($objType, $objTypes) && $i != $comp_word_num) { + LSsession :: loadLSobject($objType, false); + } } elseif (is_null($dn)) { $dn = $command_args[$i]; + LScli :: unquote_word($dn); $dn_arg_num = $i; } elseif (is_null($relation_id)) { $relation_id = $command_args[$i]; + LScli :: unquote_word($relation_id); $relation_id_arg_num = $i; } }