home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / ncftp-2.3.0-base.tgz / ncftp-2.3.0-base.tar / contrib / ncftp / MakeArgv.c < prev    next >
C/C++ Source or Header  |  1995-04-16  |  17KB  |  575 lines

  1. /* MakeArgv.c
  2.  *
  3.  * Purpose: Translate a command line string into an array of arguments
  4.  *   for use with the user commands;  Pre-process a command line string
  5.  *   and expand $variables for the macros.
  6.  */
  7.  
  8. #include "Sys.h"
  9. #include <ctype.h>
  10. #include "Util.h"
  11. #include "MakeArgv.h"
  12.  
  13. static char *gMAVErrStrings[] = {
  14.     NULL,
  15.     "Unbalanced quotes",
  16.     "Too many sets of quotes",
  17.     "Cannot redirect output to more than one file",
  18.     "No filename supplied to redirect output to",
  19.     "No command supplied to pipe output to",
  20.     "Redirection of input not implemented",
  21.     "Cannot redirect and pipe out at the same time"
  22. };
  23.  
  24. int MakeArgVector(char *str, CmdLineInfoPtr clp)
  25. {
  26.     register char *src;        /* The command line to parse. */
  27.     register char *dst;        /* Where the argument(s) are written. */
  28.     int argc;                /* The number of arguments. */
  29.     char **argv;            /* An array of pointers to the arguments. */
  30.     char quotestack[kMaxQuotePairs + 1];
  31.                             /* Keeps track of each successive group of
  32.                              * (different) quotes.
  33.                              */
  34.     char *qp;                /* The most recent set of quotes we're tracking. */
  35.     int err;                /* Which syntax error occurred. */
  36.     int addQuoteToArg;        /* Needed to tell whether a quote character
  37.                              * should be added to the current argument.
  38.                              */
  39.     int prevArgType;        /* Used to track if a previous parameter was
  40.                              * one that should not be included in the
  41.                              * argument buffer, such as a redirection
  42.                              * symbol (>, >>) and its filename.
  43.                              */
  44.     char *curArg;            /* Points to the beginning of the token we
  45.                              * are writing to.
  46.                              */
  47.     unsigned int c;            /* Char to copy to dst. */
  48.     char numBuf[4];            /* An atoi buffer for a 3-digit octal number. */
  49.     unsigned int octalNum;    /* The resulting integer. */
  50.  
  51.     err = kMavNoErr;
  52.     src = str;
  53.     dst = clp->argBuf;
  54.     argc = 0;
  55.     argv = clp->argVector;
  56.     
  57.     /* Initialize the stack of quote characters.  Each time we get a
  58.      * quote character, we add it to the stack.  When we encounter
  59.      * another quote character of the same type, we remove it from the
  60.      * stack to denote that the set has been balanced.
  61.      *
  62.      * The stack pointer, qp, is init'ed to the first element of the
  63.      * stack, to denote that there are no quote sets active.
  64.      */
  65.     qp = quotestack;
  66.     *qp = 0;
  67.  
  68.     /* Init this to a regular type, meaning we don't have to do any
  69.      * cleanup after this argument like removing the redirected file
  70.      * from the list of arguments.
  71.      */
  72.     prevArgType = kRegularArg;
  73.     
  74.     /* Initially, no outfile has been specified. */
  75.     *clp->outFileName = 0;
  76.     clp->isAppend = 0;
  77.     
  78.     /* Initially, no pipe sub-command either. */
  79.     *clp->pipeCmdLine = 0;
  80.  
  81.     for (;argc < kMaxArgs; argc++) {
  82.         /* First, decide where to write the next argument.
  83.          * Normally we would write it to the argument buffer,
  84.          * but if in a previous iteration we detected a redirection
  85.          * symbol, we would want to write the next argument to the
  86.          * outfile string instead of adding the name of the out file
  87.          * to the argument list.
  88.          */
  89.         if (prevArgType == kRegularArg)
  90.             argv[argc] = dst;
  91.         else {
  92.             /* If the out file string already had something in it when
  93.              * we get here, it means that we are ready to start collecting
  94.              * arguments again.
  95.              */
  96.             if (*clp->outFileName) {
  97.                 /* Fix the argument counter to what it should be. */
  98.                 argc -= 2;
  99.                 
  100.                 /* Re-set dst to point to the argument directly after
  101.                  * the argument before the redirection symbol and the
  102.                  * redirected filename.
  103.                  */
  104.                 dst = argv[argc];
  105.                 
  106.                 /* Don't forget to keep track of whether we got an
  107.                  * overwrite redirection symbol (>) or an append
  108.                  * symbol (>>).
  109.                  */
  110.                 clp->isAppend = (prevArgType == kReDirectOutAppendArg);
  111.                 
  112.                 /* Tell the parser that we're writing arguments again. */
  113.                 prevArgType = kRegularArg;
  114.             } else {
  115.                 /* The out file's name has not been set yet, so write the
  116.                  * next token as the name of the out file.
  117.                  */
  118.                 dst = clp->outFileName;
  119.             }
  120.         }
  121.         
  122.         /* Save the location of the current token for later. */
  123.         curArg = dst;
  124.         *curArg = 0;
  125.  
  126.         /* Skip any whitespace between arguments. */
  127.         while (isspace(*src))
  128.             src++;
  129.         if (!*src) {
  130.             /* We have reached the end of the command line. */
  131.             
  132.             /* If dst was left pointing to the name of the outfile,
  133.              * outfile has an empty name, meaning the user did something
  134.              * like 'aaa bbb >' and forgot the outfile name.
  135.              */
  136.             if (dst == clp->outFileName) {
  137.                 err = kMavErrNoReDirectedFileName;
  138.                 argc--;
  139.             }
  140.                 
  141.             /* If the quote stack pointer does not point to the
  142.              * first element of the stack, it means that there
  143.              * is atleast one set of quotes that wasn't properly
  144.              * balanced.
  145.              */
  146.             if (qp != quotestack)
  147.                 err = kMavErrUnbalancedQuotes;
  148.             goto done;
  149.         }
  150.  
  151.         for (;;) {
  152.             if (*src == '\\') {
  153.                 /* As usual, go ahead and add the character directly after
  154.                  * backslash without worrying about that character's
  155.                  * special meaning, if any.
  156.                  */
  157.                 src++;    /* Skip the backslash, which we don't want. */
  158.                 if (isdigit(src[0]) && isdigit(src[1]) && isdigit(src[2])) {
  159.                     /* They can also specify a non-printing character,
  160.                      * by saying \xxx.
  161.                      */
  162.                     memcpy(numBuf, src, (size_t)3);
  163.                     numBuf[3] = '\0';
  164.                     sscanf(numBuf, "%o", &octalNum);
  165.                     c = octalNum;
  166.                     src += 3;
  167.                     goto copyCharToArg2;
  168.                 }
  169.                 goto copyCharToArg;
  170.             } else if (!*src) {
  171.                 /* If there are no more characters in the command line,
  172.                  * end the current argument in progress.
  173.                  */
  174.                 *dst++ = '\0';
  175.                 break;
  176.             } else if (*src == '^') {
  177.                 /* Let them fake a control character by using the two
  178.                  * character sequence ^X.
  179.                  */
  180.                 ++src;
  181.                 if (*src == '?')
  182.                     c = 0x7f;    /* DEL */
  183.                 else
  184.                     c = ((*src) & 31);
  185.                 ++src;
  186.                 goto copyCharToArg2;
  187.             } else if (ISQUOTE(*src)) {
  188.                 /* First, check to see if this quote is the second
  189.                  * quote to complete the pair.
  190.                  */
  191.                 if (*src == *qp) {
  192.                     /* If it is, remove this set from the stack. */
  193.                     *qp-- = 0;
  194.                     
  195.                     /* Add the quote to the argument if it is not
  196.                      * the outermost set.
  197.                      */
  198.                     addQuoteToArg = (quotestack < qp);
  199.                 } else {
  200.                     /* Add the quote to the argument if it is not
  201.                      * the outermost set.
  202.                      */
  203.                     addQuoteToArg = (quotestack < qp);
  204.                     
  205.                     /* It's extremely unlikely, but prevent over-writing
  206.                      * the quotestack if the user has an extremely
  207.                      * complicated command line.
  208.                      */
  209.                     if (qp < (quotestack + kMaxQuotePairs))
  210.                         *++qp = *src;
  211.                     else {
  212.                         err = kMavErrTooManyQuotePairs;
  213.                         goto done;
  214.                     }
  215.                 }
  216.                 if (addQuoteToArg)
  217.                     goto copyCharToArg;
  218.                 else
  219.                     ++src;
  220.             } else if (qp == quotestack) {
  221.                 /* Take action on some special characters, as long
  222.                  * as we aren't in the middle of a quoted argument.
  223.                  */
  224.                 if (isspace(*src)) {
  225.                     /* End an argument in progress if we encounter
  226.                      * delimiting whitespace, unless this whitespace
  227.                      * is being enclosed within quotes.
  228.                      */
  229.                     *dst++ = 0;
  230.                     break;
  231.                 } else if (*src == '>') {
  232.                     /* The user wants to redirect stdout out to a file. */
  233.                     if (*curArg != 0) {
  234.                         /* End an argument in progress.  We'll be back
  235.                          * at the statement above in the next loop
  236.                          * iteration, but we'll take the 'else'
  237.                          * branch next time since it will look like we
  238.                          * had an empy argument.
  239.                          */
  240.                         *dst++ = 0;    
  241.                     } else {
  242.                         /* Make sure the user doesn't try to redirect
  243.                          * to 2 different files.
  244.                          */
  245.                         if ((*clp->outFileName == 0) && (prevArgType == kRegularArg)) {
  246.                             ++src;    /* Skip the redirection symbol (>). */
  247.                             prevArgType = kReDirectOutArg;
  248.                             if (*src == '>') {
  249.                                 /* If there we had >>, we want to append
  250.                                  * to a file, not overwrite it.
  251.                                  */
  252.                                 ++src;    /* Skip this one also. */
  253.                                 prevArgType = kReDirectOutAppendArg;
  254.                             }
  255.                         } else {
  256.                             err = kMavErrTooManyReDirections;
  257.                             goto done;
  258.                         }
  259.                     }
  260.                     break;
  261.                     /* End '>' */
  262.                 } else if (*src == '<') {
  263.                     err = kMavErrNoReDirectedInput;
  264.                     goto done;
  265.                     /* End '<' */
  266.                 } else if (*src == '|') {
  267.                     /* The rest of this line is an another whole
  268.                      * sub-command line, so we copy everything beyond
  269.                      * the | into the string pipeCmd.
  270.                      */
  271.  
  272.                     /* But first check if we had started an argument,
  273.                      * and if we did, go ahead and
  274.                      * finish it.
  275.                      */
  276.                     if (dst > curArg)
  277.                         *dst++ = 0;
  278.                     else {
  279.                         /* We have an empty argument, so get rid of it.
  280.                          * To do that, we prevent the arg counter from
  281.                          * advancing to the next item by decrementing
  282.                          * it now, and letting the loop counter
  283.                          * re-increment it, thus having the effect of
  284.                          * not adding anything at all.
  285.                          */
  286.                         --argc;
  287.                     }
  288.  
  289.                     /* It would be an error to try and >redirect and
  290.                      * |pipe out both, so check for this first by
  291.                      * making sure the outfile is empty.
  292.                      */
  293.                     if (*clp->outFileName != 0) {
  294.                         err = kMavErrBothPipeAndReDirOut;
  295.                         goto done;
  296.                     }
  297.  
  298.                     /* Now start writing to the pipe string. */
  299.                     dst = clp->pipeCmdLine;
  300.                     src++;        /* Don't copy the | character... */
  301.                     while (isspace(*src))    /* ...or whitespace. */
  302.                         src++;
  303.                     while ((*src != '\0') && (*src != '\n'))
  304.                         *dst++ = *src++;
  305.                     *dst = 0;
  306.                     
  307.                     /* Ensure the user gave us something to pipe into. */
  308.                     if (dst == clp->pipeCmdLine)
  309.                         err = kMavErrNoPipeCommand;
  310.                     break;
  311.                     /* End '|' */
  312.                 } else if (*src == '#') {
  313.                     /* Ignore everything after the #comment character. */
  314.                     *src = 0;
  315.                     
  316.                     /* If we had started an argument, go ahead and
  317.                      * finish it.
  318.                      */
  319.                     if (dst > curArg)
  320.                         *dst++ = 0;
  321.                     else {
  322.                         /* We have an empty argument, so get rid of it.
  323.                          * To do that, we prevent the arg counter from
  324.                          * advancing to the next item by decrementing
  325.                          * it now, and letting the loop counter
  326.                          * re-increment it, thus having the effect of
  327.                          * not adding anything at all.
  328.                          */
  329.                         --argc;
  330.                     }
  331.                     break;
  332.                     /* End '#' */
  333.                 } else if (*src == '!') {
  334.                     /* We need a special case when the user want to
  335.                      * spawn a shell command.  We need argument 0 to
  336.                      * be just an !.
  337.                      */
  338.                     if (dst == argv[0]) {
  339.                         /* Form argument one, then break. */
  340.                         *dst++ = *src++;
  341.                         *dst++ = 0;
  342.                         break;
  343.                     } else {
  344.                         /* Otherwise pretend that ! is a normal character. */
  345.                         goto copyCharToArg;
  346.                     }
  347.                     /* End '!' */
  348.                 } else {
  349.                     /* We have a regular character, not within a
  350.                      * quoted string, to copy to an argument.
  351.                      */
  352.                     goto copyCharToArg;
  353.                 }
  354.                 /* End if not within a quoted string. */
  355.             } else {
  356.                 /* If we get to this point (naturally), we are within
  357.                  * set of quotes, so just copy everything in between
  358.                  * them to the argument.
  359.                  */
  360.                  
  361. copyCharToArg:    /* Add this character to the current token. */
  362.                 c = *src++;
  363. copyCharToArg2:
  364.                 *dst++ = c;
  365.             }
  366.             /* End collecting an argument. */
  367.         }
  368.         /* End collecting all arguments. */
  369.     }
  370.  
  371. done:
  372.  
  373.     /* Probably should not act on lines with syntax errors. */
  374.     if (err)
  375.         argc = 0;
  376.     clp->argCount = argc;
  377.     argv[argc] = NULL;
  378.     
  379.     /* Keep a copy of the original command line, for commands that
  380.      * need to peek at it.
  381.      */
  382.     argv[argc + 1] = str;
  383.     argv[argc + 2] = (char *) clp;
  384.     argv[argc + 3] = NULL;    /* This will stay NULL. */
  385.  
  386.     clp->err = err;
  387.     clp->errStr = gMAVErrStrings[err];
  388.     return err;
  389. }    /* MakeArgVector */
  390.  
  391.  
  392.  
  393.  
  394. /* This routine scans a command line and expands dollar-variables, like
  395.  * $2 (to denote the second argument), given a previously formed argument
  396.  * vector.  Why isn't this a part of the MakeArgVector() routine, above?
  397.  * Because MakeArgVector() quits after it finds a |;  Because we only
  398.  * need to do this for macros, so don't waste time doing it if it isn't
  399.  * necessary;  and because MakeArgVector() is already quite large.
  400.  */
  401. void ExpandDollarVariables(char *cmdLine, size_t sz, int argc, char **argv)
  402. {
  403.     char *dst, *cp, *cp2, *start;
  404.     char s[32];
  405.     int argNum, argNum2;
  406.     size_t dstLen, amtToMove;
  407.     long amtToShift;
  408.  
  409.     /* We need to create a buffer large enough to hold a possibly long
  410.      * string consisting of several arguments cat'ted together.
  411.      */
  412.     dst = (char *) malloc(sz);
  413.     if ((dst != NULL) && (argc > 0)) {
  414.         /* Scan the line looking for trouble (i.e. $vars). */
  415.         for (cp = cmdLine; ; cp++) {
  416.             /* Allow the user to insert a $ if she needs to, without
  417.              * having our routine trying to expand it into something.
  418.              */
  419.             if (*cp == '\\') {
  420.                 ++cp;    /* skip backslash. */
  421.                 if (!*cp)
  422.                     break;    /* no \, then 0 please. */
  423.                 ++cp;    /* skip thing being escaped. */
  424.             }
  425.             if (!*cp)
  426.                 break;    /* Done. */
  427.             if (*cp == '$') {
  428.                 start = cp;
  429.                 *dst = 0;
  430.                 /* Now determine whether it is the simple form,
  431.                  * like $3, or a complex form, which requires parentheses,
  432.                  * such as:
  433.                  *   $(1,2,4)  :  Insert arguments one, two, and four.
  434.                  *   $(1-4)    :  Arguments one through four inclusive.
  435.                  *   $(3+)     :  Arg 3, and all args after it.
  436.                  * or a special form, such as:
  437.                  *   $*        :  All arguments (except 0).
  438.                  *   $@        :  All arguments (except 0), each "quoted."
  439.                  */
  440.                 ++cp;    /* skip $. */
  441.                 if (*cp == '(') {
  442.                     /* Complex form. */
  443.                     ++cp;    /* Skip left paren. */
  444.                     
  445.                     while (1) {
  446.                         if (!*cp) break;
  447.                         if (*cp == ')') {
  448.                             ++cp;
  449.                             break;
  450.                         }
  451.                         /* Collect the first (and maybe only) number. */
  452.                         for (cp2=s; isdigit(*cp); )
  453.                             *cp2++ = *cp++;
  454.                         *cp2 = 0;
  455.                         argNum = atoi(s);
  456.                         if (argNum >= 0 && argNum < argc) {
  457.                             if (!*dst)
  458.                                 strncpy(dst, argv[argNum], sz);
  459.                             else {
  460.                                 strncat(dst, " ", sz);                            
  461.                                 strncat(dst, argv[argNum], sz);                            
  462.                             }
  463.                             if (*cp == '-') {
  464.                                 /* We have a range. */
  465.                                 ++cp;    /* Skip dash. */
  466.                                 /* Collect the second number. */
  467.                                 for (cp2=s; isdigit(*cp); )
  468.                                     *cp2++ = *cp++;
  469.                                 *cp2 = 0;
  470.                                 argNum2 = atoi(s);
  471.                                 
  472.                                 /* Make sure we don't copy too many. */
  473.                                 if (argNum2 >= argc)
  474.                                     argNum2 = argc - 1;
  475.                                 if (argNum2 > argNum)
  476.                                     goto copyRest;
  477.                                 /* End range. */
  478.                             } else if (*cp == '+') {
  479.                                 ++cp;
  480.                                 argNum2 = argc - 1;
  481.                             copyRest:
  482.                                 for (++argNum; argNum <= argNum2; ++argNum) {
  483.                                     strncat(dst, " ", sz);
  484.                                     strncat(dst, argv[argNum], sz);
  485.                                 }
  486.                             }
  487.                             /* End if the argument number was valid. */
  488.                         }
  489.                         /* Now see if we have a comma.  If we have a comma,
  490.                          * we can do some more.  Otherwise it is either a
  491.                          * bad char or a right paren, so we'll break out.
  492.                          */
  493.                         if (*cp == ',')
  494.                             ++cp;
  495.                         /* End loop between parens. */
  496.                     }
  497.                 } else if (isdigit(*cp)) {
  498.                     /* Simple form. */
  499.                     for (cp2=s; isdigit(*cp); )
  500.                         *cp2++ = *cp++;
  501.                     *cp2 = 0;
  502.                     argNum = atoi(s);
  503.                     if (argNum >= 0 && argNum < argc)
  504.                         strncpy(dst, argv[argNum], sz);
  505.                 } else if (*cp == '*') {
  506.                     /* Insert all arguments, except 0. */
  507.                     ++cp;    /* Skip *. */
  508.                     argNum = 1;
  509.                     argNum2 = argc - 1;
  510.                     if (argNum <= argNum2)
  511.                         strncpy(dst, argv[argNum], sz);
  512.                     for (++argNum; argNum <= argNum2; ++argNum) {
  513.                         strncat(dst, " ", sz);
  514.                         strncat(dst, argv[argNum], sz);
  515.                     }
  516.                 } else if (*cp == '@') {
  517.                     /* Insert all arguments, except 0, each quoted. */
  518.                     ++cp;    /* Skip @. */
  519.                     argNum = 1;
  520.                     argNum2 = argc - 1;
  521.                     if (argNum <= argNum2) {
  522.                         strncpy(dst, "\"", sz);
  523.                         strncat(dst, argv[argNum], sz);
  524.                         strncat(dst, "\"", sz);
  525.                     }
  526.                     for (++argNum; argNum <= argNum2; ++argNum) {
  527.                         strncat(dst, " \"", sz);
  528.                         strncat(dst, argv[argNum], sz);
  529.                         strncat(dst, "\"", sz);
  530.                     }
  531.                 }
  532.                 dstLen = strlen(dst);
  533.                 
  534.                 /* This is the number of bytes we need to move the
  535.                  * rest of the line down, so we can insert the
  536.                  * variable's value, which would be the dst pointer.
  537.                  */
  538.                 amtToShift = dstLen - (cp - start);
  539.                 
  540.                 /* Don't bother moving if we don't have to. */
  541.                 if (amtToShift != 0) {
  542.                     amtToMove = (size_t) (
  543.                         (long)sz - (long)(cp - cmdLine) - amtToShift - 1);
  544.                     /* Move the rest of the line down, so we can do
  545.                      * the insertion without overwriting any of the
  546.                      * original string.
  547.                      */
  548.                     MEMMOVE(cp + amtToShift, cp, amtToMove);
  549.                 }
  550.                 
  551.                 /* Copy the value of the variable into the space we
  552.                  * created for it.
  553.                  */
  554.                 memcpy(start, dst, dstLen);
  555.                 
  556.                 /* Adjust the scan pointer so it points after the stuff
  557.                  * we just wrote.
  558.                  */
  559.                 cp = start + amtToShift;
  560.             }
  561.             
  562.         }
  563.         /* It's possible (but extremely unlikely) that the null terminator
  564.          * got overwritten when we did an insertion.  This could happen
  565.          * if the command line was very close to "full," or if the stuff
  566.          * we inserted was long.
  567.          */
  568.         cmdLine[sz - 1] = 0;
  569.  
  570.         free(dst);
  571.     }
  572. }    /* ExpandDollarVariables */
  573.  
  574. /* eof makeargv.c */
  575.