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 | } |
172 | |