2008-11-13 4 views
5

J'écris un programme simple pour parcourir le réseau local et transmettre les noms de fichiers à mplayer en utilisant "système". Cependant, parfois les noms de fichiers contiennent des espaces ou des guillemets. Évidemment, je pourrais écrire ma propre fonction pour échapper à ceux-ci, mais je ne suis pas sûr exactement quels caractères ont ou n'ont pas besoin d'échapper.Comment faire pour échapper en toute sécurité une chaîne de C++

Y at-il une fonction disponible dans le CRT ou quelque part dans les en-têtes linux pour échapper en toute sécurité une chaîne pour passer à la ligne de commande?

Répondre

7

Il n'y a pas une seule solution qui fonctionne partout parce que les différentes coquilles ont des idées différentes de ce que sont les caractères spéciaux et comment ils sont interprétés. Pour bash, vous pourriez probablement sortir avec entourer le nom de fichier entier entre guillemets après avoir remplacé chaque citation dans le nom de fichier par '"'"' (la première citation unique arrête la séquence, le "'" ajoute la citation unique littérale à la chaîne, le dernier single citation recommence la séquence citée). Une meilleure solution serait de trouver un moyen d'appeler le programme sans utiliser le système, comme en utilisant fork avec l'une des fonctions exec, donc il n'y a pas d'interpolation de shell.

+0

vous avez raison. ce n'est pas seulement '' mais '' '' '': p –

+0

Il est possible de créer une solution sûre pour un shell spécifique mais j'ai répondu à la question posée, j'ai reconnu que ce n'était pas la meilleure façon d'y arriver et j'ai fourni un alternative. Je ne pense pas que cela mérite un vote négatif. –

2

Alors que je ne connais pas une fonction qui fait cela, vous pouvez entourer chacun de vos arguments avec '...', et remplacer ' dans l'argument original par '"'"'. comme system("mplayer 'foo'\"'\"' bar'"); donnera un seul argument à mplayer qui est foo 'bar et qui est autorisé à contenir des choses étranges comme " ou \n. Notez l'échappement avant " ci-dessus (\") est seulement pour le rendre valide C++.

Vous devriez envisager d'utiliser une fonction qui accepte les arguments séparément, évitant ainsi de tels problèmes. Wikipédia a un bon article à ce sujet sur le fameux patron fork-and-exec. http://en.wikipedia.org/wiki/Fork-exec

8

D'autres réponses incluent cette solution fork et exec, mais je prétends que c'est la seule bonne façon de le faire.

L'échappement des arguments shell est sujette à des bogues et une perte de temps, tout comme essayer d'échapper des paramètres SQL est une idée idiote quand des API de liaison de paramètres plus sûrs et plus efficaces existent.

Voici une fonction exemple:

void play(const char *path) 
{ 
    /* Fork, then exec */ 
    pid = fork(); 

    if(pid < 0) { 
     /* This is an error! */ 
     return; 
    } 

    if(pid == 0) { 
     /* This is the child */ 
     freopen("/dev/null", "r", stdin); 
     freopen("/dev/null", "w", stdout); 
     freopen("/dev/null", "w", stderr); 

     execlp("mplayer", "mplayer", path, (char *)0); 
     /* This is also an error! */ 
     return; 
    } 
} 
+0

vous avez en effet raison, c'est la seule bonne façon. mais néanmoins, la question était de savoir comment vous échapper en C++ pour le shell. Nous avons donc répondu en premier, puis nous avons montré comment le faire correctement. –

+0

Comme je l'ai mentionné, fork/exec est la meilleure façon de le faire mais il est * possible * de gérer ceci en toute sécurité pour un shell donné que j'ai inclus dans ma réponse puisque c'était la question posée. J'ai dû faire ceci où fork/exec n'était pas une option donc ce n'est pas toujours une perte de temps. –

+0

Tout le monde avait déjà couvert la citation, j'ai donc décidé de donner des détails sur fork/exec et exprimer mon opinion en même temps. Aucune offense destinée à personne. –

0

Et maintenant est ici une solution complète au problème d'échappement du shell. Bien que ce ne répond pas à la question exacte d'échapper une chaîne pour shell. Il résout le problème de passer des arguments au programme. Cette solution est un moyen POSIX portable d'exécuter des commandes avec des arguments correctement passés dans la commande sans se soucier de devoir les échapper.

#include <cstdio> 
#include <cstdlib> 
#include <iostream> 
#include <sstream> 
#include <string> 
#include <sys/stat.h> 
#include <vector> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <string.h> 

std::vector<std::string> split(std::string delimiter, std::string str){ 
    std::size_t nextPos = 0; 
    std::size_t delimiterSize = delimiter.size(); 
    std::vector<std::string> list; 
    while(true){ 
     std::size_t pos = str.find(delimiter, nextPos); 
     std::string subStr; 

     if(pos == std::string::npos){ 
      list.push_back(str.substr(nextPos)); 
      break; 
     } 
     subStr = str.substr(nextPos, pos - nextPos); 
     list.push_back(subStr); 

     nextPos = pos + delimiterSize; 
    } 
    return list; 
} 


bool isFileExecutable(const std::string &file) 
{ 
    struct stat st; 

    if (stat(file.c_str(), &st) < 0) 
     return false; 
    if ((st.st_mode & S_IEXEC) != 0) 
     return true; 
    return false; 
} 

std::string ensureEndsWithSlash(std::string path){ 
    if(path[path.length()-1] != '/'){ 
     path += "/"; 
    } 
    return path; 
} 
std::string findProgram(std::string name){ 
    // check if it's relative 
    if(name.size() > 2){ 
     if(name[0] == '.' && name[1] == '/'){ 
      if(isFileExecutable(name)){ 
       return name; 
      } 
      return std::string(); 
     } 
    } 
    std::vector<std::string> pathEnv = split(":", getenv("PATH")); 
    for(std::string path : pathEnv){ 
     path = ensureEndsWithSlash(path); 
     path += name; 
     if(isFileExecutable(path)){ 
      return path; 
     } 
    } 
    return std::string(); 
} 

// terminal condition 
void toVector(std::vector<std::string> &vector, const std::string &str){ 
    vector.push_back(str); 
} 
template<typename ...Args> 
void toVector(std::vector<std::string> &vector, const std::string &str, Args ...args){ 
    vector.push_back(str); 
    toVector(vector, args...); 
} 

int waitForProcess(pid_t processId){ 
    if(processId == 0){ 
     return 0; 
    } 
    int status = 0; 
    int exitCode = -1; 
    while(waitpid(processId, &status, 0) != processId){ 
     // wait for it 
    } 
    if (WIFEXITED(status)) { 
     exitCode = WEXITSTATUS(status); 
    } 
    return exitCode; 
} 

/** 
    Runs the process and returns the exit code. 

    You should change it so you can detect process failure 
    vs this function actually failing as a process can return -1 too 

    @return -1 on failure, or exit code of process. 
*/ 
template<typename ...Args> 
int mySystem(Args ...args){ 
    std::vector<std::string> command; 
    toVector(command, args...); 
    command[0] = findProgram(command[0]); 
    if(command[0].empty()){ 
     // handle this case by returning error or something 
     // maybe std::abort() with error message 
     return -1; 
    } 
    pid_t pid = fork(); 
    if(pid) { 
     // parent wait for child 
     return waitForProcess(pid); 
    } 

    // we are child make a C friendly array 
    // this process will be replaced so we don't care about memory 
    // leaks at this point. 
    std::vector<char*> c_command; 
    for(int i = 0; i < command.size(); ++i){ 
     c_command.push_back(strdup(command[i].c_str())); 
    } 
    // null terminate the sequence 
    c_command.push_back(nullptr); 
    execvp(c_command[0], &c_command[0]); 
    // just incase 
    std::abort(); 
    return 0; 
} 



int main(int argc, char**argv){ 

    // example usage 
    mySystem("echo", "hello", "world"); 

} 
Questions connexes