Как безопасно экранировать строку из C ++ - PullRequest
5 голосов
/ 13 ноября 2008

Я пишу простую программу для просмотра локальной сети и передачи имен файлов в mplayer с помощью «system». Однако иногда имена файлов содержат пробелы или кавычки. Очевидно, я мог бы написать свою собственную функцию, чтобы избежать их, но я не уверен, что именно символы делают или не нуждаются в экранировании.

Есть ли функция, доступная в CRT или где-нибудь в заголовочных файлах Linux, для безопасного экранирования строки для передачи в командную строку?

Ответы [ 4 ]

8 голосов
/ 13 ноября 2008

Другие ответы включают это решение fork и exec, но я утверждаю, что это единственный правильный способ сделать это.

Экранирование аргументов оболочки подвержено ошибкам и трате времени, так же как попытка избежать параметров SQL - глупая идея, когда существуют более безопасные и более эффективные API привязки параметров.

Вот пример функции:

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;
    }
}
7 голосов
/ 13 ноября 2008

Не существует единого решения, которое бы работало везде, потому что разные оболочки имеют разные представления о том, что такое специальные символы и как они интерпретируются. Для bash вы, вероятно, могли бы избежать использования целого имени файла в одинарных кавычках после замены каждой одинарной кавычки в имени файла на '"'"' (первая одинарная кавычка останавливает последовательность, "'" добавляет буквальную одинарную кавычку к строке окончательная одинарная кавычка снова начинает цитируемую последовательность). Лучшим решением было бы найти способ вызова программы без использования системы, например, используя fork с одной из функций exec, чтобы не было интерполяции оболочки.

3 голосов
/ 13 ноября 2008

Хотя я не знаю функции, которая делает это, вы можете окружить каждый из ваших аргументов '...' и заменить любой ' в исходном аргументе '"'"'. лайк system("mplayer 'foo'\"'\"' bar'"); передаст mplayer один аргумент: foo 'bar, который может содержать странные вещи, такие как " или \n. Обратите внимание, что экранирование до " выше (\") только для того, чтобы сделать его действительным C ++.

Вам следует рассмотреть возможность использования функции, которая принимает аргументы по отдельности, что позволяет избежать таких проблем. В Wikipedia есть хорошая статья на эту тему об известном шаблоне fork-and-exec. http://en.wikipedia.org/wiki/Fork-exec

0 голосов
/ 24 июля 2016

А теперь вот полное решение проблемы побега оболочки. Хотя это не отвечает на точный вопрос экранирования строки для оболочки. Это решает проблему передачи аргументов в программу. Это решение представляет собой переносимый способ 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");

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...