1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 12:45:07 +00:00

Shell: Correctly parse quoted filenames in redirection

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

Fixes #1857 :^)
This commit is contained in:
AnotherTest 2020-04-30 05:26:16 +04:30 committed by Andreas Kling
parent fc34123a54
commit fb63b84c78
2 changed files with 73 additions and 23 deletions

View file

@ -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); if (!in_state(State::InRedirectionPath))
m_state = State::Free; 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); if (!in_state(State::InRedirectionPath))
m_state = State::Free; 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();

View file

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