如何安全地从C ++中转义字符串

时间:2008-11-13 19:54:38

标签: c++ c linux shell

我正在编写一个简单的程序来浏览本地网络,并使用“system”将文件名传递给mplayer。但是,有时文件名包含空格或引号。 很显然,我可以编写自己的函数来逃避这些,但我不确定字符是什么或不需要转义。

CRT或linux头文件中是否有一个函数可以安全地转义字符串以传递给命令行?

4 个答案:

答案 0 :(得分:8)

其他答案包括这个fork和exec解决方案,但我声称这是唯一正确的方法。

转义shell参数容易出错并浪费时间,就像在存在更安全和更有效的参数绑定API时尝试转义SQL参数一样愚蠢。

以下是一个示例函数:

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;
    }
}

答案 1 :(得分:7)

没有一种解决方案可以在任何地方使用,因为不同的shell对特殊字符是什么以及如何解释它们有不同的想法。对于bash,在用'"'"'替换文件名中的每个引号后,你可能会用单引号包围整个文件名(第一个单引号停止序列,"'"附加文字单引用字符串,最后的单引号再次引用引用的序列)。一个更好的解决方案是找到一种不使用系统调用程序的方法,例如使用fork和一个exec函数,这样就没有shell插值。

答案 2 :(得分:3)

虽然我不知道执行此操作的函数,但您可以使用'...'包围每个参数,并用'替换原始参数中的任何'"'"'。喜欢 system("mplayer 'foo'\"'\"' bar'");将为mplayer提供一个foo'bar的参数,并且允许包含"\n之类的奇怪内容。请注意,上面"之前的转义(\")只是为了让它成为有效的C ++。

您应该考虑使用一个接受每个参数的函数,从而避免这些问题。维基百科有一篇关于着名的fork-and-exec模式的文章。 http://en.wikipedia.org/wiki/Fork-exec

答案 3 :(得分:0)

现在这里是shell转义问题的完整解决方案。虽然这个 没有回答转义shell的字符串的确切问题。它解决了将参数传递给程序的问题。这个解决方案是一种POSIX可移植方式,用于执行带有正确传递给命令的参数的命令,而不必担心需要将它们转义。

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

}