Move charset library to top level.
[samba.git] / lib / util / charset / charcnv.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Character set conversion Extensions
4    Copyright (C) Igor Vergeichik <iverg@mail.ru> 2001
5    Copyright (C) Andrew Tridgell 2001
6    Copyright (C) Simo Sorce 2001
7    Copyright (C) Jelmer Vernooij 2007
8    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
22 */
23 #include "includes.h"
24 #include "system/iconv.h"
25 #include "param/param.h"
26
27 /**
28  * @file
29  *
30  * @brief Character-set conversion routines built on our iconv.
31  * 
32  * @note Samba's internal character set (at least in the 3.0 series)
33  * is always the same as the one for the Unix filesystem.  It is
34  * <b>not</b> necessarily UTF-8 and may be different on machines that
35  * need i18n filenames to be compatible with Unix software.  It does
36  * have to be a superset of ASCII.  All multibyte sequences must start
37  * with a byte with the high bit set.
38  *
39  * @sa lib/iconv.c
40  */
41
42 struct smb_iconv_convenience {
43         const char *unix_charset;
44         const char *dos_charset;
45         bool native_iconv;
46         smb_iconv_t conv_handles[NUM_CHARSETS][NUM_CHARSETS];
47 };
48
49
50 /**
51  * Return the name of a charset to give to iconv().
52  **/
53 static const char *charset_name(struct smb_iconv_convenience *ic, charset_t ch)
54 {
55         switch (ch) {
56         case CH_UTF16: return "UTF-16LE";
57         case CH_UNIX: return ic->unix_charset;
58         case CH_DOS: return ic->dos_charset;
59         case CH_UTF8: return "UTF8";
60         case CH_UTF16BE: return "UTF-16BE";
61         default:
62         return "ASCII";
63         }
64 }
65
66 /**
67  re-initialize iconv conversion descriptors
68 **/
69 static int close_iconv(struct smb_iconv_convenience *data)
70 {
71         unsigned c1, c2;
72         for (c1=0;c1<NUM_CHARSETS;c1++) {
73                 for (c2=0;c2<NUM_CHARSETS;c2++) {
74                         if (data->conv_handles[c1][c2] != NULL) {
75                                 if (data->conv_handles[c1][c2] != (smb_iconv_t)-1) {
76                                         smb_iconv_close(data->conv_handles[c1][c2]);
77                                 }
78                                 data->conv_handles[c1][c2] = NULL;
79                         }
80                 }
81         }
82
83         return 0;
84 }
85
86 _PUBLIC_ struct smb_iconv_convenience *smb_iconv_convenience_init(TALLOC_CTX *mem_ctx,
87                                                          const char *dos_charset,
88                                                          const char *unix_charset,
89                                                          bool native_iconv)
90 {
91         struct smb_iconv_convenience *ret = talloc_zero(mem_ctx, 
92                                         struct smb_iconv_convenience);
93
94         if (ret == NULL) {
95                 return NULL;
96         }
97
98         talloc_set_destructor(ret, close_iconv);
99
100         ret->dos_charset = talloc_strdup(ret, dos_charset);
101         ret->unix_charset = talloc_strdup(ret, unix_charset);
102         ret->native_iconv = native_iconv;
103
104         return ret;
105 }
106
107 /*
108   on-demand initialisation of conversion handles
109 */
110 static smb_iconv_t get_conv_handle(struct smb_iconv_convenience *ic,
111                                    charset_t from, charset_t to)
112 {
113         const char *n1, *n2;
114         static bool initialised;
115
116         if (initialised == false) {
117                 initialised = true;
118                 
119 #ifdef LC_ALL
120                 /* we set back the locale to C to get ASCII-compatible
121                    toupper/lower functions.  For now we do not need
122                    any other POSIX localisations anyway. When we
123                    should really need localized string functions one
124                    day we need to write our own ascii_tolower etc.
125                 */
126                 setlocale(LC_ALL, "C");
127 #endif
128         }
129
130         if (ic->conv_handles[from][to]) {
131                 return ic->conv_handles[from][to];
132         }
133
134         n1 = charset_name(ic, from);
135         n2 = charset_name(ic, to);
136
137         ic->conv_handles[from][to] = smb_iconv_open_ex(ic, n2, n1, 
138                                                        ic->native_iconv);
139         
140         if (ic->conv_handles[from][to] == (smb_iconv_t)-1) {
141                 if ((from == CH_DOS || to == CH_DOS) &&
142                     strcasecmp(charset_name(ic, CH_DOS), "ASCII") != 0) {
143                         DEBUG(0,("dos charset '%s' unavailable - using ASCII\n",
144                                  charset_name(ic, CH_DOS)));
145                         ic->dos_charset = "ASCII";
146
147                         n1 = charset_name(ic, from);
148                         n2 = charset_name(ic, to);
149                         
150                         ic->conv_handles[from][to] = 
151                                 smb_iconv_open_ex(ic, n2, n1, ic->native_iconv);
152                 }
153         }
154
155         return ic->conv_handles[from][to];
156 }
157
158
159 /**
160  * Convert string from one encoding to another, making error checking etc
161  *
162  * @param src pointer to source string (multibyte or singlebyte)
163  * @param srclen length of the source string in bytes
164  * @param dest pointer to destination string (multibyte or singlebyte)
165  * @param destlen maximal length allowed for string
166  * @returns the number of bytes occupied in the destination
167  **/
168 _PUBLIC_ ssize_t convert_string(struct smb_iconv_convenience *ic,
169                                 charset_t from, charset_t to,
170                                 void const *src, size_t srclen, 
171                                 void *dest, size_t destlen)
172 {
173         size_t i_len, o_len;
174         size_t retval;
175         const char* inbuf = (const char*)src;
176         char* outbuf = (char*)dest;
177         smb_iconv_t descriptor;
178
179         if (srclen == (size_t)-1)
180                 srclen = strlen(inbuf)+1;
181
182         descriptor = get_conv_handle(ic, from, to);
183
184         if (descriptor == (smb_iconv_t)-1 || descriptor == (smb_iconv_t)0) {
185                 /* conversion not supported, use as is */
186                 size_t len = MIN(srclen,destlen);
187                 memcpy(dest,src,len);
188                 return len;
189         }
190
191         i_len=srclen;
192         o_len=destlen;
193         retval = smb_iconv(descriptor,  &inbuf, &i_len, &outbuf, &o_len);
194         if(retval==(size_t)-1) {
195                 const char *reason;
196                 switch(errno) {
197                         case EINVAL:
198                                 reason="Incomplete multibyte sequence";
199                                 return -1;
200                         case E2BIG:
201                                 reason="No more room"; 
202                                 if (from == CH_UNIX) {
203                                         DEBUG(0,("E2BIG: convert_string(%s,%s): srclen=%d destlen=%d - '%s'\n",
204                                                  charset_name(ic, from), charset_name(ic, to),
205                                                  (int)srclen, (int)destlen, 
206                                                  (const char *)src));
207                                 } else {
208                                         DEBUG(0,("E2BIG: convert_string(%s,%s): srclen=%d destlen=%d\n",
209                                                  charset_name(ic, from), charset_name(ic, to),
210                                                  (int)srclen, (int)destlen));
211                                 }
212                                return -1;
213                         case EILSEQ:
214                                reason="Illegal multibyte sequence";
215                                return -1;
216                 }
217                 /* smb_panic(reason); */
218         }
219         return destlen-o_len;
220 }
221         
222 _PUBLIC_ ssize_t convert_string_talloc_descriptor(TALLOC_CTX *ctx, smb_iconv_t descriptor, void const *src, size_t srclen, void **dest)
223 {
224         size_t i_len, o_len, destlen;
225         size_t retval;
226         const char *inbuf = (const char *)src;
227         char *outbuf, *ob;
228
229         *dest = NULL;
230
231         /* it is _very_ rare that a conversion increases the size by
232            more than 3x */
233         destlen = srclen;
234         outbuf = NULL;
235 convert:
236         destlen = 2 + (destlen*3);
237         ob = talloc_realloc(ctx, outbuf, char, destlen);
238         if (!ob) {
239                 DEBUG(0, ("convert_string_talloc: realloc failed!\n"));
240                 talloc_free(outbuf);
241                 return (size_t)-1;
242         } else {
243                 outbuf = ob;
244         }
245
246         /* we give iconv 2 less bytes to allow us to terminate at the
247            end */
248         i_len = srclen;
249         o_len = destlen-2;
250         retval = smb_iconv(descriptor,
251                            &inbuf, &i_len,
252                            &outbuf, &o_len);
253         if(retval == (size_t)-1)                {
254                 const char *reason="unknown error";
255                 switch(errno) {
256                         case EINVAL:
257                                 reason="Incomplete multibyte sequence";
258                                 break;
259                         case E2BIG:
260                                 goto convert;           
261                         case EILSEQ:
262                                 reason="Illegal multibyte sequence";
263                                 break;
264                 }
265                 DEBUG(0,("Conversion error: %s(%s)\n",reason,inbuf));
266                 talloc_free(ob);
267                 return (size_t)-1;
268         }
269         
270         destlen = (destlen-2) - o_len;
271
272         /* guarantee null termination in all charsets */
273         SSVAL(ob, destlen, 0);
274
275         *dest = ob;
276
277         return destlen;
278 }
279
280 /**
281  * Convert between character sets, allocating a new buffer using talloc for the result.
282  *
283  * @param srclen length of source buffer.
284  * @param dest always set at least to NULL
285  * @note -1 is not accepted for srclen.
286  *
287  * @returns Size in bytes of the converted string; or -1 in case of error.
288  **/
289
290 _PUBLIC_ ssize_t convert_string_talloc(TALLOC_CTX *ctx, 
291                                        struct smb_iconv_convenience *ic, 
292                                        charset_t from, charset_t to, 
293                                        void const *src, size_t srclen, 
294                                        void **dest)
295 {
296         smb_iconv_t descriptor;
297
298         *dest = NULL;
299
300         if (src == NULL || srclen == (size_t)-1 || srclen == 0)
301                 return (size_t)-1;
302
303         descriptor = get_conv_handle(ic, from, to);
304
305         if (descriptor == (smb_iconv_t)-1 || descriptor == (smb_iconv_t)0) {
306                 /* conversion not supported, return -1*/
307                 DEBUG(3, ("convert_string_talloc: conversion from %s to %s not supported!\n",
308                           charset_name(ic, from), 
309                           charset_name(ic, to)));
310                 return -1;
311         }
312
313         return convert_string_talloc_descriptor(ctx, descriptor, src, srclen, dest);
314 }
315
316 /**
317  * Copy a string from a char* unix src to a dos codepage string destination.
318  *
319  * @return the number of bytes occupied by the string in the destination.
320  *
321  * @param flags can include
322  * <dl>
323  * <dt>STR_TERMINATE</dt> <dd>means include the null termination</dd>
324  * <dt>STR_UPPER</dt> <dd>means uppercase in the destination</dd>
325  * </dl>
326  *
327  * @param dest_len the maximum length in bytes allowed in the
328  * destination.  If @p dest_len is -1 then no maximum is used.
329  **/
330 static ssize_t push_ascii(struct smb_iconv_convenience *ic, 
331                           void *dest, const char *src, size_t dest_len, int flags)
332 {
333         size_t src_len;
334         ssize_t ret;
335
336         if (flags & STR_UPPER) {
337                 char *tmpbuf = strupper_talloc(NULL, src);
338                 if (tmpbuf == NULL) {
339                         return -1;
340                 }
341                 ret = push_ascii(ic, dest, tmpbuf, dest_len, flags & ~STR_UPPER);
342                 talloc_free(tmpbuf);
343                 return ret;
344         }
345
346         src_len = strlen(src);
347
348         if (flags & (STR_TERMINATE | STR_TERMINATE_ASCII))
349                 src_len++;
350
351         return convert_string(ic, CH_UNIX, CH_DOS, src, src_len, dest, dest_len);
352 }
353
354 /**
355  * Copy a string from a unix char* src to an ASCII destination,
356  * allocating a buffer using talloc().
357  *
358  * @param dest always set at least to NULL 
359  *
360  * @returns The number of bytes occupied by the string in the destination
361  *         or -1 in case of error.
362  **/
363 _PUBLIC_ ssize_t push_ascii_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src)
364 {
365         size_t src_len = strlen(src)+1;
366         *dest = NULL;
367         return convert_string_talloc(ctx, ic, CH_UNIX, CH_DOS, src, src_len, (void **)dest);
368 }
369
370
371 /**
372  * Copy a string from a dos codepage source to a unix char* destination.
373  *
374  * The resulting string in "dest" is always null terminated.
375  *
376  * @param flags can have:
377  * <dl>
378  * <dt>STR_TERMINATE</dt>
379  * <dd>STR_TERMINATE means the string in @p src
380  * is null terminated, and src_len is ignored.</dd>
381  * </dl>
382  *
383  * @param src_len is the length of the source area in bytes.
384  * @returns the number of bytes occupied by the string in @p src.
385  **/
386 static ssize_t pull_ascii(struct smb_iconv_convenience *ic, char *dest, const void *src, size_t dest_len, size_t src_len, int flags)
387 {
388         size_t ret;
389
390         if (flags & (STR_TERMINATE | STR_TERMINATE_ASCII)) {
391                 if (src_len == (size_t)-1) {
392                         src_len = strlen((const char *)src) + 1;
393                 } else {
394                         size_t len = strnlen((const char *)src, src_len);
395                         if (len < src_len)
396                                 len++;
397                         src_len = len;
398                 }
399         }
400
401         ret = convert_string(ic, CH_DOS, CH_UNIX, src, src_len, dest, dest_len);
402
403         if (dest_len)
404                 dest[MIN(ret, dest_len-1)] = 0;
405
406         return src_len;
407 }
408
409 /**
410  * Copy a string from a char* src to a unicode destination.
411  *
412  * @returns the number of bytes occupied by the string in the destination.
413  *
414  * @param flags can have:
415  *
416  * <dl>
417  * <dt>STR_TERMINATE <dd>means include the null termination.
418  * <dt>STR_UPPER     <dd>means uppercase in the destination.
419  * <dt>STR_NOALIGN   <dd>means don't do alignment.
420  * </dl>
421  *
422  * @param dest_len is the maximum length allowed in the
423  * destination. If dest_len is -1 then no maxiumum is used.
424  **/
425 static ssize_t push_ucs2(struct smb_iconv_convenience *ic, 
426                          void *dest, const char *src, size_t dest_len, int flags)
427 {
428         size_t len=0;
429         size_t src_len = strlen(src);
430         size_t ret;
431
432         if (flags & STR_UPPER) {
433                 char *tmpbuf = strupper_talloc(NULL, src);
434                 if (tmpbuf == NULL) {
435                         return -1;
436                 }
437                 ret = push_ucs2(ic, dest, tmpbuf, dest_len, flags & ~STR_UPPER);
438                 talloc_free(tmpbuf);
439                 return ret;
440         }
441
442         if (flags & STR_TERMINATE)
443                 src_len++;
444
445         if (ucs2_align(NULL, dest, flags)) {
446                 *(char *)dest = 0;
447                 dest = (void *)((char *)dest + 1);
448                 if (dest_len) dest_len--;
449                 len++;
450         }
451
452         /* ucs2 is always a multiple of 2 bytes */
453         dest_len &= ~1;
454
455         ret = convert_string(ic, CH_UNIX, CH_UTF16, src, src_len, dest, dest_len);
456         if (ret == (size_t)-1) {
457                 return 0;
458         }
459
460         len += ret;
461
462         return len;
463 }
464
465
466 /**
467  * Copy a string from a unix char* src to a UCS2 destination,
468  * allocating a buffer using talloc().
469  *
470  * @param dest always set at least to NULL 
471  *
472  * @returns The number of bytes occupied by the string in the destination
473  *         or -1 in case of error.
474  **/
475 _PUBLIC_ ssize_t push_ucs2_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, void **dest, const char *src)
476 {
477         size_t src_len = strlen(src)+1;
478         *dest = NULL;
479         return convert_string_talloc(ctx, ic, CH_UNIX, CH_UTF16, src, src_len, dest);
480 }
481
482
483 /**
484  * Copy a string from a unix char* src to a UTF-8 destination, allocating a buffer using talloc
485  *
486  * @param dest always set at least to NULL 
487  *
488  * @returns The number of bytes occupied by the string in the destination
489  **/
490
491 _PUBLIC_ ssize_t push_utf8_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src)
492 {
493         size_t src_len = strlen(src)+1;
494         *dest = NULL;
495         return convert_string_talloc(ctx, ic, CH_UNIX, CH_UTF8, src, src_len, (void **)dest);
496 }
497
498 /**
499  Copy a string from a ucs2 source to a unix char* destination.
500  Flags can have:
501   STR_TERMINATE means the string in src is null terminated.
502   STR_NOALIGN   means don't try to align.
503  if STR_TERMINATE is set then src_len is ignored if it is -1.
504  src_len is the length of the source area in bytes
505  Return the number of bytes occupied by the string in src.
506  The resulting string in "dest" is always null terminated.
507 **/
508
509 static size_t pull_ucs2(struct smb_iconv_convenience *ic, char *dest, const void *src, size_t dest_len, size_t src_len, int flags)
510 {
511         size_t ret;
512
513         if (ucs2_align(NULL, src, flags)) {
514                 src = (const void *)((const char *)src + 1);
515                 if (src_len > 0)
516                         src_len--;
517         }
518
519         if (flags & STR_TERMINATE) {
520                 if (src_len == (size_t)-1) {
521                         src_len = utf16_len(src);
522                 } else {
523                         src_len = utf16_len_n(src, src_len);
524                 }
525         }
526
527         /* ucs2 is always a multiple of 2 bytes */
528         if (src_len != (size_t)-1)
529                 src_len &= ~1;
530         
531         ret = convert_string(ic, CH_UTF16, CH_UNIX, src, src_len, dest, dest_len);
532         if (dest_len)
533                 dest[MIN(ret, dest_len-1)] = 0;
534
535         return src_len;
536 }
537
538 /**
539  * Copy a string from a ASCII src to a unix char * destination, allocating a buffer using talloc
540  *
541  * @param dest always set at least to NULL 
542  *
543  * @returns The number of bytes occupied by the string in the destination
544  **/
545
546 _PUBLIC_ ssize_t pull_ascii_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src)
547 {
548         size_t src_len = strlen(src)+1;
549         *dest = NULL;
550         return convert_string_talloc(ctx, ic, CH_DOS, CH_UNIX, src, src_len, (void **)dest);
551 }
552
553 /**
554  * Copy a string from a UCS2 src to a unix char * destination, allocating a buffer using talloc
555  *
556  * @param dest always set at least to NULL 
557  *
558  * @returns The number of bytes occupied by the string in the destination
559  **/
560
561 _PUBLIC_ ssize_t pull_ucs2_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const void *src)
562 {
563         size_t src_len = utf16_len(src);
564         *dest = NULL;
565         return convert_string_talloc(ctx, ic, CH_UTF16, CH_UNIX, src, src_len, (void **)dest);
566 }
567
568 /**
569  * Copy a string from a UTF-8 src to a unix char * destination, allocating a buffer using talloc
570  *
571  * @param dest always set at least to NULL 
572  *
573  * @returns The number of bytes occupied by the string in the destination
574  **/
575
576 _PUBLIC_ ssize_t pull_utf8_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src)
577 {
578         size_t src_len = strlen(src)+1;
579         *dest = NULL;
580         return convert_string_talloc(ctx, ic, CH_UTF8, CH_UNIX, src, src_len, (void **)dest);
581 }
582
583 /**
584  Copy a string from a char* src to a unicode or ascii
585  dos codepage destination choosing unicode or ascii based on the 
586  flags in the SMB buffer starting at base_ptr.
587  Return the number of bytes occupied by the string in the destination.
588  flags can have:
589   STR_TERMINATE means include the null termination.
590   STR_UPPER     means uppercase in the destination.
591   STR_ASCII     use ascii even with unicode packet.
592   STR_NOALIGN   means don't do alignment.
593  dest_len is the maximum length allowed in the destination. If dest_len
594  is -1 then no maxiumum is used.
595 **/
596
597 _PUBLIC_ ssize_t push_string(struct smb_iconv_convenience *ic, 
598                              void *dest, const char *src, size_t dest_len, int flags)
599 {
600         if (flags & STR_ASCII) {
601                 return push_ascii(ic, dest, src, dest_len, flags);
602         } else if (flags & STR_UNICODE) {
603                 return push_ucs2(ic, dest, src, dest_len, flags);
604         } else {
605                 smb_panic("push_string requires either STR_ASCII or STR_UNICODE flag to be set");
606                 return -1;
607         }
608 }
609
610
611 /**
612  Copy a string from a unicode or ascii source (depending on
613  the packet flags) to a char* destination.
614  Flags can have:
615   STR_TERMINATE means the string in src is null terminated.
616   STR_UNICODE   means to force as unicode.
617   STR_ASCII     use ascii even with unicode packet.
618   STR_NOALIGN   means don't do alignment.
619  if STR_TERMINATE is set then src_len is ignored is it is -1
620  src_len is the length of the source area in bytes.
621  Return the number of bytes occupied by the string in src.
622  The resulting string in "dest" is always null terminated.
623 **/
624
625 _PUBLIC_ ssize_t pull_string(struct smb_iconv_convenience *ic,
626                              char *dest, const void *src, size_t dest_len, size_t src_len, int flags)
627 {
628         if (flags & STR_ASCII) {
629                 return pull_ascii(ic, dest, src, dest_len, src_len, flags);
630         } else if (flags & STR_UNICODE) {
631                 return pull_ucs2(ic, dest, src, dest_len, src_len, flags);
632         } else {
633                 smb_panic("pull_string requires either STR_ASCII or STR_UNICODE flag to be set");
634                 return -1;
635         }
636 }
637
638
639 /*
640   return the unicode codepoint for the next multi-byte CH_UNIX character
641   in the string
642
643   also return the number of bytes consumed (which tells the caller
644   how many bytes to skip to get to the next CH_UNIX character)
645
646   return INVALID_CODEPOINT if the next character cannot be converted
647 */
648 _PUBLIC_ codepoint_t next_codepoint(struct smb_iconv_convenience *ic, 
649                                     const char *str, size_t *size)
650 {
651         /* it cannot occupy more than 4 bytes in UTF16 format */
652         uint8_t buf[4];
653         smb_iconv_t descriptor;
654         size_t ilen_orig;
655         size_t ilen;
656         size_t olen;
657         char *outbuf;
658
659         if ((str[0] & 0x80) == 0) {
660                 *size = 1;
661                 return (codepoint_t)str[0];
662         }
663
664         /* we assume that no multi-byte character can take
665            more than 5 bytes. This is OK as we only
666            support codepoints up to 1M */
667         ilen_orig = strnlen(str, 5);
668         ilen = ilen_orig;
669
670         descriptor = get_conv_handle(ic, CH_UNIX, CH_UTF16);
671         if (descriptor == (smb_iconv_t)-1) {
672                 *size = 1;
673                 return INVALID_CODEPOINT;
674         }
675
676         /* this looks a little strange, but it is needed to cope
677            with codepoints above 64k */
678         olen = 2;
679         outbuf = (char *)buf;
680         smb_iconv(descriptor, &str, &ilen, &outbuf, &olen);
681         if (olen == 2) {
682                 olen = 4;
683                 outbuf = (char *)buf;
684                 smb_iconv(descriptor,  &str, &ilen, &outbuf, &olen);
685                 if (olen == 4) {
686                         /* we didn't convert any bytes */
687                         *size = 1;
688                         return INVALID_CODEPOINT;
689                 }
690                 olen = 4 - olen;
691         } else {
692                 olen = 2 - olen;
693         }
694
695         *size = ilen_orig - ilen;
696
697         if (olen == 2) {
698                 return (codepoint_t)SVAL(buf, 0);
699         }
700         if (olen == 4) {
701                 /* decode a 4 byte UTF16 character manually */
702                 return (codepoint_t)0x10000 + 
703                         (buf[2] | ((buf[3] & 0x3)<<8) | 
704                          (buf[0]<<10) | ((buf[1] & 0x3)<<18));
705         }
706
707         /* no other length is valid */
708         return INVALID_CODEPOINT;
709 }
710
711 /*
712   push a single codepoint into a CH_UNIX string the target string must
713   be able to hold the full character, which is guaranteed if it is at
714   least 5 bytes in size. The caller may pass less than 5 bytes if they
715   are sure the character will fit (for example, you can assume that
716   uppercase/lowercase of a character will not add more than 1 byte)
717
718   return the number of bytes occupied by the CH_UNIX character, or
719   -1 on failure
720 */
721 _PUBLIC_ ssize_t push_codepoint(struct smb_iconv_convenience *ic, 
722                                 char *str, codepoint_t c)
723 {
724         smb_iconv_t descriptor;
725         uint8_t buf[4];
726         size_t ilen, olen;
727         const char *inbuf;
728         
729         if (c < 128) {
730                 *str = c;
731                 return 1;
732         }
733
734         descriptor = get_conv_handle(ic, 
735                                      CH_UTF16, CH_UNIX);
736         if (descriptor == (smb_iconv_t)-1) {
737                 return -1;
738         }
739
740         if (c < 0x10000) {
741                 ilen = 2;
742                 olen = 5;
743                 inbuf = (char *)buf;
744                 SSVAL(buf, 0, c);
745                 smb_iconv(descriptor, &inbuf, &ilen, &str, &olen);
746                 if (ilen != 0) {
747                         return -1;
748                 }
749                 return 5 - olen;
750         }
751
752         c -= 0x10000;
753
754         buf[0] = (c>>10) & 0xFF;
755         buf[1] = (c>>18) | 0xd8;
756         buf[2] = c & 0xFF;
757         buf[3] = ((c>>8) & 0x3) | 0xdc;
758
759         ilen = 4;
760         olen = 5;
761         inbuf = (char *)buf;
762
763         smb_iconv(descriptor, &inbuf, &ilen, &str, &olen);
764         if (ilen != 0) {
765                 return -1;
766         }
767         return 5 - olen;
768 }