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