LScli autocompleter: fix handling quoted arguments

This commit is contained in:
Benjamin Renard 2020-07-06 17:53:54 +02:00
parent f277528400
commit a21b40d706
3 changed files with 102 additions and 40 deletions

View file

@ -18,5 +18,9 @@ _ldapsaisie()
COMPREPLY[i]=${COMPREPLY[i]#"$equal_word"} COMPREPLY[i]=${COMPREPLY[i]#"$equal_word"}
done done
fi 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 complete -o default -F _ldapsaisie ldapsaisie

View file

@ -387,16 +387,19 @@ class LScli extends LSlog_staticLoggerClass {
$command_arg_num = null; $command_arg_num = null;
$command_args = array(); $command_args = array();
for ($i=1; $i < count($comp_words); $i++) { 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) { if (!$command) {
$command = $comp_words[$i]; $command = $comp_words[$i];
self :: unquote_word($command);
$command_arg_num = $i; $command_arg_num = $i;
} }
else else
$command_args[] = $comp_words[$i]; $command_args[] = $comp_words[$i];
} }
else { else {
switch($comp_words[$i]) { switch($unescaped_comp_word) {
case '-S': case '-S':
case '--ldap-server': case '--ldap-server':
$i++; $i++;
@ -409,6 +412,7 @@ class LScli extends LSlog_staticLoggerClass {
break; break;
if (isset($comp_words[$i])) { if (isset($comp_words[$i])) {
$ldap_server_id = intval($comp_words[$i]); $ldap_server_id = intval($comp_words[$i]);
self :: unquote_word($ldap_server_id);
if(!LSsession :: setLdapServer($ldap_server_id)) if(!LSsession :: setLdapServer($ldap_server_id))
self :: usage("Fail to select LDAP server #$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])) if (!isset($comp_words[$i]))
break; break;
$class = $comp_words[$i]; $class = $comp_words[$i];
self :: unquote_word($class);
if(!LSsession :: loadLSclass($class)) if(!LSsession :: loadLSclass($class))
self :: usage("Fail to load class '$class'."); self :: usage("Fail to load class '$class'.");
break; break;
@ -438,6 +443,7 @@ class LScli extends LSlog_staticLoggerClass {
if (!isset($comp_words[$i])) if (!isset($comp_words[$i]))
break; break;
$addon = $comp_words[$i]; $addon = $comp_words[$i];
self :: unquote_word($addon);
if(!LSsession :: loadLSaddon($addon)) if(!LSsession :: loadLSaddon($addon))
self :: usage("Fail to load addon '$addon'."); self :: usage("Fail to load addon '$addon'.");
break; break;
@ -497,12 +503,13 @@ class LScli extends LSlog_staticLoggerClass {
**/ **/
public static function autocomplete_class_name($prefix='') { public static function autocomplete_class_name($prefix='') {
$classes = array(); $classes = array();
$quote_char = self :: unquote_word($prefix);
$regex = "/^class\.($prefix.*)\.php$/"; $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(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) { foreach (listFiles($dir_path, $regex) as $file) {
$class = $file[1]; $class = $file[1];
if (!in_array($class, $classes)) if (!in_array($class, $classes))
$classes[] = $class; $classes[] = self :: quote_word($class, $quote_char);
} }
} }
return $classes; return $classes;
@ -517,12 +524,13 @@ class LScli extends LSlog_staticLoggerClass {
**/ **/
public static function autocomplete_addon_name($prefix='') { public static function autocomplete_addon_name($prefix='') {
$addons = array(); $addons = array();
$quote_char = self :: unquote_word($prefix);
$regex = "/^LSaddons\.($prefix.*)\.php$/"; $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(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) { foreach (listFiles($dir_path, $regex) as $file) {
$addon = $file[1]; $addon = $file[1];
if (!in_array($addon, $addons)) if (!in_array($addon, $addons))
$addons[] = $addon; $addons[] = self :: quote_word($addon, $quote_char);
} }
} }
return $addons; return $addons;
@ -534,12 +542,16 @@ class LScli extends LSlog_staticLoggerClass {
* @param[in] $opts array Available options * @param[in] $opts array Available options
* @param[in] $prefix string Option name prefix (optional, default=empty string) * @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] $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 * @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) if (!is_string($prefix) || strlen($prefix)==0)
return $opts; return $opts;
if (!$quote_char)
$quote_char = self :: unquote_word($prefix);
if (!$case_sensitive) if (!$case_sensitive)
$prefix = strtolower($prefix); $prefix = strtolower($prefix);
@ -547,8 +559,9 @@ class LScli extends LSlog_staticLoggerClass {
foreach($opts as $key => $opt) { foreach($opts as $key => $opt) {
if (!$case_sensitive) if (!$case_sensitive)
$opt = strtolower($opt); $opt = strtolower($opt);
self :: unquote_word($opt);
if (substr($opt, 0, strlen($prefix)) == $prefix) 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)); self :: log_debug("autocomplete_opts(".implode('|', $opts).", $prefix, case ".($case_sensitive?"sensitive":"insensitive").") : matched opts: ".print_r($matched_opts, true));
return $matched_opts; return $matched_opts;
@ -573,10 +586,13 @@ class LScli extends LSlog_staticLoggerClass {
* Autocomplete LSobject type option * Autocomplete LSobject type option
* *
* @param[in] $prefix string Option prefix (optional, default=empty string) * @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 * @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); $types = LSconfig :: get('LSaccess', array(), null, LSsession :: $ldapServer);
$subdn_config = LSconfig :: get('subDn', null, null, LSsession :: $ldapServer); $subdn_config = LSconfig :: get('subDn', null, null, LSsession :: $ldapServer);
if (is_array($subdn_config)) { 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] $objType string LSobject type
* @param[in] $prefix string Option prefix (optional, default=empty string) * @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 * @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)) if (!LSsession ::loadLSobject($objType, false))
return array(); return array();
// Make sure to unquote prefix
if (!$quote_char && $prefix)
$quote_char = self :: unquote_word($prefix);
$rdn_attr = LSconfig :: get("LSobjects.$objType.rdn"); $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=") 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 // Split prefix by comma to keep only RDN
$prefix_parts = explode(',', $prefix); $prefix_parts = explode(',', $prefix);
@ -627,13 +650,41 @@ class LScli extends LSlog_staticLoggerClass {
if (is_array($objs)) { if (is_array($objs)) {
$dns = array_keys($objs); $dns = array_keys($objs);
self :: log_debug("Matching $objType DNs with prefix '$prefix_rdn': ".implode(', ', $dns)); 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 return self :: autocomplete_opts($dns, $prefix, $case_sensitive, $quote_char);
// full match
if ($prefix_rdn != $prefix)
return self :: autocomplete_opts($dns, $prefix);
return $dns;
} }
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;
} }
} }

View file

@ -1967,20 +1967,22 @@ class LSldapObject extends LSlog_staticLoggerClass {
if (!in_array($command_args[$i], $opts)) { if (!in_array($command_args[$i], $opts)) {
// If object type not defined // If object type not defined
if (is_null($objType)) { 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 // Defined it
$objType = $command_args[$i]; $objType = $command_args[$i];
LScli :: unquote_word($objType);
$objType_arg_num = $i; $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)) { elseif (is_null($dn)) {
$dn = $command_args[$i]; $dn = $command_args[$i];
LScli :: unquote_word($dn);
$dn_arg_num = $i; $dn_arg_num = $i;
} }
} }
@ -2183,20 +2185,22 @@ class LSldapObject extends LSlog_staticLoggerClass {
if (!in_array($command_args[$i], $opts)) { if (!in_array($command_args[$i], $opts)) {
// If object type not defined // If object type not defined
if (is_null($objType)) { 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 // Defined it
$objType = $command_args[$i]; $objType = $command_args[$i];
LScli :: unquote_word($objType);
$objType_arg_num = $i; $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)) { elseif (is_null($dn)) {
$dn = $command_args[$i]; $dn = $command_args[$i];
LScli :: unquote_word($dn);
$dn_arg_num = $i; $dn_arg_num = $i;
} }
} }
@ -2605,24 +2609,27 @@ class LSldapObject extends LSlog_staticLoggerClass {
if (!in_array($command_args[$i], $opts)) { if (!in_array($command_args[$i], $opts)) {
// If object type not defined // If object type not defined
if (is_null($objType)) { 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 // Defined it
$objType = $command_args[$i]; $objType = $command_args[$i];
LScli :: unquote_word($objType);
$objType_arg_num = $i; $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)) { elseif (is_null($dn)) {
$dn = $command_args[$i]; $dn = $command_args[$i];
LScli :: unquote_word($dn);
$dn_arg_num = $i; $dn_arg_num = $i;
} }
elseif (is_null($relation_id)) { elseif (is_null($relation_id)) {
$relation_id = $command_args[$i]; $relation_id = $command_args[$i];
LScli :: unquote_word($relation_id);
$relation_id_arg_num = $i; $relation_id_arg_num = $i;
} }
} }