【问题标题】:How to safely escape a string from C++如何安全地从 C++ 中转义字符串
【发布时间】:2010-09-22 05:30:02
【问题描述】:

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

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

【问题讨论】:

    标签: c++ c linux shell


    【解决方案1】:

    其他答案包括这个 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;
        }
    }
    

    【讨论】:

    • 你确实是对的,这是唯一正确的方法。但是,问题是如何在 c++ 中为 shell 进行转义。所以我们先回答了它,然后展示了如何正确地做。
    • 正如我所提到的,fork/exec 是更好的方法,但是对于我在答案中包含的给定 shell,可以安全地处理这个问题,因为那是问的问题。我不得不在不能选择 fork/exec 的情况下这样做,所以这并不总是浪费时间。
    • 每个人都已经涵盖了引用,所以我决定详细介绍 fork/exec 并同时表达我的意见。无意冒犯任何人。
    【解决方案2】:

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

    【讨论】:

    • 可以为特定的 shell 创建一个安全的解决方案,但我回答了这个问题,承认这不是最好的解决方案,并提供了一个安全的替代方案。我认为这不值得投反对票。
    【解决方案3】:

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

    您应该考虑使用单独接受参数的函数,从而避免此类问题。 Wikipedia 上有一篇关于著名的 fork-and-exec 模式的好文章。 http://en.wikipedia.org/wiki/Fork-exec

    【讨论】:

      【解决方案4】:

      现在这里是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");
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-10
        • 1970-01-01
        • 2012-04-02
        • 2018-11-12
        • 1970-01-01
        相关资源
        最近更新 更多