forked from openkylin/libssh2
253 lines
6.9 KiB
C
253 lines
6.9 KiB
C
/*
|
|
* 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: */
|