about summary refs log tree commit diff
path: root/iconv/gconv_simple.c
diff options
context:
space:
mode:
Diffstat (limited to 'iconv/gconv_simple.c')
-rw-r--r--iconv/gconv_simple.c183
1 files changed, 158 insertions, 25 deletions
diff --git a/iconv/gconv_simple.c b/iconv/gconv_simple.c
index 7fbdfbacf3..197a3d1e9c 100644
--- a/iconv/gconv_simple.c
+++ b/iconv/gconv_simple.c
@@ -26,6 +26,24 @@
 #include <wchar.h>
 #include <sys/param.h>
 
+#ifndef EILSEQ
+# define EILSEQ EINVAL
+#endif
+
+
+/* These are definitions used by some of the functions for handling
+   UTF-8 encoding below.  */
+static const wchar_t encoding_mask[] =
+{
+  ~0x7ff, ~0xffff, ~0x1fffff, ~0x3ffffff
+};
+
+static const unsigned char encoding_byte[] =
+{
+  0xc0, 0xe0, 0xf0, 0xf8, 0xfc
+};
+
+
 
 int
 __gconv_transform_dummy (struct gconv_step *step, struct gconv_step_data *data,
@@ -97,30 +115,70 @@ __gconv_transform_ucs4_utf8 (struct gconv_step *step,
       int save_errno = errno;
       do_write = 0;
 
+      result = GCONV_OK;
       do
 	{
-	  const char *newinbuf = inbuf;
-	  size_t actually;
+	  const wchar_t *newinbuf = (const wchar_t *) inbuf;
+	  size_t actually = 0;
+	  size_t cnt = 0;
 
-	  errno = 0;
-	  actually = __wmemrtombs (&data->outbuf[data->outbufavail],
-				   (const wchar_t **) &newinbuf,
-				   *inlen / sizeof (wchar_t),
-				   data->outbufsize - data->outbufavail,
-				   data->statep);
+	  while (data->outbufavail < data->outbufsize
+		 && cnt * sizeof (wchar_t) <= *inlen)
+	    {
+	      wchar_t wc = newinbuf[cnt];
+
+	      if (wc < 0 && wc > 0x7fffffff)
+		{
+		  /* This is no correct ISO 10646 character.  */
+		  result = GCONV_ILLEGAL_INPUT;
+		  break;
+		}
+
+	      if (wc < 0x80)
+		{
+		  /* It's an one byte sequence.  */
+		  data->outbuf[data->outbufavail++] = (char) wc;
+		  ++actually;
+		}
+	      else
+		{
+		  size_t step;
+		  size_t start;
+
+		  for (step = 2; step < 6; ++step)
+		    if ((wc & encoding_mask[step - 2]) == 0)
+		      break;
+
+		  if (data->outbufavail + step >= data->outbufsize)
+		    /* Too long.  */
+		    break;
+
+		  start = data->outbufavail;
+		  data->outbufavail += step;
+		  actually += step;
+		  data->outbuf[start] = encoding_byte[step - 2];
+		  --step;
+		  do
+		    {
+		      data->outbuf[start + step] = 0x80 | (wc & 0x3f);
+		      wc >>= 6;
+		    }
+		  while (--step > 0);
+		  data->outbuf[start] |= wc;
+		}
+
+	      ++cnt;
+	    }
 
 	  /* Remember how much we converted.  */
-	  do_write += newinbuf - inbuf;
-	  *inlen -= newinbuf - inbuf;
+	  do_write += cnt * sizeof (wchar_t);
+	  *inlen -= cnt * sizeof (wchar_t);
 
 	  data->outbufavail += actually;
 
 	  /* Check whether an illegal character appeared.  */
-	  if (errno != 0)
-	    {
-	      result = GCONV_ILLEGAL_INPUT;
-	      break;
-	    }
+	  if (result != GCONV_OK)
+	    break;
 
 	  if (data->is_last)
 	    {
@@ -199,26 +257,101 @@ __gconv_transform_utf8_ucs4 (struct gconv_step *step,
       int save_errno = errno;
       do_write = 0;
 
+      result = GCONV_OK;
       do
 	{
-	  const char *newinbuf = inbuf;
-	  size_t actually;
+	  wchar_t *outbuf = (wchar_t *) &data->outbuf[data->outbufavail];
+	  size_t cnt = 0;
+	  size_t actually = 0;
 
-	  errno = 0;
-	  actually = __wmemrtowcs ((wchar_t *) &data->outbuf[data->outbufavail],
-				   &newinbuf, *inlen,
-				   ((data->outbufsize
-				     - data->outbufavail) / sizeof (wchar_t)),
-				   data->statep);
+	  while (data->outbufavail + sizeof (wchar_t) <= data->outbufsize
+		 && cnt < *inlen)
+	    {
+	      size_t start = cnt;
+	      wchar_t value;
+	      unsigned char byte;
+	      int count;
+
+	      /* Next input byte.  */
+	      byte = inbuf[cnt++];
+
+	      if (byte < 0x80)
+		{
+		  /* One byte sequence.  */
+		  count = 0;
+		  value = byte;
+		}
+	      else if ((byte & 0xe0) == 0xc0)
+		{
+		  count = 1;
+		  value = byte & 0x1f;
+		}
+	      else if ((byte & 0xf0) == 0xe0)
+		{
+		  /* We expect three bytes.  */
+		  count = 2;
+		  value = byte & 0x0f;
+		}
+	      else if ((byte & 0xf8) == 0xf0)
+		{
+		  /* We expect four bytes.  */
+		  count = 3;
+		  value = byte & 0x07;
+		}
+	      else if ((byte & 0xfc) == 0xf8)
+		{
+		  /* We expect five bytes.  */
+		  count = 4;
+		  value = byte & 0x03;
+		}
+	      else if ((byte & 0xfe) == 0xfc)
+		{
+		  /* We expect six bytes.  */
+		  count = 5;
+		  value = byte & 0x01;
+		}
+	      else
+		{
+		  /* This is an illegal encoding.  */
+		  result = GCONV_ILLEGAL_INPUT;
+		  break;
+		}
+
+	      /* Read the possible remaining bytes.  */
+	      while (cnt < *inbuf && count > 0)
+		{
+		  byte = inbuf[cnt++];
+		  --count;
+
+		  if ((byte & 0xc0) != 0x80)
+		    {
+		      /* This is an illegal encoding.  */
+		      result = GCONV_ILLEGAL_INPUT;
+		      break;
+		    }
+
+		  value <<= 6;
+		  value |= byte & 0x3f;
+		}
+
+	      if (result != GCONV_OK)
+		{
+		  cnt = start;
+		  break;
+		}
+
+	      *outbuf++ = value;
+	      ++actually;
+	    }
 
 	  /* Remember how much we converted.  */
 	  do_write += actually;
-	  *inlen -= newinbuf - inbuf;
+	  *inlen -= cnt;
 
 	  data->outbufavail += actually * sizeof (wchar_t);
 
 	  /* Check whether an illegal character appeared.  */
-	  if (errno != 0)
+	  if (result != GCONV_OK)
 	    {
 	      result = GCONV_ILLEGAL_INPUT;
 	      break;