Blame src/parsecmd.cc

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