Serenity Operating System
at hosted 287 lines 9.7 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "Parser.h" 28#include <stdio.h> 29#include <unistd.h> 30#include <ctype.h> 31 32void Parser::commit_token(AllowEmptyToken allow_empty) 33{ 34 if (allow_empty == AllowEmptyToken::No && m_token.is_empty()) 35 return; 36 if (m_state == InRedirectionPath) { 37 m_redirections.last().path = String::copy(m_token); 38 m_token.clear_with_capacity(); 39 return; 40 } 41 m_tokens.append(String::copy(m_token)); 42 m_token.clear_with_capacity(); 43}; 44 45void Parser::commit_subcommand() 46{ 47 if (m_tokens.is_empty()) 48 return; 49 m_subcommands.append({ move(m_tokens), move(m_redirections), {} }); 50} 51 52void Parser::commit_command() 53{ 54 if (m_subcommands.is_empty()) 55 return; 56 m_commands.append({ move(m_subcommands) }); 57} 58 59void Parser::do_pipe() 60{ 61 m_redirections.append({ Redirection::Pipe, STDOUT_FILENO }); 62 commit_subcommand(); 63} 64 65void Parser::begin_redirect_read(int fd) 66{ 67 m_redirections.append({ Redirection::FileRead, fd }); 68} 69 70void Parser::begin_redirect_write(int fd) 71{ 72 m_redirections.append({ Redirection::FileWrite, fd }); 73} 74 75Vector<Command> Parser::parse() 76{ 77 for (size_t i = 0; i < m_input.length(); ++i) { 78 char ch = m_input.characters()[i]; 79 switch (m_state) { 80 case State::Free: 81 if (ch == ' ') { 82 commit_token(); 83 break; 84 } 85 if (ch == ';') { 86 commit_token(); 87 commit_subcommand(); 88 commit_command(); 89 break; 90 } 91 if (ch == '|') { 92 commit_token(); 93 if (m_tokens.is_empty()) { 94 fprintf(stderr, "Syntax error: Nothing before pipe (|)\n"); 95 return {}; 96 } 97 do_pipe(); 98 break; 99 } 100 if (ch == '>') { 101 commit_token(); 102 begin_redirect_write(STDOUT_FILENO); 103 104 // Search for another > for append. 105 m_state = State::InWriteAppendOrRedirectionPath; 106 break; 107 } 108 if (ch == '<') { 109 commit_token(); 110 begin_redirect_read(STDIN_FILENO); 111 m_state = State::InRedirectionPath; 112 break; 113 } 114 if (ch == '\\') { 115 if (i == m_input.length() - 1) { 116 fprintf(stderr, "Syntax error: Nothing to escape (\\)\n"); 117 return {}; 118 } 119 char next_ch = m_input.characters()[i + 1]; 120 m_token.append(next_ch); 121 ++i; 122 break; 123 } 124 if (ch == '\'') { 125 m_state = State::InSingleQuotes; 126 break; 127 } 128 if (ch == '\"') { 129 m_state = State::InDoubleQuotes; 130 break; 131 } 132 133 // redirection from zsh-style multi-digit fd, such as {10}>file 134 if (ch == '{') { 135 bool is_multi_fd_redirection = false; 136 size_t redir_end = i + 1; 137 138 while (redir_end < m_input.length()) { 139 char lookahead_ch = m_input.characters()[redir_end]; 140 if (isdigit(lookahead_ch)) { 141 ++redir_end; 142 continue; 143 } 144 if (lookahead_ch == '}' && redir_end + 1 != m_input.length()) { 145 // Disallow {}> and {}< 146 if (redir_end == i + 1) 147 break; 148 149 ++redir_end; 150 if (m_input.characters()[redir_end] == '>' || m_input.characters()[redir_end] == '<') 151 is_multi_fd_redirection = true; 152 break; 153 } 154 break; 155 } 156 157 if (is_multi_fd_redirection) { 158 commit_token(); 159 160 int fd = atoi(&m_input.characters()[i + 1]); 161 162 if (m_input.characters()[redir_end] == '>') { 163 begin_redirect_write(fd); 164 // Search for another > for append. 165 m_state = State::InWriteAppendOrRedirectionPath; 166 } 167 if (m_input.characters()[redir_end] == '<') { 168 begin_redirect_read(fd); 169 m_state = State::InRedirectionPath; 170 } 171 172 i = redir_end; 173 174 break; 175 } 176 } 177 if (isdigit(ch)) { 178 if (i != m_input.length() - 1) { 179 char next_ch = m_input.characters()[i + 1]; 180 if (next_ch == '>') { 181 commit_token(); 182 begin_redirect_write(ch - '0'); 183 ++i; 184 185 // Search for another > for append. 186 m_state = State::InWriteAppendOrRedirectionPath; 187 break; 188 } 189 if (next_ch == '<') { 190 commit_token(); 191 begin_redirect_read(ch - '0'); 192 ++i; 193 194 m_state = State::InRedirectionPath; 195 break; 196 } 197 } 198 } 199 m_token.append(ch); 200 break; 201 case State::InWriteAppendOrRedirectionPath: 202 if (ch == '>') { 203 commit_token(); 204 m_state = State::InRedirectionPath; 205 ASSERT(m_redirections.size()); 206 m_redirections[m_redirections.size() - 1].type = Redirection::FileWriteAppend; 207 break; 208 } 209 210 // Not another > means that it's probably a path. 211 m_state = InRedirectionPath; 212 [[fallthrough]]; 213 case State::InRedirectionPath: 214 if (ch == '<') { 215 commit_token(); 216 begin_redirect_read(STDIN_FILENO); 217 m_state = State::InRedirectionPath; 218 break; 219 } 220 if (ch == '>') { 221 commit_token(); 222 begin_redirect_read(STDOUT_FILENO); 223 m_state = State::InRedirectionPath; 224 break; 225 } 226 if (ch == '|') { 227 commit_token(); 228 if (m_tokens.is_empty()) { 229 fprintf(stderr, "Syntax error: Nothing before pipe (|)\n"); 230 return {}; 231 } 232 do_pipe(); 233 m_state = State::Free; 234 break; 235 } 236 if (ch == ' ') 237 break; 238 m_token.append(ch); 239 break; 240 case State::InSingleQuotes: 241 if (ch == '\'') { 242 commit_token(AllowEmptyToken::Yes); 243 m_state = State::Free; 244 break; 245 } 246 m_token.append(ch); 247 break; 248 case State::InDoubleQuotes: 249 if (ch == '\"') { 250 commit_token(AllowEmptyToken::Yes); 251 m_state = State::Free; 252 break; 253 } 254 if (ch == '\\') { 255 if (i == m_input.length() - 1) { 256 fprintf(stderr, "Syntax error: Nothing to escape (\\)\n"); 257 return {}; 258 } 259 char next_ch = m_input.characters()[i + 1]; 260 if (next_ch == '$' || next_ch == '`' 261 || next_ch == '"' || next_ch == '\\') { 262 m_token.append(next_ch); 263 ++i; 264 continue; 265 } 266 m_token.append('\\'); 267 break; 268 } 269 m_token.append(ch); 270 break; 271 }; 272 } 273 commit_token(); 274 commit_subcommand(); 275 commit_command(); 276 277 if (!m_subcommands.is_empty()) { 278 for (auto& redirection : m_subcommands.last().redirections) { 279 if (redirection.type == Redirection::Pipe) { 280 fprintf(stderr, "Syntax error: Nothing after last pipe (|)\n"); 281 return {}; 282 } 283 } 284 } 285 286 return move(m_commands); 287}