/* Copyright (C) 2004, 2009
   Free Software Foundation, Inc.
     Written by:  Jeff Conrad    (jeff_conrad@msn.com)
       and        Keith Marshall (keith.d.marshall@ntlworld.com)

This file is part of groff.

groff 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.

groff 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 <http://www.gnu.org/licenses/>. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

/* Define the default mechanism, and messages, for error reporting
 * (user may substitute a preferred alternative, by defining his own
 *  implementation of the macros REPORT_ERROR, QUOTE_ARG_MALLOC_FAILED
 *  and QUOTE_ARG_REALLOC_FAILED, in the header file `nonposix.h').
 */

#include "nonposix.h"

#ifndef  REPORT_ERROR
# define REPORT_ERROR(WHY)  fprintf(stderr, "%s:%s\n", program_name, WHY)
#endif
#ifndef  QUOTE_ARG_MALLOC_ERROR
# define QUOTE_ARG_MALLOC_ERROR   "malloc: Buffer allocation failed"
#endif
#ifndef  QUOTE_ARG_REALLOC_ERROR
# define QUOTE_ARG_REALLOC_ERROR  "realloc: Buffer resize failed"
#endif

extern char *program_name;	/* main program must define this */

#undef FALSE
#undef TRUE
#define FALSE 0
#define TRUE  1

static int
needs_quoting(const char *string)
{
  /* Scan `string' to see whether it needs quoting for MSVC `spawn'/`exec'
   * (i.e., whether it contains whitespace or embedded quotes).
   */

  if (string == NULL)		/* ignore NULL strings */
    return FALSE;

  if (*string == '\0')		/* explicit arguments of zero length	  */
    return TRUE;		/* need quoting, so they aren't discarded */
        
  while (*string) {
    /* Scan non-NULL strings, up to '\0' terminator,
     * returning 'TRUE' if quote or white space found.
     */

    if (*string == '"' || isspace(*string))
      return TRUE;

    /* otherwise, continue scanning to end of string */

    ++string;
  }

  /* Fall through, if no quotes or white space found,
   * in which case, return `FALSE'.
   */

  return FALSE;
}
      
char *
quote_arg(char *string)
{
  /* Enclose arguments in double quotes so that the parsing done in the
   * MSVC runtime startup code doesn't split them at whitespace.  Escape
   * embedded double quotes so that they emerge intact from the parsing.
   */

  int backslashes;
  char *quoted, *p, *q;

  if (needs_quoting(string)) {
    /* Need to create a quoted copy of `string';
     * maximum buffer space needed is twice the original length,
     * plus two enclosing quotes and one `\0' terminator.
     */
    
    if ((quoted = (char *)malloc(2 * strlen(string) + 3)) == NULL) {
      /* Couldn't get a buffer for the quoted string,
       * so complain, and bail out gracefully.
       */

      REPORT_ERROR(QUOTE_ARG_MALLOC_ERROR);
      exit(1);
    }

    /* Ok to proceed:
     * insert the opening quote, then copy the source string,
     * adding escapes as required.
     */

    *quoted = '"';
    for (backslashes = 0, p = string, q = quoted; *p; p++) {
      if (*p == '\\') {
	/* Just count backslashes when we find them.
	 * We will copy them out later, when we know if the count
	 * needs to be adjusted, to escape an embedded quote.
	 */
	
	++backslashes;
      }
      else if (*p == '"') {
	/* This embedded quote character must be escaped,
	 * but first double up any immediately preceding backslashes,
	 * with one extra, as the escape character.
	 */

	for (backslashes += backslashes + 1; backslashes; backslashes--)
	  *++q = '\\';

	/* and now, add the quote character itself */

	*++q = '"';
      }
      else {
	/* Any other character is simply copied,
	 * but first, if we have any pending backslashes,
	 * we must now insert them, without any count adjustment.
	 */

	while (backslashes) {
	  *++q = '\\';
	  --backslashes;
	}

	/* and then, copy the current character */

	*++q = *p;
      }
    }

    /* At end of argument:
     * If any backslashes remain to be copied out, append them now,
     * doubling the actual count to protect against reduction by MSVC,
     * as a consequence of the immediately following closing quote.
     */

    for (backslashes += backslashes; backslashes; backslashes--)
      *++q = '\\';

    /* Finally,
     * add the closing quote, terminate the quoted string,
     * and adjust its size to what was actually required,
     * ready for return.
     */

    *++q = '"';
    *++q = '\0';
    if ((string = (char *)realloc(quoted, strlen(quoted) + 1)) == NULL) {
      /* but bail out gracefully, on error */

      REPORT_ERROR(QUOTE_ARG_REALLOC_ERROR);
      exit(1);
    }
  }

  /* `string' now refers to the argument,
   * quoted and escaped, as required.
   */

  return string;
}

void
purge_quoted_args(char **argv)
{
  /* To avoid memory leaks,
   * free all memory previously allocated by `quoted_arg()',
   * within the scope of the referring argument vector, `argv'.
   */

  if (argv)
    while (*argv) {
      /* Any argument beginning with a double quote
       * SHOULD have been allocated by `quoted_arg()'.
       */
      
      if (**argv == '"')
        free( *argv );		/* so free its allocation */
      ++argv;			/* and continue to the next argument */
    }
}

/* quotearg.c: end of file */
