瀏覽代碼

Shell: Correctly parse quoted filenames in redirection

This commit fixes the (incorrect) behaviour of treating quotes as part
of the redirection filename.

Fixes #1857 :^)
AnotherTest 5 年之前
父節點
當前提交
fb63b84c78
共有 2 個文件被更改,包括 73 次插入23 次删除
  1. 53 21
      Shell/Parser.cpp
  2. 20 2
      Shell/Parser.h

+ 53 - 21
Shell/Parser.cpp

@@ -25,15 +25,15 @@
  */
  */
 
 
 #include "Parser.h"
 #include "Parser.h"
+#include <ctype.h>
 #include <stdio.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <unistd.h>
-#include <ctype.h>
 
 
 void Parser::commit_token(AllowEmptyToken allow_empty)
 void Parser::commit_token(AllowEmptyToken allow_empty)
 {
 {
     if (allow_empty == AllowEmptyToken::No && m_token.is_empty())
     if (allow_empty == AllowEmptyToken::No && m_token.is_empty())
         return;
         return;
-    if (m_state == InRedirectionPath) {
+    if (state() == InRedirectionPath) {
         m_redirections.last().path = String::copy(m_token);
         m_redirections.last().path = String::copy(m_token);
         m_token.clear_with_capacity();
         m_token.clear_with_capacity();
         return;
         return;
@@ -72,11 +72,19 @@ void Parser::begin_redirect_write(int fd)
     m_redirections.append({ Redirection::FileWrite, fd });
     m_redirections.append({ Redirection::FileWrite, fd });
 }
 }
 
 
+bool Parser::in_state(State state) const
+{
+    for (auto s : m_state_stack)
+        if (s == state)
+            return true;
+    return false;
+}
+
 Vector<Command> Parser::parse()
 Vector<Command> Parser::parse()
 {
 {
     for (size_t i = 0; i < m_input.length(); ++i) {
     for (size_t i = 0; i < m_input.length(); ++i) {
         char ch = m_input.characters()[i];
         char ch = m_input.characters()[i];
-        switch (m_state) {
+        switch (state()) {
         case State::Free:
         case State::Free:
             if (ch == ' ') {
             if (ch == ' ') {
                 commit_token();
                 commit_token();
@@ -102,13 +110,13 @@ Vector<Command> Parser::parse()
                 begin_redirect_write(STDOUT_FILENO);
                 begin_redirect_write(STDOUT_FILENO);
 
 
                 // Search for another > for append.
                 // Search for another > for append.
-                m_state = State::InWriteAppendOrRedirectionPath;
+                push_state(State::InWriteAppendOrRedirectionPath);
                 break;
                 break;
             }
             }
             if (ch == '<') {
             if (ch == '<') {
                 commit_token();
                 commit_token();
                 begin_redirect_read(STDIN_FILENO);
                 begin_redirect_read(STDIN_FILENO);
-                m_state = State::InRedirectionPath;
+                push_state(State::InRedirectionPath);
                 break;
                 break;
             }
             }
             if (ch == '\\') {
             if (ch == '\\') {
@@ -122,14 +130,14 @@ Vector<Command> Parser::parse()
                 break;
                 break;
             }
             }
             if (ch == '\'') {
             if (ch == '\'') {
-                m_state = State::InSingleQuotes;
+                push_state(State::InSingleQuotes);
                 break;
                 break;
             }
             }
             if (ch == '\"') {
             if (ch == '\"') {
-                m_state = State::InDoubleQuotes;
+                push_state(State::InDoubleQuotes);
                 break;
                 break;
             }
             }
-            
+
             // redirection from zsh-style multi-digit fd, such as {10}>file
             // redirection from zsh-style multi-digit fd, such as {10}>file
             if (ch == '{') {
             if (ch == '{') {
                 bool is_multi_fd_redirection = false;
                 bool is_multi_fd_redirection = false;
@@ -162,11 +170,11 @@ Vector<Command> Parser::parse()
                     if (m_input.characters()[redir_end] == '>') {
                     if (m_input.characters()[redir_end] == '>') {
                         begin_redirect_write(fd);
                         begin_redirect_write(fd);
                         // Search for another > for append.
                         // Search for another > for append.
-                        m_state = State::InWriteAppendOrRedirectionPath;
+                        push_state(State::InWriteAppendOrRedirectionPath);
                     }
                     }
                     if (m_input.characters()[redir_end] == '<') {
                     if (m_input.characters()[redir_end] == '<') {
                         begin_redirect_read(fd);
                         begin_redirect_read(fd);
-                        m_state = State::InRedirectionPath;
+                        push_state(State::InRedirectionPath);
                     }
                     }
 
 
                     i = redir_end;
                     i = redir_end;
@@ -183,7 +191,7 @@ Vector<Command> Parser::parse()
                         ++i;
                         ++i;
 
 
                         // Search for another > for append.
                         // Search for another > for append.
-                        m_state = State::InWriteAppendOrRedirectionPath;
+                        push_state(State::InWriteAppendOrRedirectionPath);
                         break;
                         break;
                     }
                     }
                     if (next_ch == '<') {
                     if (next_ch == '<') {
@@ -191,7 +199,7 @@ Vector<Command> Parser::parse()
                         begin_redirect_read(ch - '0');
                         begin_redirect_read(ch - '0');
                         ++i;
                         ++i;
 
 
-                        m_state = State::InRedirectionPath;
+                        push_state(State::InRedirectionPath);
                         break;
                         break;
                     }
                     }
                 }
                 }
@@ -201,26 +209,30 @@ Vector<Command> Parser::parse()
         case State::InWriteAppendOrRedirectionPath:
         case State::InWriteAppendOrRedirectionPath:
             if (ch == '>') {
             if (ch == '>') {
                 commit_token();
                 commit_token();
-                m_state = State::InRedirectionPath;
+                pop_state();
+                push_state(State::InRedirectionPath);
                 ASSERT(m_redirections.size());
                 ASSERT(m_redirections.size());
                 m_redirections[m_redirections.size() - 1].type = Redirection::FileWriteAppend;
                 m_redirections[m_redirections.size() - 1].type = Redirection::FileWriteAppend;
                 break;
                 break;
             }
             }
 
 
             // Not another > means that it's probably a path.
             // Not another > means that it's probably a path.
-            m_state = InRedirectionPath;
+            pop_state();
+            push_state(InRedirectionPath);
             [[fallthrough]];
             [[fallthrough]];
         case State::InRedirectionPath:
         case State::InRedirectionPath:
             if (ch == '<') {
             if (ch == '<') {
                 commit_token();
                 commit_token();
                 begin_redirect_read(STDIN_FILENO);
                 begin_redirect_read(STDIN_FILENO);
-                m_state = State::InRedirectionPath;
+                pop_state();
+                push_state(State::InRedirectionPath);
                 break;
                 break;
             }
             }
             if (ch == '>') {
             if (ch == '>') {
                 commit_token();
                 commit_token();
                 begin_redirect_read(STDOUT_FILENO);
                 begin_redirect_read(STDOUT_FILENO);
-                m_state = State::InRedirectionPath;
+                pop_state();
+                push_state(State::InRedirectionPath);
                 break;
                 break;
             }
             }
             if (ch == '|') {
             if (ch == '|') {
@@ -230,7 +242,15 @@ Vector<Command> Parser::parse()
                     return {};
                     return {};
                 }
                 }
                 do_pipe();
                 do_pipe();
-                m_state = State::Free;
+                pop_state();
+                break;
+            }
+            if (ch == '"') {
+                push_state(State::InDoubleQuotes);
+                break;
+            }
+            if (ch == '\'') {
+                push_state(State::InSingleQuotes);
                 break;
                 break;
             }
             }
             if (ch == ' ')
             if (ch == ' ')
@@ -239,16 +259,18 @@ Vector<Command> Parser::parse()
             break;
             break;
         case State::InSingleQuotes:
         case State::InSingleQuotes:
             if (ch == '\'') {
             if (ch == '\'') {
-                commit_token(AllowEmptyToken::Yes);
-                m_state = State::Free;
+                if (!in_state(State::InRedirectionPath))
+                    commit_token(AllowEmptyToken::Yes);
+                pop_state();
                 break;
                 break;
             }
             }
             m_token.append(ch);
             m_token.append(ch);
             break;
             break;
         case State::InDoubleQuotes:
         case State::InDoubleQuotes:
             if (ch == '\"') {
             if (ch == '\"') {
-                commit_token(AllowEmptyToken::Yes);
-                m_state = State::Free;
+                if (!in_state(State::InRedirectionPath))
+                    commit_token(AllowEmptyToken::Yes);
+                pop_state();
                 break;
                 break;
             }
             }
             if (ch == '\\') {
             if (ch == '\\') {
@@ -270,6 +292,16 @@ Vector<Command> Parser::parse()
             break;
             break;
         };
         };
     }
     }
+
+    while (m_state_stack.size() > 1) {
+        auto allow_empty = AllowEmptyToken::No;
+        if (state() == State::InDoubleQuotes || state() == State::InSingleQuotes)
+            allow_empty = AllowEmptyToken::Yes;
+        commit_token(allow_empty);
+        pop_state();
+    }
+    ASSERT(state() == State::Free);
+
     commit_token();
     commit_token();
     commit_subcommand();
     commit_subcommand();
     commit_command();
     commit_command();

+ 20 - 2
Shell/Parser.h

@@ -67,7 +67,10 @@ public:
     Vector<Command> parse();
     Vector<Command> parse();
 
 
 private:
 private:
-    enum class AllowEmptyToken { No, Yes };
+    enum class AllowEmptyToken {
+        No,
+        Yes,
+    };
     void commit_token(AllowEmptyToken = AllowEmptyToken::No);
     void commit_token(AllowEmptyToken = AllowEmptyToken::No);
     void commit_subcommand();
     void commit_subcommand();
     void commit_command();
     void commit_command();
@@ -82,7 +85,22 @@ private:
         InWriteAppendOrRedirectionPath,
         InWriteAppendOrRedirectionPath,
         InRedirectionPath,
         InRedirectionPath,
     };
     };
-    State m_state { Free };
+
+    State state() const { return m_state_stack.last(); }
+
+    void pop_state()
+    {
+        m_state_stack.take_last();
+    }
+
+    void push_state(State state)
+    {
+        m_state_stack.append(state);
+    }
+
+    bool in_state(State) const;
+
+    Vector<State> m_state_stack { Free };
     String m_input;
     String m_input;
 
 
     Vector<Command> m_commands;
     Vector<Command> m_commands;