more portability fixes
[samba.git] / source / lib / iconv.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 3.0
4    minimal iconv implementation
5    Copyright (C) Andrew Tridgell 2001
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 static size_t ascii_pull(char **, size_t *, char **, size_t *);
25 static size_t ascii_push(char **, size_t *, char **, size_t *);
26 static size_t weird_pull(char **, size_t *, char **, size_t *);
27 static size_t weird_push(char **, size_t *, char **, size_t *);
28 static size_t iconv_copy(char **, size_t *, char **, size_t *);
29
30 /*
31   for each charset we have a function that pulls from that charset to 
32   a ucs2 buffer, and a function that pushes to a ucs2 buffer 
33 */
34 static struct {
35         char *name;
36         size_t (*pull)(char **inbuf, size_t *inbytesleft,
37                        char **outbuf, size_t *outbytesleft);
38         size_t (*push)(char **inbuf, size_t *inbytesleft,
39                        char **outbuf, size_t *outbytesleft);
40 } charsets[] = {
41         {"UCS2", iconv_copy, iconv_copy},
42         {"ASCII", ascii_pull, ascii_push},
43         {"WEIRD", weird_pull, weird_push},
44         {NULL, NULL, NULL}
45 };
46
47 /*
48   this is a simple portable iconv() implementaion. It only knows about
49   a very small number of character sets - just enough that Samba works
50   on systems that don't have iconv
51  */
52 size_t smb_iconv(smb_iconv_t cd, 
53                  char **inbuf, size_t *inbytesleft,
54                  char **outbuf, size_t *outbytesleft)
55 {
56         char cvtbuf[2048];
57         char *bufp = cvtbuf;
58         size_t bufsize;
59
60 #ifdef HAVE_NATIVE_ICONV
61         if (cd->cd) {
62                 return iconv(cd->cd, inbuf, inbytesleft, outbuf, outbytesleft);
63         }
64 #endif
65
66         if (!inbuf || ! *inbuf || !outbuf || ! *outbuf) return 0;
67
68         /* in most cases we can go direct */
69         if (cd->direct) {
70                 return cd->direct(inbuf, inbytesleft, outbuf, outbytesleft);
71         }
72
73         /* otherwise we have to do it chunks at a time */
74         while (*inbytesleft > 0) {
75                 bufp = cvtbuf;
76                 bufsize = sizeof(cvtbuf);
77                 if (cd->pull(inbuf, inbytesleft, &bufp, &bufsize) == -1 &&
78                     errno != E2BIG) return -1;
79
80                 bufp = cvtbuf;
81                 bufsize = sizeof(cvtbuf) - bufsize;
82                 if (cd->push(&bufp, &bufsize, outbuf, outbytesleft) == -1) return -1;
83         }
84
85         return 0;
86 }
87
88 /*
89   simple iconv_open() wrapper
90  */
91 smb_iconv_t smb_iconv_open(const char *tocode, const char *fromcode)
92 {
93         smb_iconv_t ret;
94         int from, to;
95 #ifdef HAVE_NATIVE_ICONV
96         iconv_t cd = NULL;
97 #endif
98
99         for (from=0; charsets[from].name; from++) {
100                 if (strcasecmp(charsets[from].name, fromcode) == 0) break;
101         }
102         for (to=0; charsets[to].name; to++) {
103                 if (strcasecmp(charsets[to].name, tocode) == 0) break;
104         }
105
106         if (!charsets[from].name || !charsets[to].name) {
107 #ifdef HAVE_NATIVE_ICONV
108                 cd = iconv_open(tocode, fromcode);
109                 if (!cd)
110 #endif
111                 {
112                         errno = EINVAL;
113                         return (smb_iconv_t)-1;
114                 }
115         }
116
117         ret = (smb_iconv_t)malloc(sizeof(*ret));
118         if (!ret) {
119                 errno = ENOMEM;
120                 return (smb_iconv_t)-1;
121         }
122         memset(ret, 0, sizeof(*ret));
123
124 #ifdef HAVE_NATIVE_ICONV
125         /* see if we wil be using the native iconv */
126         if (cd) {
127                 ret->cd = cd;
128                 return ret;
129         }
130 #endif
131
132         /* check for the simplest null conversion */
133         if (from == to) {
134                 ret->direct = iconv_copy;
135                 return ret;
136         }
137
138         /* check for conversion to/from ucs2 */
139         if (from == 0) {
140                 ret->direct = charsets[to].push;
141                 return ret;
142         }
143         if (to == 0) {
144                 ret->direct = charsets[from].pull;
145                 return ret;
146         }
147
148         /* the general case has to go via a buffer */
149         ret->pull = charsets[from].pull;
150         ret->push = charsets[to].push;
151         return ret;
152 }
153
154 /*
155   simple iconv_close() wrapper
156 */
157 int smb_iconv_close (smb_iconv_t cd)
158 {
159 #ifdef HAVE_NATIVE_ICONV
160         if (cd->cd) {
161                 iconv_close(cd->cd);
162         }
163 #endif
164         memset(cd, 0, sizeof(*cd));
165         free(cd);
166         return 0;
167 }
168
169
170 /**********************************************************************
171  the following functions implement the builtin character sets in Samba
172  and also the "test" character sets that are designed to test
173  multi-byte character set support for english users
174 ***********************************************************************/
175
176 static size_t ascii_pull(char **inbuf, size_t *inbytesleft,
177                          char **outbuf, size_t *outbytesleft)
178 {
179         while (*inbytesleft >= 1 && *outbytesleft >= 2) {
180                 (*outbuf)[0] = (*inbuf)[0];
181                 (*outbuf)[1] = 0;
182                 (*inbytesleft)  -= 1;
183                 (*outbytesleft) -= 2;
184                 (*inbuf)  += 1;
185                 (*outbuf) += 2;
186         }
187
188         if (*inbytesleft > 0) {
189                 errno = E2BIG;
190                 return -1;
191         }
192         
193         return 0;
194 }
195
196 static size_t ascii_push(char **inbuf, size_t *inbytesleft,
197                          char **outbuf, size_t *outbytesleft)
198 {
199         int ir_count=0;
200
201         while (*inbytesleft >= 2 && *outbytesleft >= 1) {
202                 (*outbuf)[0] = (*inbuf)[0];
203                 if ((*inbuf)[1]) ir_count++;
204                 (*inbytesleft)  -= 2;
205                 (*outbytesleft) -= 1;
206                 (*inbuf)  += 2;
207                 (*outbuf) += 1;
208         }
209
210         if (*inbytesleft == 1) {
211                 errno = EINVAL;
212                 return -1;
213         }
214
215         if (*inbytesleft > 1) {
216                 errno = E2BIG;
217                 return -1;
218         }
219         
220         return ir_count;
221 }
222
223
224 /* the "weird" character set is very useful for testing multi-byte
225    support and finding bugs. Don't use on a production system! 
226 */
227 static struct {
228         char from;
229         char *to;
230         int len;
231 } weird_table[] = {
232         {'q', "^q^", 3},
233         {'Q', "^Q^", 3},
234         {0, NULL}
235 };
236
237 static size_t weird_pull(char **inbuf, size_t *inbytesleft,
238                          char **outbuf, size_t *outbytesleft)
239 {
240         while (*inbytesleft >= 1 && *outbytesleft >= 2) {
241                 int i;
242                 int done = 0;
243                 for (i=0;weird_table[i].from;i++) {
244                         if (strncmp((*inbuf), 
245                                     weird_table[i].to, 
246                                     weird_table[i].len) == 0) {
247                                 if (*inbytesleft < weird_table[i].len) {
248                                         DEBUG(0,("ERROR: truncated weird string\n"));
249                                         /* smb_panic("weird_pull"); */
250
251                                 } else {
252                                         (*outbuf)[0] = weird_table[i].from;
253                                         (*outbuf)[1] = 0;
254                                         (*inbytesleft)  -= weird_table[i].len;
255                                         (*outbytesleft) -= 2;
256                                         (*inbuf)  += weird_table[i].len;
257                                         (*outbuf) += 2;
258                                         done = 1;
259                                         break;
260                                 }
261                         }
262                 }
263                 if (done) continue;
264                 (*outbuf)[0] = (*inbuf)[0];
265                 (*outbuf)[1] = 0;
266                 (*inbytesleft)  -= 1;
267                 (*outbytesleft) -= 2;
268                 (*inbuf)  += 1;
269                 (*outbuf) += 2;
270         }
271
272         if (*inbytesleft > 0) {
273                 errno = E2BIG;
274                 return -1;
275         }
276         
277         return 0;
278 }
279
280 static size_t weird_push(char **inbuf, size_t *inbytesleft,
281                          char **outbuf, size_t *outbytesleft)
282 {
283         int ir_count=0;
284
285         while (*inbytesleft >= 2 && *outbytesleft >= 1) {
286                 int i;
287                 int done=0;
288                 for (i=0;weird_table[i].from;i++) {
289                         if ((*inbuf)[0] == weird_table[i].from &&
290                             (*inbuf)[1] == 0) {
291                                 if (*outbytesleft < weird_table[i].len) {
292                                         DEBUG(0,("No room for weird character\n"));
293                                         /* smb_panic("weird_push"); */
294                                 } else {
295                                         memcpy(*outbuf, weird_table[i].to, 
296                                                weird_table[i].len);
297                                         (*inbytesleft)  -= 2;
298                                         (*outbytesleft) -= weird_table[i].len;
299                                         (*inbuf)  += 2;
300                                         (*outbuf) += weird_table[i].len;
301                                         done = 1;
302                                         break;
303                                 }
304                         }
305                 }
306                 if (done) continue;
307
308                 (*outbuf)[0] = (*inbuf)[0];
309                 if ((*inbuf)[1]) ir_count++;
310                 (*inbytesleft)  -= 2;
311                 (*outbytesleft) -= 1;
312                 (*inbuf)  += 2;
313                 (*outbuf) += 1;
314         }
315
316         if (*inbytesleft == 1) {
317                 errno = EINVAL;
318                 return -1;
319         }
320
321         if (*inbytesleft > 1) {
322                 errno = E2BIG;
323                 return -1;
324         }
325         
326         return ir_count;
327 }
328
329 static size_t iconv_copy(char **inbuf, size_t *inbytesleft,
330                          char **outbuf, size_t *outbytesleft)
331 {
332         int n;
333
334         n = MIN(*inbytesleft, *outbytesleft);
335
336         memmove(*outbuf, *inbuf, n);
337
338         (*inbytesleft) -= n;
339         (*outbytesleft) -= n;
340         (*inbuf) += n;
341         (*outbuf) += n;
342
343         if (*inbytesleft > 0) {
344                 errno = E2BIG;
345                 return -1;
346         }
347
348         return 0;
349 }