| /* |
| * Copyright (C) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com> |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, |
| * with or without modification, are permitted provided |
| * that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the |
| * following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * Neither the name of the copyright holder nor the names |
| * of any other contributors may be used to endorse or |
| * promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
| * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
| * OF SUCH DAMAGE. |
| */ |
| |
| /* Character encoding wrappers. */ |
| |
| #include "libssh2_priv.h" |
| #include "libssh2_ccsid.h" |
| |
| #include <qtqiconv.h> |
| #include <iconv.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| |
| |
| #define CCSID_UTF8 1208 |
| #define CCSID_UTF16BE 13488 |
| #define STRING_GRANULE 256 |
| #define MAX_CHAR_SIZE 4 |
| |
| #define OFFSET_OF(t, f) ((size_t) ((char *) &((t *) 0)->f - (char *) 0)) |
| |
| |
| struct _libssh2_string_cache { |
| libssh2_string_cache * next; |
| char string[1]; |
| }; |
| |
| |
| static const QtqCode_T utf8code = { CCSID_UTF8 }; |
| |
| |
| static ssize_t |
| terminator_size(unsigned short ccsid) |
| { |
| QtqCode_T outcode; |
| iconv_t cd; |
| char *inp; |
| char *outp; |
| size_t ilen; |
| size_t olen; |
| char buf[MAX_CHAR_SIZE]; |
| |
| /* Return the null-terminator size for the given CCSID. */ |
| |
| /* Fast check usual CCSIDs. */ |
| switch (ccsid) { |
| case CCSID_UTF8: |
| case 0: /* Job CCSID is SBCS EBCDIC. */ |
| return 1; |
| case CCSID_UTF16BE: |
| return 2; |
| } |
| |
| /* Convert an UTF-8 NUL to the target CCSID: use the converted size as |
| result. */ |
| memset((void *) &outcode, 0, sizeof outcode); |
| outcode.CCSID = ccsid; |
| cd = QtqIconvOpen(&outcode, (QtqCode_T *) &utf8code); |
| if (cd.return_value == -1) |
| return -1; |
| inp = ""; |
| ilen = 1; |
| outp = buf; |
| olen = sizeof buf; |
| iconv(cd, &inp, &ilen, &outp, &olen); |
| iconv_close(cd); |
| olen = sizeof buf - olen; |
| return olen? olen: -1; |
| } |
| |
| static char * |
| convert_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache, |
| unsigned short outccsid, unsigned short inccsid, |
| const char *instring, ssize_t inlen, size_t *outlen) |
| { |
| char *inp; |
| char *outp; |
| size_t olen; |
| size_t ilen; |
| size_t buflen; |
| size_t curlen; |
| ssize_t termsize; |
| int i; |
| char *dst; |
| libssh2_string_cache *outstring; |
| QtqCode_T incode; |
| QtqCode_T outcode; |
| iconv_t cd; |
| |
| if (!instring) { |
| if (outlen) |
| *outlen = 0; |
| return NULL; |
| } |
| if (outlen) |
| *outlen = -1; |
| if (!session || !cache) |
| return NULL; |
| |
| /* Get terminator size. */ |
| termsize = terminator_size(outccsid); |
| if (termsize < 0) |
| return NULL; |
| |
| /* Prepare conversion parameters. */ |
| memset((void *) &incode, 0, sizeof incode); |
| memset((void *) &outcode, 0, sizeof outcode); |
| incode.CCSID = inccsid; |
| outcode.CCSID = outccsid; |
| curlen = OFFSET_OF(libssh2_string_cache, string); |
| inp = (char *) instring; |
| ilen = inlen; |
| buflen = inlen + curlen; |
| if (inlen < 0) { |
| incode.length_option = 1; |
| buflen = STRING_GRANULE; |
| ilen = 0; |
| } |
| |
| /* Allocate output string buffer and open conversion descriptor. */ |
| dst = LIBSSH2_ALLOC(session, buflen + termsize); |
| if (!dst) |
| return NULL; |
| cd = QtqIconvOpen(&outcode, &incode); |
| if (cd.return_value == -1) { |
| LIBSSH2_FREE(session, (char *) dst); |
| return NULL; |
| } |
| |
| /* Convert string. */ |
| for (;;) { |
| outp = dst + curlen; |
| olen = buflen - curlen; |
| i = iconv(cd, &inp, &ilen, &outp, &olen); |
| if (inlen < 0 && olen == buflen - curlen) { |
| /* Special case: converted 0-length (sub)strings do not store the |
| terminator. */ |
| if (termsize) { |
| memset(outp, 0, termsize); |
| olen -= termsize; |
| } |
| } |
| curlen = buflen - olen; |
| if (i >= 0 || errno != E2BIG) |
| break; |
| /* Must expand buffer. */ |
| buflen += STRING_GRANULE; |
| outp = LIBSSH2_REALLOC(session, dst, buflen + termsize); |
| if (!outp) |
| break; |
| dst = outp; |
| } |
| |
| iconv_close(cd); |
| |
| /* Check for error. */ |
| if (i < 0 || !outp) { |
| LIBSSH2_FREE(session, dst); |
| return NULL; |
| } |
| |
| /* Process terminator. */ |
| if (inlen < 0) |
| curlen -= termsize; |
| else if (termsize) |
| memset(dst + curlen, 0, termsize); |
| |
| /* Shorten buffer if possible. */ |
| if (curlen < buflen) |
| dst = LIBSSH2_REALLOC(session, dst, curlen + termsize); |
| |
| /* Link to cache. */ |
| outstring = (libssh2_string_cache *) dst; |
| outstring->next = *cache; |
| *cache = outstring; |
| |
| /* Return length if required. */ |
| if (outlen) |
| *outlen = curlen - OFFSET_OF(libssh2_string_cache, string); |
| |
| return outstring->string; |
| } |
| |
| LIBSSH2_API char * |
| libssh2_from_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache, |
| unsigned short ccsid, const char *string, ssize_t inlen, |
| size_t *outlen) |
| { |
| return convert_ccsid(session, cache, |
| CCSID_UTF8, ccsid, string, inlen, outlen); |
| } |
| |
| LIBSSH2_API char * |
| libssh2_to_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache, |
| unsigned short ccsid, const char *string, ssize_t inlen, |
| size_t *outlen) |
| { |
| return convert_ccsid(session, cache, |
| ccsid, CCSID_UTF8, string, inlen, outlen); |
| } |
| |
| LIBSSH2_API void |
| libssh2_release_string_cache(LIBSSH2_SESSION *session, |
| libssh2_string_cache **cache) |
| { |
| libssh2_string_cache *p; |
| |
| if (session && cache) |
| while ((p = *cache)) { |
| *cache = p->next; |
| LIBSSH2_FREE(session, (char *) p); |
| } |
| } |
| |
| /* vim: set expandtab ts=4 sw=4: */ |