/*      $NetBSD: interp_backslash.c,v 1.3 2009/07/20 04:59:03 kiyohara Exp $    */

/*-
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* Jordan K. Hubbard
* 29 August 1998
*
* Routine for doing backslash elimination.
*/

#include <sys/cdefs.h>
/* __FBSDID("$FreeBSD: src/sys/boot/common/interp_backslash.c,v 1.6 2003/08/25 23:30:41 obrien Exp $"); */

#include <lib/libsa/stand.h>
#include <lib/libsa/loadfile.h>
#include <lib/libkern/libkern.h>

#include "bootstrap.h"

#define DIGIT(x) (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A')

/*
* backslash: Return malloc'd copy of str with all standard "backslash
* processing" done on it.  Original can be free'd if desired.
*/
char *
backslash(char *str)
{
   /*
    * Remove backslashes from the strings. Turn \040 etc. into a single
    * character (we allow eight bit values). Currently NUL is not
    * allowed.
    *
    * Turn "\n" and "\t" into '\n' and '\t' characters. Etc.
    *
    */
   char *new_str;
   int seenbs = 0;
   int i = 0;

   if ((new_str = strdup(str)) == NULL)
       return NULL;

   while (*str) {
       if (seenbs) {
           seenbs = 0;
           switch (*str) {
           case '\\':
               new_str[i++] = '\\';
               str++;
               break;

           /* preserve backslashed quotes, dollar signs */
           case '\'':
           case '"':
           case '$':
               new_str[i++] = '\\';
               new_str[i++] = *str++;
               break;

           case 'b':
               new_str[i++] = '\b';
               str++;
               break;

           case 'f':
               new_str[i++] = '\f';
               str++;
               break;

           case 'r':
               new_str[i++] = '\r';
               str++;
               break;

           case 'n':
               new_str[i++] = '\n';
               str++;
               break;

           case 's':
               new_str[i++] = ' ';
               str++;
               break;

           case 't':
               new_str[i++] = '\t';
               str++;
               break;

           case 'v':
               new_str[i++] = '\13';
               str++;
               break;

           case 'z':
               str++;
               break;

           case '0': case '1': case '2': case '3': case '4':
           case '5': case '6': case '7': case '8': case '9': {
               char val;

               /* Three digit octal constant? */
               if (*str >= '0' && *str <= '3' &&
                   *(str + 1) >= '0' && *(str + 1) <= '7' &&
                   *(str + 2) >= '0' && *(str + 2) <= '7') {

                   val = (DIGIT(*str) << 6) + (DIGIT(*(str + 1)) << 3) +
                       DIGIT(*(str + 2));

                   /* Allow null value if user really wants to shoot
                      at feet, but beware! */
                   new_str[i++] = val;
                   str += 3;
                   break;
               }

               /* One or two digit hex constant?
                * If two are there they will both be taken.
                * Use \z to split them up if this is not wanted.
                */
               if (*str == '0' &&
                   (*(str + 1) == 'x' || *(str + 1) == 'X') &&
                   isxdigit(*(str + 2))) {
                   val = DIGIT(*(str + 2));
                   if (isxdigit(*(str + 3))) {
                       val = (val << 4) + DIGIT(*(str + 3));
                       str += 4;
                   }
                   else
                       str += 3;
                   /* Yep, allow null value here too */
                   new_str[i++] = val;
                   break;
               }
           }
           break;

           default:
               new_str[i++] = *str++;
               break;
           }
       }
       else {
           if (*str == '\\') {
               seenbs = 1;
               str++;
           }
           else
               new_str[i++] = *str++;
       }
   }

   if (seenbs) {
       /*
        * The final character was a '\'. Put it in as a single backslash.
        */
       new_str[i++] = '\\';
   }
   new_str[i] = '\0';
   return new_str;
}