No specific user configuration
[platform/upstream/bash.git] / lib / sh / strtrans.c
index 7005c46..ae88e69 100644 (file)
@@ -1,24 +1,22 @@
-/* strtrans.c - Translate and untranslate strings with ANSI-C escape
-               sequences. */
+/* strtrans.c - Translate and untranslate strings with ANSI-C escape sequences. */
 
-/* Copyright (C) 2000
-   Free Software Foundation, Inc.
+/* Copyright (C) 2000-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash 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 2, or (at your option) any later
-   version.
+   Bash 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.
 
-   Bash 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.
+   Bash 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 Bash; see the file COPYING.  If not, write to the Free Software
-   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include <config.h>
 
 #  include <unistd.h>
 #endif
 
-#include "bashansi.h"
+#include <bashansi.h>
 #include <stdio.h>
-#include <ctype.h>
+#include <chartypes.h>
 
 #include "shell.h"
 
+#include "shmbchar.h"
+#include "shmbutil.h"
+
 #ifdef ESC
 #undef ESC
 #endif
 #define ESC '\033'     /* ASCII */
 
-#ifndef ISOCTAL
-#define ISOCTAL(c)     ((c) >= '0' && (c) <= '7')
-#endif
-
-#ifndef OCTVALUE
-#define OCTVALUE(c)    ((c) - '0')
-#endif
-
-#ifndef isxdigit
-#  define isxdigit(c)  (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
-#endif
-
-#define HEXVALUE(c) \
-  ((c) >= 'a' && (c) <= 'f' ? (c)-'a'+10 : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0')
-
 /* Convert STRING by expanding the escape sequences specified by the
    ANSI C standard.  If SAWC is non-null, recognize `\c' and use that
    as a string terminator.  If we see \c, set *SAWC to 1 before
-   returning.  LEN is the length of STRING.  FOR_ECHO is a flag that
-   means, if non-zero, that we're translating a string for `echo -e',
-   and therefore should not treat a single quote as a character that
-   may be escaped with a backslash. */
+   returning.  LEN is the length of STRING.  If (FLAGS&1) is non-zero,
+   that we're translating a string for `echo -e', and therefore should not
+   treat a single quote as a character that may be escaped with a backslash.
+   If (FLAGS&2) is non-zero, we're expanding for the parser and want to
+   quote CTLESC and CTLNUL with CTLESC.  If (flags&4) is non-zero, we want
+   to remove the backslash before any unrecognized escape sequence. */
 char *
-ansicstr (string, len, for_echo, sawc, rlen)
+ansicstr (string, len, flags, sawc, rlen)
      char *string;
-     int len, for_echo, *sawc, *rlen;
+     int len, flags, *sawc, *rlen;
 {
   int c, temp;
   char *ret, *r, *s;
+  unsigned long v;
 
   if (string == 0 || *string == '\0')
     return ((char *)NULL);
 
-  ret = xmalloc (len + 1);
+#if defined (HANDLE_MULTIBYTE)
+  ret = (char *)xmalloc (4*len + 1);
+#else
+  ret = (char *)xmalloc (2*len + 1);   /* 2*len for possible CTLESC */
+#endif
   for (r = ret, s = string; s && *s; )
     {
       c = *s++;
@@ -84,7 +77,7 @@ ansicstr (string, len, for_echo, sawc, rlen)
            case 'a': c = '\a'; break;
            case 'v': c = '\v'; break;
 #else
-           case 'a': c = '\007'; break;
+           case 'a': c = (int) 0x07; break;
            case 'v': c = (int) 0x0B; break;
 #endif
            case 'b': c = '\b'; break;
@@ -94,25 +87,82 @@ ansicstr (string, len, for_echo, sawc, rlen)
            case 'n': c = '\n'; break;
            case 'r': c = '\r'; break;
            case 't': c = '\t'; break;
-           case '0': case '1': case '2': case '3':
-           case '4': case '5': case '6': case '7':
-             for (temp = 2, c -= '0'; ISOCTAL (*s) && temp--; s++)
+           case '1': case '2': case '3':
+           case '4': case '5': case '6':
+           case '7':
+#if 1
+             if (flags & 1)
+               {
+                 *r++ = '\\';
+                 break;
+               }
+           /*FALLTHROUGH*/
+#endif
+           case '0':
+             /* If (FLAGS & 1), we're translating a string for echo -e (or
+                the equivalent xpg_echo option), so we obey the SUSv3/
+                POSIX-2001 requirement and accept 0-3 octal digits after
+                a leading `0'. */
+             temp = 2 + ((flags & 1) && (c == '0'));
+             for (c -= '0'; ISOCTAL (*s) && temp--; s++)
                c = (c * 8) + OCTVALUE (*s);
+             c &= 0xFF;
              break;
            case 'x':                   /* Hex digit -- non-ANSI */
-             for (temp = 3, c = 0; isxdigit (*s) && temp--; s++)
+             if ((flags & 2) && *s == '{')
+               {
+                 flags |= 16;          /* internal flag value */
+                 s++;
+               }
+             /* Consume at least two hex characters */
+             for (temp = 2, c = 0; ISXDIGIT ((unsigned char)*s) && temp--; s++)
                c = (c * 16) + HEXVALUE (*s);
+             /* DGK says that after a `\x{' ksh93 consumes ISXDIGIT chars
+                until a non-xdigit or `}', so potentially more than two
+                chars are consumed. */
+             if (flags & 16)
+               {
+                 for ( ; ISXDIGIT ((unsigned char)*s); s++)
+                   c = (c * 16) + HEXVALUE (*s);
+                 flags &= ~16;
+                 if (*s == '}')
+                   s++;
+               }
              /* \x followed by non-hex digits is passed through unchanged */
-             if (temp == 3)
+             else if (temp == 2)
                {
                  *r++ = '\\';
                  c = 'x';
                }
+             c &= 0xFF;
              break;
+#if defined (HANDLE_MULTIBYTE)
+           case 'u':
+           case 'U':
+             temp = (c == 'u') ? 4 : 8;        /* \uNNNN \UNNNNNNNN */
+             for (v = 0; ISXDIGIT ((unsigned char)*s) && temp--; s++)
+               v = (v * 16) + HEXVALUE (*s);
+             if (temp == ((c == 'u') ? 4 : 8))
+               {
+                 *r++ = '\\';  /* c remains unchanged */
+                 break;
+               }
+             else if (v <= 0x7f)       /* <= 0x7f translates directly */
+               {
+                 c = v;
+                 break;
+               }
+             else
+               {
+                 temp = u32cconv (v, r);
+                 r += temp;
+                 continue;
+               }
+#endif
            case '\\':
              break;
-           case '\'':
-             if (for_echo)
+           case '\'': case '"': case '?':
+             if (flags & 1)
                *r++ = '\\';
              break;
            case 'c':
@@ -124,8 +174,24 @@ ansicstr (string, len, for_echo, sawc, rlen)
                    *rlen = r - ret;
                  return ret;
                }
-           default:  *r++ = '\\'; break;
+             else if ((flags & 1) == 0 && *s == 0)
+               ;               /* pass \c through */
+             else if ((flags & 1) == 0 && (c = *s))
+               {
+                 s++;
+                 if ((flags & 2) && c == '\\' && c == *s)
+                   s++;        /* Posix requires $'\c\\' do backslash escaping */
+                 c = TOCTRL(c);
+                 break;
+               }
+               /*FALLTHROUGH*/
+           default:
+               if ((flags & 4) == 0)
+                 *r++ = '\\';
+               break;
            }
+         if ((flags & 2) && (c == CTLESC || c == CTLNUL))
+           *r++ = CTLESC;
          *r++ = c;
        }
     }
@@ -142,23 +208,32 @@ ansic_quote (str, flags, rlen)
      char *str;
      int flags, *rlen;
 {
-  char *r, *ret, *s, obuf[8];
-  int l, c, rsize, t;
+  char *r, *ret, *s;
+  int l, rsize;
+  unsigned char c;
+  size_t clen;
+  int b;
+#if defined (HANDLE_MULTIBYTE)
+  wchar_t wc;
+#endif
 
   if (str == 0 || *str == 0)
     return ((char *)0);
 
   l = strlen (str);
-  rsize = 2 * l + 4;
-  r = ret = xmalloc (rsize);
+  rsize = 4 * l + 4;
+  r = ret = (char *)xmalloc (rsize);
 
   *r++ = '$';
   *r++ = '\'';
 
-  for (s = str, l = 0; *s; s++)
+  s = str;
+
+  for (s = str; c = *s; s++)
     {
-      c = *(unsigned char *)s;
-      l = 1;           /* 1 == add backslash; 0 == no backslash */
+      b = l = 1;               /* 1 == add backslash; 0 == no backslash */
+      clen = 1;
+
       switch (c)
        {
        case ESC: c = 'E'; break;
@@ -166,7 +241,7 @@ ansic_quote (str, flags, rlen)
        case '\a': c = 'a'; break;
        case '\v': c = 'v'; break;
 #else
-       case '\007': c = 'a'; break;
+       case 0x07: c = 'a'; break;
        case 0x0b: c = 'v'; break;
 #endif
 
@@ -179,22 +254,38 @@ ansic_quote (str, flags, rlen)
        case '\'':
          break;
        default:
-         if (isprint (c) == 0)
+#if defined (HANDLE_MULTIBYTE)
+         b = is_basic (c);
+         /* XXX - clen comparison to 0 is dicey */
+         if ((b == 0 && ((clen = mbrtowc (&wc, s, MB_CUR_MAX, 0)) < 0 || MB_INVALIDCH (clen) || iswprint (wc) == 0)) ||
+             (b == 1 && ISPRINT (c) == 0))
+#else
+         if (ISPRINT (c) == 0)
+#endif
            {
-             sprintf (obuf, "\\%.3o", c);
-             t = r - ret;
-             RESIZE_MALLOCED_BUFFER (ret, t, 5, rsize, 16);
-             r = ret + t;      /* in case reallocated */
-             for (t = 0; t < 4; t++)
-               *r++ = obuf[t];
+             *r++ = '\\';
+             *r++ = TOCHAR ((c >> 6) & 07);
+             *r++ = TOCHAR ((c >> 3) & 07);
+             *r++ = TOCHAR (c & 07);
              continue;
            }
          l = 0;
          break;
        }
+      if (b == 0 && clen == 0)
+       break;
+
       if (l)
        *r++ = '\\';
-      *r++ = c;
+
+      if (clen == 1)
+       *r++ = c;
+      else
+       {
+         for (b = 0; b < (int)clen; b++)
+           *r++ = (unsigned char)s[b];
+         s += clen - 1;        /* -1 because of the increment above */
+       }
     }
 
   *r++ = '\'';
@@ -203,3 +294,87 @@ ansic_quote (str, flags, rlen)
     *rlen = r - ret;
   return ret;
 }
+
+#if defined (HANDLE_MULTIBYTE)
+int
+ansic_wshouldquote (string)
+     const char *string;
+{
+  const wchar_t *wcs;
+  wchar_t wcc;
+
+  wchar_t *wcstr = NULL;
+  size_t slen;
+
+
+  slen = mbstowcs (wcstr, string, 0);
+
+  if (slen == -1)
+    slen = 0;
+  wcstr = (wchar_t *)xmalloc (sizeof (wchar_t) * (slen + 1));
+  mbstowcs (wcstr, string, slen + 1);
+
+  for (wcs = wcstr; wcc = *wcs; wcs++)
+    if (iswprint(wcc) == 0)
+      {
+       free (wcstr);
+       return 1;
+      }
+
+  free (wcstr);
+  return 0;
+}
+#endif
+
+/* return 1 if we need to quote with $'...' because of non-printing chars. */
+int
+ansic_shouldquote (string)
+     const char *string;
+{
+  const char *s;
+  unsigned char c;
+
+  if (string == 0)
+    return 0;
+
+  for (s = string; c = *s; s++)
+    {
+#if defined (HANDLE_MULTIBYTE)
+      if (is_basic (c) == 0)
+       return (ansic_wshouldquote (s));
+#endif
+      if (ISPRINT (c) == 0)
+       return 1;
+    }
+
+  return 0;
+}
+
+/* $'...' ANSI-C expand the portion of STRING between START and END and
+   return the result.  The result cannot be longer than the input string. */
+char *
+ansiexpand (string, start, end, lenp)
+     char *string;
+     int start, end, *lenp;
+{
+  char *temp, *t;
+  int len, tlen;
+
+  temp = (char *)xmalloc (end - start + 1);
+  for (tlen = 0, len = start; len < end; )
+    temp[tlen++] = string[len++];
+  temp[tlen] = '\0';
+
+  if (*temp)
+    {
+      t = ansicstr (temp, tlen, 2, (int *)NULL, lenp);
+      free (temp);
+      return (t);
+    }
+  else
+    {
+      if (lenp)
+       *lenp = 0;
+      return (temp);
+    }
+}