| xf.li | bdd93d5 | 2023-05-12 07:10:14 -0700 | [diff] [blame] | 1 | /* Recode strings between character sets, using iconv. | 
 | 2 |    Copyright (C) 2002-2016 Free Software Foundation, Inc. | 
 | 3 |  | 
 | 4 |    This program is free software; you can redistribute it and/or | 
 | 5 |    modify it under the terms of the GNU Lesser General Public License as | 
 | 6 |    published by the Free Software Foundation; either version 2.1, or (at | 
 | 7 |    your option) any later version. | 
 | 8 |  | 
 | 9 |    This program is distributed in the hope that it will be useful, | 
 | 10 |    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 11 |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 12 |    GNU Lesser General Public License for more details. | 
 | 13 |  | 
 | 14 |    You should have received a copy of the GNU Lesser General Public | 
 | 15 |    License along with this program; if not, see | 
 | 16 |    <http://www.gnu.org/licenses/>.  */ | 
 | 17 |  | 
 | 18 | #ifdef HAVE_CONFIG_H | 
 | 19 | # include <config.h> | 
 | 20 | #endif | 
 | 21 |  | 
 | 22 | /* Get prototype. */ | 
 | 23 | #include "iconvme.h" | 
 | 24 |  | 
 | 25 | /* Get malloc. */ | 
 | 26 | #include <stdlib.h> | 
 | 27 |  | 
 | 28 | /* Get strcmp. */ | 
 | 29 | #include <string.h> | 
 | 30 |  | 
 | 31 | /* Get errno. */ | 
 | 32 | #include <errno.h> | 
 | 33 |  | 
 | 34 | #ifdef _LIBC | 
 | 35 | # define HAVE_ICONV 1 | 
 | 36 | #else | 
 | 37 | /* Get strdup. */ | 
 | 38 | # include "strdup.h" | 
 | 39 | #endif | 
 | 40 |  | 
 | 41 | #if HAVE_ICONV | 
 | 42 | /* Get iconv etc. */ | 
 | 43 | # include <iconv.h> | 
 | 44 | /* Get MB_LEN_MAX, CHAR_BIT.  */ | 
 | 45 | # include <limits.h> | 
 | 46 | #endif | 
 | 47 |  | 
 | 48 | #ifndef SIZE_MAX | 
 | 49 | # define SIZE_MAX ((size_t) -1) | 
 | 50 | #endif | 
 | 51 |  | 
 | 52 | /* Convert a zero-terminated string STR from the FROM_CODSET code set | 
 | 53 |    to the TO_CODESET code set.  The returned string is allocated using | 
 | 54 |    malloc, and must be dellocated by the caller using free.  On | 
 | 55 |    failure, NULL is returned and errno holds the error reason.  Note | 
 | 56 |    that if TO_CODESET uses \0 for anything but to terminate the | 
 | 57 |    string, the caller of this function may have difficulties finding | 
 | 58 |    out the length of the output string.  */ | 
 | 59 | char * | 
 | 60 | iconv_string (const char *str, const char *from_codeset, | 
 | 61 | 	      const char *to_codeset) | 
 | 62 | { | 
 | 63 |   char *dest = NULL; | 
 | 64 | #if HAVE_ICONV | 
 | 65 |   iconv_t cd; | 
 | 66 |   char *outp; | 
 | 67 |   char *p = (char *) str; | 
 | 68 |   size_t inbytes_remaining = strlen (p); | 
 | 69 |   /* Guess the maximum length the output string can have.  */ | 
 | 70 |   size_t outbuf_size = inbytes_remaining + 1; | 
 | 71 |   size_t outbytes_remaining; | 
 | 72 |   size_t err; | 
 | 73 |   int have_error = 0; | 
 | 74 |  | 
 | 75 |   /* Use a worst-case output size guess, so long as that wouldn't be | 
 | 76 |      too large for comfort.  It's OK if the guess is wrong so long as | 
 | 77 |      it's nonzero.  */ | 
 | 78 |   size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2); | 
 | 79 |   if (outbuf_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX) | 
 | 80 |     outbuf_size *= MB_LEN_MAX; | 
 | 81 |   outbytes_remaining = outbuf_size - 1; | 
 | 82 | #endif | 
 | 83 |  | 
 | 84 |   if (strcmp (to_codeset, from_codeset) == 0) | 
 | 85 |     return strdup (str); | 
 | 86 |  | 
 | 87 | #if HAVE_ICONV | 
 | 88 |   cd = iconv_open (to_codeset, from_codeset); | 
 | 89 |   if (cd == (iconv_t) -1) | 
 | 90 |     return NULL; | 
 | 91 |  | 
 | 92 |   outp = dest = (char *) malloc (outbuf_size); | 
 | 93 |   if (dest == NULL) | 
 | 94 |     goto out; | 
 | 95 |  | 
 | 96 | again: | 
 | 97 |   err = iconv (cd, &p, &inbytes_remaining, &outp, &outbytes_remaining); | 
 | 98 |  | 
 | 99 |   if (err == (size_t) - 1) | 
 | 100 |     { | 
 | 101 |       switch (errno) | 
 | 102 | 	{ | 
 | 103 | 	case EINVAL: | 
 | 104 | 	  /* Incomplete text, do not report an error */ | 
 | 105 | 	  break; | 
 | 106 |  | 
 | 107 | 	case E2BIG: | 
 | 108 | 	  { | 
 | 109 | 	    size_t used = outp - dest; | 
 | 110 | 	    size_t newsize = outbuf_size * 2; | 
 | 111 | 	    char *newdest; | 
 | 112 |  | 
 | 113 | 	    if (newsize <= outbuf_size) | 
 | 114 | 	      { | 
 | 115 | 		errno = ENOMEM; | 
 | 116 | 		have_error = 1; | 
 | 117 | 		goto out; | 
 | 118 | 	      } | 
 | 119 | 	    newdest = (char *) realloc (dest, newsize); | 
 | 120 | 	    if (newdest == NULL) | 
 | 121 | 	      { | 
 | 122 | 		have_error = 1; | 
 | 123 | 		goto out; | 
 | 124 | 	      } | 
 | 125 | 	    dest = newdest; | 
 | 126 | 	    outbuf_size = newsize; | 
 | 127 |  | 
 | 128 | 	    outp = dest + used; | 
 | 129 | 	    outbytes_remaining = outbuf_size - used - 1;	/* -1 for NUL */ | 
 | 130 |  | 
 | 131 | 	    goto again; | 
 | 132 | 	  } | 
 | 133 | 	  break; | 
 | 134 |  | 
 | 135 | 	case EILSEQ: | 
 | 136 | 	  have_error = 1; | 
 | 137 | 	  break; | 
 | 138 |  | 
 | 139 | 	default: | 
 | 140 | 	  have_error = 1; | 
 | 141 | 	  break; | 
 | 142 | 	} | 
 | 143 |     } | 
 | 144 |  | 
 | 145 |   *outp = '\0'; | 
 | 146 |  | 
 | 147 | out: | 
 | 148 |   { | 
 | 149 |     int save_errno = errno; | 
 | 150 |  | 
 | 151 |     if (iconv_close (cd) < 0 && !have_error) | 
 | 152 |       { | 
 | 153 | 	/* If we didn't have a real error before, make sure we restore | 
 | 154 | 	   the iconv_close error below. */ | 
 | 155 | 	save_errno = errno; | 
 | 156 | 	have_error = 1; | 
 | 157 |       } | 
 | 158 |  | 
 | 159 |     if (have_error && dest) | 
 | 160 |       { | 
 | 161 | 	free (dest); | 
 | 162 | 	dest = NULL; | 
 | 163 | 	errno = save_errno; | 
 | 164 |       } | 
 | 165 |   } | 
 | 166 | #else | 
 | 167 |   errno = ENOSYS; | 
 | 168 | #endif | 
 | 169 |  | 
 | 170 |   return dest; | 
 | 171 | } |