/* * lftp - file transfer program * * Copyright (c) 1996-2015 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "CmdExec.h" #include "alias.h" #include "xstring.h" bool CmdExec::quotable(char ch,char in_quotes) { if(!ch) return false; if(ch=='\\' || ch=='!' || ch==in_quotes) return true; if(in_quotes) return false; if(strchr("\"' \t>|;&",ch)) return true; return false; } CmdExec::parse_result CmdExec::parse_one_cmd() { char in_quotes; const char *line=cmd_buf.Get(); const char *line_begin=line; static xstring nextarg; const char *alias=0; if(args) args->Empty(); else args=new ArgV; output=0; char redir_type=0; background=0; if(!*line) { // empty command return PARSE_OK; } if(line[0]=='&' && line[1]=='&') { condition=COND_AND; line+=2; } else if(line[0]=='|' && line[1]=='|') { condition=COND_OR; line+=2; } else { condition=COND_ANY; } // loop for all arguments for(;;) { // skip leading whitespace while(is_space(*line)) line++; if(line[0]=='\\' && line[1]=='\n') { line+=2; continue; // and continue skipping space } if(line[0]=='\r' && line[1]=='\n') line++; if(*line==0) return PARSE_AGAIN; if(*line=='\n' || *line=='|' || *line=='>' || *line==';' || *line=='&') break; nextarg.truncate(0); if(args->count()==0 && *line=='#') { // comment -- skip and return while(*line!='\n' && *line) line++; if(*line=='\n') line++; else return PARSE_AGAIN; skip_cmd(line-line_begin); return PARSE_OK; } if(args->count()==0 && *line=='!') { // shell command -- it ends only with '\n' args->Append("!"); line++; while(is_space(*line)) line++; while(*line!='\n' && *line) { if(*line=='\\' && line[1]=='\n') { line+=2; continue; } nextarg.append(*line++); } if(*line==0) return PARSE_AGAIN; if(*line=='\n') line++; skip_cmd(line-line_begin); if(nextarg.length()>0) args->Append(nextarg); return PARSE_OK; } if(args->count()==0 && *line=='(') { line++; args->Append("("); int level=1; in_quotes=0; for(;;) { if(*line==0) return PARSE_AGAIN; if(*line=='\\' && line[1] && (strchr("\"\\",line[1]) || (level==1 && line[1]==')'))) { nextarg.append(*line++); } else { if(!in_quotes) { if(*line==')') { if(--level==0) break; } else if(*line=='(') level++; } if(in_quotes && *line==in_quotes) in_quotes=0; else if(!in_quotes && is_quote(*line)) in_quotes=*line; } nextarg.append(*line++); } args->Append(nextarg); line++; // skip ) while(is_space(*line)) line++; goto cmd_end; } if(args->count()==0 && *line=='?') { line++; args->Append("?"); continue; } // loop for one argument in_quotes=0; for(;;) { if(*line=='\\' && line[1]=='\n') { line+=2; continue; } if(line[0]=='\r' && line[1]=='\n') line++; if(*line=='\\' && quotable(line[1],in_quotes)) { line++; } else { if(*line==0) return PARSE_AGAIN; if(*line=='\n' || (!in_quotes && (is_space(*line) || *line=='>' || *line=='|' || *line==';' || *line=='&'))) break; if(!in_quotes && is_quote(*line)) { in_quotes=*line; line++; continue; } if(in_quotes && *line==in_quotes) { in_quotes=0; line++; continue; } } nextarg.append(*line++); } if(*line==0) return PARSE_AGAIN; // normal commands finish with \n or ; // check if the first arg is an alias, expand it accordingly. if(args->count()==0) { alias=Alias::Find(nextarg); if(alias) { int alias_len=strlen(alias); /* Check if the previous alias ends before the end of new one. * So the new alias does not expand entirely from previous * aliases and we can repeat the expansion from the very beginning. */ if(alias_field<(int)(line-line_begin)) free_used_aliases(); if(!TouchedAlias::IsTouched(alias,used_aliases)) { skip_cmd(line-line_begin); used_aliases=new TouchedAlias(alias,used_aliases); cmd_buf.Prepend(alias); alias_field+=alias_len; line=line_begin=cmd_buf.Get(); continue; } } } args->Append(nextarg); } if(*line==0) return PARSE_AGAIN; if((line[0]=='&' && line[1]=='&') || (line[0]=='|' && line[1]=='|')) { skip_cmd(line-line_begin); return PARSE_OK; } if(*line=='>' || *line=='|') { redir_type=*line; line++; if(*line=='>') { // '>>' means append redir_type='+'; line++; } // skip leading whitespace while(is_space(*line)) line++; if(*line==0) return PARSE_AGAIN; if(*line=='\n' || *line==';' || *line=='&') { if(redir_type=='|') eprintf(_("parse: missing filter command\n")); else eprintf(_("parse: missing redirection filename\n")); if(*line==';' || *line=='&' || *line=='\n') line++; skip_cmd(line-line_begin); return PARSE_ERR; } nextarg.truncate(0); in_quotes=0; for(;;) { if(*line=='\\' && line[1]=='\n') { line+=2; continue; } if(*line=='\\' && quotable(line[1],in_quotes)) line++; else { if(*line==0) return PARSE_AGAIN; // filename can end with a space, filter command can't. if(*line=='\n' || (!in_quotes && ((redir_type!='|' && is_space(*line)) || *line==';' || *line=='&'))) break; if(!in_quotes && is_quote(*line)) { in_quotes=*line; line++; continue; } if(in_quotes && *line==in_quotes) { in_quotes=0; line++; continue; } } nextarg.append(*line++); } // skip spaces while(is_space(*line)) line++; } cmd_end: if((line[0]=='&' && line[1]=='&') || (line[0]=='|' && line[1]=='|')) ; else if(*line==';' || *line=='&' || *line=='\n') { if(*line=='&') background=1; line++; } skip_cmd(line-line_begin); switch(redir_type) { case('|'): output=new OutputFilter(nextarg); break; case('>'): output=new FileStream(nextarg,O_WRONLY|O_TRUNC|O_CREAT); break; case('+'): output=new FileStream(nextarg,O_WRONLY|O_APPEND|O_CREAT); break; } return PARSE_OK; }