text: /* $OpenLDAP: pkg/ldap/libraries/libldap/utf-8-conv.c,v 1.16.2.6 2010/04/13 20:23:01 kurt Exp $ */
  /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
   *
   * Copyright 1998-2010 The OpenLDAP Foundation.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted only as authorized by the OpenLDAP
   * Public License.
   *
   * A copy of this license is available in the file LICENSE in the
   * top-level directory of the distribution or, alternatively, at
   * <http://www.OpenLDAP.org/license.html>.
   */
  /* Portions Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved.
   * 
   * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
   * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
   * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
   * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
   * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
   * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
   * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
   * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. 
   *---
   * Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License 
   * can be found in the file "build/LICENSE-2.0.1" in this distribution
   * of OpenLDAP Software.
   */
  
  /*
   * UTF-8 Conversion Routines
   *
   * These routines convert between Wide Character and UTF-8,
   * or between MultiByte and UTF-8 encodings.
   *
   * Both single character and string versions of the functions are provided.
   * All functions return -1 if the character or string cannot be converted.
   */
  
  #include "portable.h"
  
  #if SIZEOF_WCHAR_T >= 4
  /* These routines assume ( sizeof(wchar_t) >= 4 ) */
  
  #include <stdio.h>
  #include <ac/stdlib.h>		/* For wctomb, wcstombs, mbtowc, mbstowcs */
  #include <ac/string.h>
  #include <ac/time.h>		/* for time_t */
  
  #include "ldap-int.h"
  
  #include <ldap_utf8.h>
  
  static unsigned char mask[] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
  
  
  /*-----------------------------------------------------------------------------
  					UTF-8 Format Summary
  
  ASCII chars 						7 bits
      0xxxxxxx
      
  2-character UTF-8 sequence:        11 bits
      110xxxxx  10xxxxxx
  
  3-character UTF-8                  16 bits
      1110xxxx  10xxxxxx  10xxxxxx   
      
  4-char UTF-8                       21 bits 
      11110xxx  10xxxxxx  10xxxxxx  10xxxxxx
      
  5-char UTF-8                       26 bits
      111110xx  10xxxxxx  10xxxxxx  10xxxxxx  10xxxxxx
      
  6-char UTF-8                       31 bits
      1111110x  10xxxxxx  10xxxxxx  10xxxxxx  10xxxxxx  10xxxxxx
      
  Unicode address space   (0 - 0x10FFFF)    21 bits
  ISO-10646 address space (0 - 0x7FFFFFFF)  31 bits
  
  Note: This code does not prevent UTF-8 sequences which are longer than
        necessary from being decoded.
  */
  
  /*----------------------------------------------------------------------------- 
     Convert a UTF-8 character to a wide char. 
     Return the length of the UTF-8 input character in bytes.
  */
  int
  ldap_x_utf8_to_wc ( wchar_t *wchar, const char *utf8char )
  {
  	int utflen, i;
  	wchar_t ch;
  
  	if (utf8char == NULL) return -1;
  
  	/* Get UTF-8 sequence length from 1st byte */
  	utflen = LDAP_UTF8_CHARLEN2(utf8char, utflen);
  	
  	if( utflen==0 || utflen > (int)LDAP_MAX_UTF8_LEN ) return -1;
  
  	/* First byte minus length tag */
  	ch = (wchar_t)(utf8char[0] & mask[utflen]);
  	
  	for(i=1; i < utflen; i++) {
  		/* Subsequent bytes must start with 10 */
  		if ((utf8char[i] & 0xc0) != 0x80) return -1;
  	
  		ch <<= 6;			/* 6 bits of data in each subsequent byte */
  		ch |= (wchar_t)(utf8char[i] & 0x3f);
  	}
  	
  	if (wchar) *wchar = ch;
  
  	return utflen;
  }
  
  /*-----------------------------------------------------------------------------
     Convert a UTF-8 string to a wide char string.
     No more than 'count' wide chars will be written to the output buffer.
     Return the size of the converted string in wide chars, excl null terminator.
  */
  int
  ldap_x_utf8s_to_wcs ( wchar_t *wcstr, const char *utf8str, size_t count )
  {
  	size_t wclen = 0;
  	int utflen, i;
  	wchar_t ch;
  
  
  	/* If input ptr is NULL or empty... */
  	if (utf8str == NULL || !*utf8str) {
  		if ( wcstr )
  			*wcstr = 0;
  		return 0;
  	}
  
  	/* Examine next UTF-8 character.  If output buffer is NULL, ignore count */
  	while ( *utf8str && (wcstr==NULL || wclen<count) ) {
  		/* Get UTF-8 sequence length from 1st byte */
  		utflen = LDAP_UTF8_CHARLEN2(utf8str, utflen);
  		
  		if( utflen==0 || utflen > (int)LDAP_MAX_UTF8_LEN ) return -1;
  
  		/* First byte minus length tag */
  		ch = (wchar_t)(utf8str[0] & mask[utflen]);
  		
  		for(i=1; i < utflen; i++) {
  			/* Subsequent bytes must start with 10 */
  			if ((utf8str[i] & 0xc0) != 0x80) return -1;
  		
  			ch <<= 6;			/* 6 bits of data in each subsequent byte */
  			ch |= (wchar_t)(utf8str[i] & 0x3f);
  		}
  		
  		if (wcstr) wcstr[wclen] = ch;
  		
  		utf8str += utflen;	/* Move to next UTF-8 character */
  		wclen++;			/* Count number of wide chars stored/required */
  	}
  
  	/* Add null terminator if there's room in the buffer. */
  	if (wcstr && wclen < count) wcstr[wclen] = 0;
  
  	return wclen;
  }
  
  
  /*----------------------------------------------------------------------------- 
     Convert one wide char to a UTF-8 character.
     Return the length of the converted UTF-8 character in bytes.
     No more than 'count' bytes will be written to the output buffer.
  */
  int
  ldap_x_wc_to_utf8 ( char *utf8char, wchar_t wchar, size_t count )
  {
  	int len=0;
  
  	if (utf8char == NULL)   /* Just determine the required UTF-8 char length. */
  	{						/* Ignore count */
  		if( wchar < 0 )
  			return -1;
  		if( wchar < 0x80 )
  			return 1;
  		if( wchar < 0x800 )
  			return 2; 
  		if( wchar < 0x10000 )
  			return 3;
  		if( wchar < 0x200000 ) 
  			return 4;
  		if( wchar < 0x4000000 ) 
  			return 5;
  #if SIZEOF_WCHAR_T > 4
  		/* UL is not strictly needed by ANSI C */
  		if( wchar < (wchar_t)0x80000000UL )
  #endif /* SIZEOF_WCHAR_T > 4 */
  			return 6;
  		return -1;
  	}
  
  	
  	if ( wchar < 0 ) {				/* Invalid wide character */
  		len = -1;
  
  	} else if( wchar < 0x80 ) {
  		if (count >= 1) {
  			utf8char[len++] = (char)wchar;
  		}
  
  	} else if( wchar < 0x800 ) {
  		if (count >=2) {
  			utf8char[len++] = 0xc0 | ( wchar >> 6 );
  			utf8char[len++] = 0x80 | ( wchar & 0x3f );
  		}
  
  	} else if( wchar < 0x10000 ) {
  		if (count >= 3) {	
  			utf8char[len++] = 0xe0 | ( wchar >> 12 );
  			utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f );
  			utf8char[len++] = 0x80 | ( wchar & 0x3f );
  		}
  	
  	} else if( wchar < 0x200000 ) {
  		if (count >= 4) {
  			utf8char[len++] = 0xf0 | ( wchar >> 18 );
  			utf8char[len++] = 0x80 | ( (wchar >> 12) & 0x3f );
  			utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f );
  			utf8char[len++] = 0x80 | ( wchar & 0x3f );
  		}
  
  	} else if( wchar < 0x4000000 ) {
  		if (count >= 5) {
  			utf8char[len++] = 0xf8 | ( wchar >> 24 );
  			utf8char[len++] = 0x80 | ( (wchar >> 18) & 0x3f );
  			utf8char[len++] = 0x80 | ( (wchar >> 12) & 0x3f );
  			utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f );
  			utf8char[len++] = 0x80 | ( wchar & 0x3f );
  		}
  
  	} else
  #if SIZEOF_WCHAR_T > 4
  		/* UL is not strictly needed by ANSI C */
  		if( wchar < (wchar_t)0x80000000UL )
  #endif /* SIZEOF_WCHAR_T > 4 */
  	{
  		if (count >= 6) {
  			utf8char[len++] = 0xfc | ( wchar >> 30 );
  			utf8char[len++] = 0x80 | ( (wchar >> 24) & 0x3f );
  			utf8char[len++] = 0x80 | ( (wchar >> 18) & 0x3f );
  			utf8char[len++] = 0x80 | ( (wchar >> 12) & 0x3f );
  			utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f );
  			utf8char[len++] = 0x80 | ( wchar & 0x3f );
  		}
  
  #if SIZEOF_WCHAR_T > 4
  	} else {
  		len = -1;
  #endif /* SIZEOF_WCHAR_T > 4 */
  	}
  	
  	return len;
  
  }
  
  
  /*-----------------------------------------------------------------------------
     Convert a wide char string to a UTF-8 string.
     No more than 'count' bytes will be written to the output buffer.
     Return the # of bytes written to the output buffer, excl null terminator.
  */
  int
  ldap_x_wcs_to_utf8s ( char *utf8str, const wchar_t *wcstr, size_t count )
  {
  	int len = 0;
  	int n;
  	char *p = utf8str;
  	wchar_t empty = 0;		/* To avoid use of L"" construct */
  
  	if (wcstr == NULL)		/* Treat input ptr NULL as an empty string */
  		wcstr = &empty;
  
  	if (utf8str == NULL)	/* Just compute size of output, excl null */
  	{
  		while (*wcstr)
  		{
  			/* Get UTF-8 size of next wide char */
  			n = ldap_x_wc_to_utf8( NULL, *wcstr++, LDAP_MAX_UTF8_LEN);
  			if (n == -1)
  				return -1;
  			len += n;
  		}
  
  		return len;
  	}
  
  	
  	/* Do the actual conversion. */
  
  	n = 1;					/* In case of empty wcstr */
  	while (*wcstr)
  	{
  		n = ldap_x_wc_to_utf8( p, *wcstr++, count);
  		
  		if (n <= 0)  		/* If encoding error (-1) or won't fit (0), quit */
  			break;
  		
  		p += n;
  		count -= n;			/* Space left in output buffer */
  	}
  
  	/* If not enough room for last character, pad remainder with null
  	   so that return value = original count, indicating buffer full. */
  	if (n == 0)
  	{
  		while (count--)
  			*p++ = 0;
  	}
  
  	/* Add a null terminator if there's room. */
  	else if (count)
  		*p = 0;
  
  	if (n == -1)			/* Conversion encountered invalid wide char. */
  		return -1;
  
  	/* Return the number of bytes written to output buffer, excl null. */ 
  	return (p - utf8str);
  }
  
  
  /*-----------------------------------------------------------------------------
     Convert a UTF-8 character to a MultiByte character.
     Return the size of the converted character in bytes.
  */
  int
  ldap_x_utf8_to_mb ( char *mbchar, const char *utf8char,
  		int (*f_wctomb)(char *mbchar, wchar_t wchar) )
  {
  	wchar_t wchar;
  	int n;
  	char tmp[6];				/* Large enough for biggest multibyte char */
  
  	if (f_wctomb == NULL)		/* If no conversion function was given... */
  		f_wctomb = wctomb;		/*    use the local ANSI C function */
   
  	/* First convert UTF-8 char to a wide char */
  	n = ldap_x_utf8_to_wc( &wchar, utf8char);
  
  	if (n == -1)
  		return -1;		/* Invalid UTF-8 character */
  
  	if (mbchar == NULL)
  		n = f_wctomb( tmp, wchar );
  	else
  		n = f_wctomb( mbchar, wchar);
  
  	return n;
  }
  
  /*-----------------------------------------------------------------------------
     Convert a UTF-8 string to a MultiByte string.
     No more than 'count' bytes will be written to the output buffer.
     Return the size of the converted string in bytes, excl null terminator.
  */
  int
  ldap_x_utf8s_to_mbs ( char *mbstr, const char *utf8str, size_t count,
  		size_t (*f_wcstombs)(char *mbstr, const wchar_t *wcstr, size_t count) )
  {
  	wchar_t *wcs;
  	size_t wcsize;
      int n;
  
  	if (f_wcstombs == NULL)		/* If no conversion function was given... */
  		f_wcstombs = wcstombs;	/*    use the local ANSI C function */
   
  	if (utf8str == NULL || *utf8str == 0)	/* NULL or empty input string */
  	{
  		if (mbstr)
  			*mbstr = 0;
  		return 0;
  	}
  
  /* Allocate memory for the maximum size wchar string that we could get. */
  	wcsize = strlen(utf8str) + 1;
  	wcs = (wchar_t *)LDAP_MALLOC(wcsize * sizeof(wchar_t));
  	if (wcs == NULL)
  		return -1;				/* Memory allocation failure. */
  
  	/* First convert the UTF-8 string to a wide char string */
  	n = ldap_x_utf8s_to_wcs( wcs, utf8str, wcsize);
  
  	/* Then convert wide char string to multi-byte string */
  	if (n != -1)
  	{
  		n = f_wcstombs(mbstr, wcs, count);
  	}
  
  	LDAP_FREE(wcs);
  
  	return n;
  }
  
  /*-----------------------------------------------------------------------------
     Convert a MultiByte character to a UTF-8 character.
     'mbsize' indicates the number of bytes of 'mbchar' to check.
     Returns the number of bytes written to the output character.
  */
  int
  ldap_x_mb_to_utf8 ( char *utf8char, const char *mbchar, size_t mbsize,
  		int (*f_mbtowc)(wchar_t *wchar, const char *mbchar, size_t count) )
  {
      wchar_t wchar;
      int n;
  
  	if (f_mbtowc == NULL)		/* If no conversion function was given... */
  		f_mbtowc = mbtowc;		/*    use the local ANSI C function */
   
      if (mbsize == 0)				/* 0 is not valid. */
          return -1;
  
      if (mbchar == NULL || *mbchar == 0)
      {
          if (utf8char)
              *utf8char = 0;
          return 1;
      }
  
  	/* First convert the MB char to a Wide Char */
  	n = f_mbtowc( &wchar, mbchar, mbsize);
  
  	if (n == -1)
  		return -1;
  
  	/* Convert the Wide Char to a UTF-8 character. */
  	n = ldap_x_wc_to_utf8( utf8char, wchar, LDAP_MAX_UTF8_LEN);
  
  	return n;
  }
  
  
  /*-----------------------------------------------------------------------------
     Convert a MultiByte string to a UTF-8 string.
     No more than 'count' bytes will be written to the output buffer.
     Return the size of the converted string in bytes, excl null terminator.
  */   
  int
  ldap_x_mbs_to_utf8s ( char *utf8str, const char *mbstr, size_t count,
  		size_t (*f_mbstowcs)(wchar_t *wcstr, const char *mbstr, size_t count) )
  {
  	wchar_t *wcs;
  	int n;
  	size_t wcsize;
  
  	if (mbstr == NULL)		   /* Treat NULL input string as an empty string */
  		mbstr = "";
  
  	if (f_mbstowcs == NULL)		/* If no conversion function was given... */
  		f_mbstowcs = mbstowcs;	/*    use the local ANSI C function */
   
  	/* Allocate memory for the maximum size wchar string that we could get. */
  	wcsize = strlen(mbstr) + 1;
  	wcs = (wchar_t *)LDAP_MALLOC( wcsize * sizeof(wchar_t) );
  	if (wcs == NULL)
  		return -1;
  
  	/* First convert multi-byte string to a wide char string */
  	n = f_mbstowcs(wcs, mbstr, wcsize);
  
  	/* Convert wide char string to UTF-8 string */
  	if (n != -1)
  	{
  		n = ldap_x_wcs_to_utf8s( utf8str, wcs, count);
  	}
  
  	LDAP_FREE(wcs);
  
  	return n;	
  }
  
  #endif /* SIZEOF_WCHAR_T >= 4 */
  