2008-11-13 22 views
5

Estoy escribiendo un programa simple para navegar por la red local y pasar los nombres de archivo a mplayer usando "sistema". Sin embargo, a veces los nombres de archivo contienen espacios o citas. Obviamente, podría escribir mi propia función para escapar de ellos, pero no estoy seguro de qué personajes necesitan escapar.Cómo escapar de forma segura una cadena de C++

¿Hay una función disponible en el CRT o en alguna parte de los encabezados de linux para escapar con seguridad de una cadena para pasarla a la línea de comando?

Respuesta

7

No existe una única solución que funcione en todas partes, ya que diferentes shells tienen diferentes ideas sobre qué caracteres especiales son y cómo se interpretan. Para bash, probablemente podría salirse con rodear todo el nombre de archivo en comillas simples después de reemplazar cada cita en el nombre de archivo con '"'"' (la primera comilla simple detiene la secuencia, el "'" agrega la comilla simple literal a la cadena, el último sencillo cita comienza la secuencia citada de nuevo). Una mejor solución sería encontrar una forma de llamar al programa sin usar el sistema, como por ejemplo usar fork con una de las funciones de ejecución para que no haya interpolación de shell.

+0

tienes razón. no es solo '' sino '' '' '': p –

+0

Es posible crear una solución segura para un shell específico, pero respondí la pregunta, reconocí que no era la mejor manera de hacerlo, y proporcioné una caja fuerte alternativa. No creo que esto merezca un voto negativo. –

2

Aunque no conozco una función que haga esto, puede rodear cada uno de sus argumentos con '...', y reemplazar ' en el argumento original por '"'"'. como system("mplayer 'foo'\"'\"' bar'"); dará un argumento único a mplayer que es foo 'bar y que puede contener cosas extrañas como " o \n. Tenga en cuenta que el escapado antes de " anterior (\") es solo para que sea válido C++.

Debería considerar el uso de una función que acepte los argumentos por separado, evitando así dichos problemas. Wikipedia tiene un buen artículo sobre esto sobre el famoso patrón fork-and-exec. http://en.wikipedia.org/wiki/Fork-exec

8

Otras respuestas incluyen esta solución tenedor y ejecutiva, pero afirmo que esta es la única manera correcta de hacerlo.

Los argumentos de escape de shell son propensos a errores y una pérdida de tiempo, al igual que tratar de escapar de los parámetros SQL es una idea tonta cuando existen API de enlace de parámetros más seguras y eficientes.

Aquí es una función de ejemplo:

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

tienes razón, es la única manera correcta. pero, sin embargo, la pregunta era cómo se puede escapar en C++ para el shell. así que primero respondimos y luego mostramos cómo hacerlo bien. –

+0

Como mencioné, fork/exec es la mejor manera de hacerlo, pero * es * posible manejar esto con seguridad para un shell dado que incluí en mi respuesta ya que esa era la pregunta. Tuve que hacer esto donde fork/exec no era una opción, así que no siempre es una pérdida de tiempo. –

+0

Todos ya habían cubierto las citas, así que decidí dar detalles sobre fork/exec y expresar mi opinión al mismo tiempo. Sin ofender a nadie. –

0

Y ahora aquí es una solución completa al problema cáscara de escape. Aunque este no responde la pregunta exacta de escapar de una cadena para shell. Resuelve el problema de pasar argumentos a un programa. Esta solución es una forma portátil de POSIX para ejecutar comandos con argumentos pasados ​​correctamente al comando sin preocuparse por la necesidad de escapar de ellos.

#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"); 

} 
Cuestiones relacionadas