driver/util.c (620 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ #include <string.h> #include <errno.h> #include <stdio.h> #include "util.h" #include "handles.h" #include "error.h" BOOL wstr2bool(wstr_st *val) { /*INDENT-OFF*/ switch (val->cnt) { case /*""*/0: return FALSE; case /*0*/1: return ! EQ_CASE_WSTR(val, &MK_WSTR("0")); case /*no*/2: return ! EQ_CASE_WSTR(val, &MK_WSTR("no")); case /*off*/3: return ! EQ_CASE_WSTR(val, &MK_WSTR("off")); case /*false*/5: return ! EQ_CASE_WSTR(val, &MK_WSTR("false")); } /*INDENT-ON*/ return TRUE; } int str2ubigint(void *val, BOOL wide, SQLUBIGINT *out, BOOL strict) { SQLUBIGINT res, digit; size_t i, cnt; SQLWCHAR *wstr; SQLCHAR *cstr; static const SQLUBIGINT max_div_10 = ULLONG_MAX / 10ULL; assert(sizeof(SQLUBIGINT) == sizeof(unsigned long long)); if (wide) { wstr = ((wstr_st *)val)->str; cnt = ((wstr_st *)val)->cnt; } else { cstr = ((cstr_st *)val)->str; cnt = ((cstr_st *)val)->cnt; } if (cnt < 1) { errno = EINVAL; return -1; } digit = wide ? wstr[0] : cstr[0]; i = (digit == '+') ? 1 : 0; /* L'+' =(ubigint)= '+' */ for (res = 0; i < cnt; i ++) { digit = wide ? wstr[i] - L'0' : cstr[i] - '0'; /* is it a number? */ if (digit < 0 || 9 < digit) { if (strict) { errno = EINVAL; return -1; } else { break; } } assert(sizeof(SQLUBIGINT) == sizeof(uint64_t)); if (i < ESODBC_PRECISION_UINT64 - 1) { res *= 10ULL; res += digit; } else { /* would it overflow? */ if (max_div_10 < res) { errno = ERANGE; return -1; } else { res *= 10ULL; } if (ULLONG_MAX - res < digit) { errno = ERANGE; return -1; } else { res += digit; } } } if (cnt <= 1 && digit == '+') { /* prevent '+' or smth like '+a..' from returning a valid result */ return -1; } *out = res; assert(i <= INT_MAX); /* long will take less than INT_MAX digits */ return (long)i; } int str2bigint(void *val, BOOL wide, SQLBIGINT *out, BOOL strict) { SQLUBIGINT ull; /* unsigned long long */ size_t i; BOOL negative; int ret; cstr_st cstr; wstr_st wstr; if (wide) { wstr = *(wstr_st *)val; i = wstr.cnt; } else { cstr = *(cstr_st *)val; i = cstr.cnt; } if (i < 1) { errno = EINVAL; return -1; } else { switch (wide ? wstr.str[0] : cstr.str[0]) { case '-': /* L'-' =(size_t)= '-' */ negative = TRUE; i = 1; break; case '+': /* L'+' =(size_t)= '+' */ negative = FALSE; i = 1; break; default: negative = FALSE; i = 0; } } if (wide) { wstr.str += i; wstr.cnt -= i; ret = str2ubigint(&wstr, wide, &ull, strict); } else { cstr.str += i; cstr.cnt -= i; ret = str2ubigint(&cstr, wide, &ull, strict); } if (ret < 0) { /* str2ubigint(,strict) won't return 0 */ return -1; } else if (ret == 0 && i) { /* +/- only is NaN */ return -1; } if (negative) { if ((SQLUBIGINT)LLONG_MIN < ull) { errno = ERANGE; return -1; /* underflow */ } else { *out = -(SQLBIGINT)ull; } } else { if ((SQLUBIGINT)LLONG_MAX < ull) { errno = ERANGE; return -1; /* overflow */ } else { *out = (SQLBIGINT)ull; } } return ret + (int)i; } int str2double(void *val, BOOL wide, SQLDOUBLE *dbl, BOOL strict) { wstr_st wstr; cstr_st cstr; wchar_t *endwptr; char *endptr; size_t digits; errno = 0; if (wide) { wstr = *(wstr_st *)val; if (wstr.str[wstr.cnt]) { // TODO: simple quick parser instead of scanf if (_snwscanf(wstr.str, wstr.cnt, L"%le", (double *)dbl) != 1) { return -1; } digits = wstr.cnt; // TODO } else { *dbl = wcstod((wchar_t *)wstr.str, &endwptr); digits = endwptr - wstr.str; if (errno || (strict && wstr.cnt != digits)) { return -1; } } } else { cstr = *(cstr_st *)val; if (cstr.str[cstr.cnt]) { if (_snscanf(cstr.str, cstr.cnt, "%le", (double *)dbl) != 1) { return -1; } digits = cstr.cnt; } else { *dbl = strtod((char *)cstr.str, &endptr); digits = endptr - cstr.str; if (errno || (strict && cstr.cnt != digits)) { return -1; } } } assert(digits <= INT_MAX); return (int)digits; } size_t i64tot(int64_t i64, void *buff, BOOL wide) { if (wide) { _i64tow((int64_t)i64, buff, /*radix*/10); /* TODO: find/write a function that returns len of conversion? */ return wcslen(buff); } else { _i64toa((int64_t)i64, buff, /*radix*/10); return strlen(buff); } } size_t ui64tot(uint64_t ui64, void *buff, BOOL wide) { if (wide) { _ui64tow((uint64_t)ui64, buff, /*radix*/10); return wcslen(buff); } else { _ui64toa((uint64_t)ui64, buff, /*radix*/10); return strlen(buff); } } /* * Trims leading WS of a wide string of 'chars' length. * 0-terminator should not be counted (as it's a non-WS). */ void wltrim_ws(wstr_st *wstr) { size_t cnt = wstr->cnt; SQLWCHAR *wcrr = wstr->str; SQLWCHAR *wend = wcrr + cnt; /* left trim */ while (wcrr < wend && iswspace(*wcrr)) { wcrr ++; cnt --; } *wstr = (wstr_st) { wcrr, cnt }; } /* * Trims leading and trailing WS of a wide string of 'chars' length. * 0-terminator should not be counted (as it's a non-WS). */ void wrtrim_ws(wstr_st *wstr) { size_t cnt = wstr->cnt; SQLWCHAR *wcrr = wstr->str; /* right trim */ while (cnt && iswspace(wcrr[cnt - 1])) { cnt --; } *wstr = (wstr_st) { wcrr, cnt }; } void trim_ws(cstr_st *cstr) { size_t cnt = cstr->cnt; SQLCHAR *crr = cstr->str; SQLCHAR *end = crr + cnt; /* right trim */ while (crr < end && isspace(*crr)) { crr ++; cnt --; } /* left trim */ while (cnt && isspace(crr[cnt - 1])) { cnt --; } *cstr = (cstr_st) { crr, cnt }; } BOOL wtrim_at(wstr_st *wstr, SQLWCHAR wchar) { SQLWCHAR *pos, *end; for (pos = wstr->str, end = pos + wstr->cnt; pos < end; pos ++) { if (*pos == wchar) { wstr->cnt = pos - wstr->str; return TRUE; } } return FALSE; } /* * Converts a wchar_t string to a C string for ASCII characters. * 'dst' should be at least as character-long as 'src', if 'src' is * 0-terminated, OR one character longer otherwise (for the 0-term). * 'dst' will always be 0-term'd. * Returns negative if conversion fails, OR number of converted wchars, * including/plus the 0-term. */ int TEST_API ascii_w2c(const SQLWCHAR *src, SQLCHAR *dst, size_t chars) { size_t i = 0; if (chars < 1) { return -1; } assert(chars < INT_MAX); do { if (SCHAR_MAX < src[i]) { return -((int)i + 1); } dst[i] = (SQLCHAR)src[i]; } while ((src[i] != L'\0') && (++i < chars)); if (chars <= i) { /* equiv to: (src[i] != 0) */ /* loop stopped b/c of length -> src is not 0-term'd */ dst[i] = 0; /* chars + 1 <= [dst] */ } return (int)i + 1; } /* * This is the inverse of ascii_w2c(). */ int TEST_API ascii_c2w(const SQLCHAR *src, SQLWCHAR *dst, size_t chars) { size_t i = 0; if (chars < 1) { return -1; } assert(chars < INT_MAX); do { if (src[i] < 0) { return -((int)i + 1); } dst[i] = (SQLWCHAR)src[i]; } while ((src[i] != '\0') && (++i < chars)); if (chars <= i) { /* equiv to: (src[i] != 0) */ /* loop stopped b/c of length -> src is not 0-term'd */ dst[i] = L'\0'; /* chars + 1 <= [dst] */ } return (int)i + 1; } int wmemncasecmp(const SQLWCHAR *a, const SQLWCHAR *b, size_t len) { size_t i; int diff = 0; /* if len == 0 */ for (i = 0; i < len; i ++) { diff = towlower(a[i]) - towlower(b[i]); if (diff) { break; } } //DBG("`" LWPDL "` vs `" LWPDL "` => %d (len=%zd, i=%d).", // len, a, len, b, diff, len, i); return diff; } int wszmemcmp(const SQLWCHAR *a, const SQLWCHAR *b, long count) { int diff; for (; *a && *b && count; a ++, b ++, count --) { diff = *a - *b; if (diff) { return diff; } } if (! count) { return 0; } return *a - *b; } const SQLWCHAR *wcsnstr(const SQLWCHAR *hay, size_t len, SQLWCHAR needle) { size_t i; for (i = 0; i < len; i ++) { if (hay[i] == needle) { return hay + i; } } return NULL; } /* retuns the length of a buffer to hold the escaped variant of the unescaped * given json object */ static inline size_t json_escaped_len(const char *json, size_t len) { size_t i, newlen = 0; unsigned char uchar; for (i = 0; i < len; i ++) { uchar = (unsigned char)json[i]; switch(uchar) { case '"': case '\\': /* '/' needs no quoting as per ECMA spec, even if listed on * json.org; ES/SQL uses it unescaped in cursor values. */ case '\b': case '\f': case '\n': case '\r': case '\t': newlen += /* '\' + json[i] */2; break; default: newlen += (0x20 <= uchar) ? 1 : /* \u00XX */6; // break } } return newlen; } /* * JSON-escapes a string. * If string [in]len is 0, it assumes a NTS. * If output buffer (jout) is NULL, it returns the buffer size needed for * escaping. * Returns number of used bytes in buffer (which might be less than out buffer * size (outlen), if some char needs an escaping longer than remaining space). */ size_t json_escape(const char *jin, size_t inlen, char *jout, size_t outlen) { size_t i, pos; unsigned char uchar; #define I16TOA(_x) (10 <= (_x)) ? 'A' + (_x) - 10 : '0' + (_x) if (! inlen) { inlen = strlen(jin); } if (! jout) { return json_escaped_len(jin, inlen); } for (i = 0, pos = 0; i < inlen; i ++) { /*INDENT-OFF*/ uchar = jin[i]; switch(uchar) { do { case '\b': uchar = 'b'; break; case '\f': uchar = 'f'; break; case '\n': uchar = 'n'; break; case '\r': uchar = 'r'; break; case '\t': uchar = 't'; break; } while (0); case '"': case '\\': /* '/' needs no quoting as per ECMA spec, even if listed on * json.org; ES/SQL uses it unescaped in cursor values. */ if (outlen <= pos + 1) { i = inlen; // break the for loop continue; } jout[pos ++] = '\\'; jout[pos ++] = (char)uchar; break; default: if (0x20 <= uchar) { if (outlen <= pos) { i = inlen; continue; } jout[pos ++] = uchar; } else { // case 0x00 .. 0x1F if (outlen <= pos + 5) { i = inlen; continue; } memcpy(jout + pos, JSON_ESC_GEN_PREF, sizeof(JSON_ESC_GEN_PREF) - 1); pos += sizeof(JSON_ESC_GEN_PREF) - 1; jout[pos ++] = I16TOA(uchar >> 4); jout[pos ++] = I16TOA(uchar & 0xF); } break; } /*INDENT-ON*/ } return pos; #undef I16TOA } /* * JSON-escapes a string (str), outputting the result in the same buffer. * The buffer needs to be long enough (outlen) for this operation (at * least json_escaped_len() long). * If string [in]len is 0, it assumes a NTS. * Returns number of used bytes in buffer (which might be less than out buffer * size (outlen), if some char needs an escaping longer than remaining space). */ size_t json_escape_overlapping(char *str, size_t inlen, size_t outlen) { char *jin; /* move the string to the end of the buffer */ jin = str + (outlen - inlen); memcpy(jin, str, inlen); return json_escape(jin, inlen, str, outlen); } /* Note: for input/output size indication (avail/usedp), some functions * require character count (eg. SQLGetDiagRec, SQLDescribeCol), some others * bytes length (eg. SQLGetInfo, SQLGetDiagField, SQLGetConnectAttr, * EsSQLColAttributeW). */ /* * Copy a WSTR back to application; typically with non-SQLFetch() calls. * The WSTR must not count the 0-term, but must include it. * The function checks against the correct size of available bytes, copies the * wstr according to avaialble space and indicates the available bytes to copy * back into provided buffer (if not NULL). */ SQLRETURN TEST_API write_wstr(SQLHANDLE hnd, SQLWCHAR *dest, wstr_st *src, SQLSMALLINT /*B*/avail, SQLSMALLINT /*B*/*usedp) { size_t wide_avail; DBGH(hnd, "copying %zd wchars (`" LWPDL "`) into buffer @0x%p, of %dB " "len; out-len @0x%p.", src->cnt, LWSTR(src), dest, avail, usedp); /* cnt must not count the 0-term (XXX: ever need to copy 0s?) */ assert(src->cnt <= 0 || src->str[src->cnt - 1] != L'\0'); assert(src->cnt <= 0 || src->str[src->cnt] == L'\0'); if (usedp) { /* how many bytes are available to return (not how many would be * written into the buffer (which could be less)); * it excludes the 0-term .*/ *usedp = (SQLSMALLINT)(src->cnt * sizeof(src->str[0])); } else { INFOH(hnd, "NULL required-space-buffer provided."); } if (dest) { /* needs to be multiple of SQLWCHAR units (2 on Win) */ if (avail < 0 || avail % sizeof(SQLWCHAR)) { ERRH(hnd, "invalid buffer length provided: %hd.", avail); RET_HDIAGS(hnd, SQL_STATE_HY090); } else { wide_avail = avail/sizeof(SQLWCHAR); } /* '=' (in <=), since src->cnt doesn't count the \0 */ if (wide_avail <= src->cnt) { if (0 < wide_avail) { wcsncpy(dest, src->str, wide_avail - /* 0-term */1); dest[wide_avail - 1] = L'\0'; } INFOH(hnd, "not enough buffer size to write required string (plus " "terminator): `" LWPDL "` [%zu]; available: %zu.", LWSTR(src), src->cnt, wide_avail); RET_HDIAGS(hnd, SQL_STATE_01004); } else { assert(src->str); wcsncpy(dest, src->str, src->cnt + /* 0-term */1); } } else { /* only return how large of a buffer we need */ INFOH(hnd, "NULL out buff."); } return SQL_SUCCESS; } cstr_st TEST_API *wstr_to_utf8(wstr_st *src, cstr_st *dst) { int len; size_t cnt; void *addr; BOOL nts; /* is the \0 present and counted in source string? */ if (0 < src->cnt) { nts = !src->str[src->cnt - 1]; /* eval the needed space for conversion */ len = U16WC_TO_MBU8(src->str, src->cnt, NULL, 0); if (! len) { ERRN("failed to eval UTF-8 conversion space necessary for [%zu] " "`" LWPDL "`.", src->cnt, LWSTR(src)); return NULL; } } else { nts = FALSE; len = 0; } assert(0 <= len); /* explicitely allocate the \0 if not present&counted */ cnt = len + /*0-term?*/!nts; if (! dst) { /* if null destination, allocate that as well */ cnt += sizeof(cstr_st); } if (! (addr = malloc(cnt))) { ERRN("OOM for size: %zuB.", cnt); return NULL; } if (! dst) { dst = (cstr_st *)addr; dst->str = (uint8_t *)addr + sizeof(cstr_st); } else { dst->str = (SQLCHAR *)addr; } if (0 < src->cnt) { /* convert the string */ len = U16WC_TO_MBU8(src->str, src->cnt, dst->str, len); if (! len) { /* should not happen, since a first scan already happened */ ERRN("failed to UTF-8 convert `" LWPDL "`.", LWSTR(src)); free(addr); return NULL; } } if (! nts) { dst->str[len] = 0; } dst->cnt = len; return dst; } wstr_st TEST_API *utf8_to_wstr(cstr_st *src, wstr_st *dst) { int len; size_t cnt; void *addr; BOOL nts; /* is the \0 present and counted in source string? */ if (0 < src->cnt) { nts = !src->str[src->cnt - 1]; /* eval the needed space for conversion */ len = U8MB_TO_U16WC(src->str, src->cnt, NULL, 0); if (! len) { ERRN("failed to eval UTF-16 conversion space necessary for [%zu] " "`" LCPDL "`.", src->cnt, LCSTR(src)); return NULL; } } else { nts = FALSE; len = 0; } assert(0 <= len); /* explicitely allocate the \0 if not present&counted */ cnt = len + /*0-term?*/!nts; if (! dst) { /* if null destination, allocate that as well */ cnt += sizeof(wstr_st); } if (! (addr = malloc(cnt * sizeof(SQLWCHAR)))) { ERRN("OOM for size: %zuB.", cnt); return NULL; } if (! dst) { dst = (wstr_st *)addr; dst->str = (SQLWCHAR *)((uint8_t *)addr + sizeof(wstr_st)); } else { dst->str = (SQLWCHAR *)addr; } if (0 < src->cnt) { /* convert the string */ len = U8MB_TO_U16WC(src->str, src->cnt, dst->str, len); if (! len) { /* should not happen, since a first scan already happened */ ERRN("failed to UTF-16 convert `" LCPDL "`.", LCSTR(src)); free(addr); return NULL; } } if (! nts) { dst->str[len] = 0; } dst->cnt = len; return dst; } /* Escape `%`, `_`, `\` characters in 'src'. * If not 'force'-d, the escaping will stop on detection of pre-existing * escaping(*), OR if the chars to be escaped are stand-alone. * (*): invalid/incomplete escaping sequences - `\\\` - are still considered * as containing escaping. * Returns: TRUE, if escaping has been applied */ BOOL TEST_API metadata_id_escape(wstr_st *src, wstr_st *dst, BOOL force) { size_t i, j; SQLWCHAR crr, prev; BOOL ret; ret = FALSE; prev = 0; for (i = 0, j = 0; i <= src->cnt; i ++) { crr = src->str[i]; switch (crr) { case '%': case L'_': case MK_WPTR(ESODBC_CHAR_ESCAPE): /* is current char already escaped OR is it stand-alone? */ if (prev == MK_WPTR(ESODBC_CHAR_ESCAPE) || src->cnt == 1) { if (! force) { wmemcpy(dst->str, src->str, src->cnt); dst->cnt = src->cnt; return FALSE; } } dst->str[j ++] = MK_WPTR(ESODBC_CHAR_ESCAPE); ret = TRUE; /* no break */ default: dst->str[j ++] = crr; } prev = crr; } dst->cnt = 0 < j ? j - 1 : 0; return ret; } /* Simple hex printing of a cstr_st object. * Returns (thread local static) printed buffer, always 0-term'd. */ char *cstr_hex_dump(const cstr_st *buff) { # ifndef WITH_EXTENDED_BUFF_LOG static thread_local char dest[ESODBC_LOG_BUF_SIZE]; const size_t dest_sz = sizeof(dest); # else /* !WITH_EXTENDED_BUFF_LOG */ static char *dest = NULL; static esodbc_mutex_lt dest_mux = ESODBC_MUX_SINIT; const size_t dest_sz = ESODBC_EXT_LOG_BUF_SIZE; # endif /* !WITH_EXTENDED_BUFF_LOG */ char *to, *from; char *to_end, *from_end; int n; # ifdef WITH_EXTENDED_BUFF_LOG ESODBC_MUX_LOCK(&dest_mux); if (! buff) { /* free resorces */ if (dest) { free(dest); dest = NULL; } ESODBC_MUX_DEL(&dest_mux); goto end; } else if (! dest) { if (! (dest = malloc(dest_sz))) { ERRN("OOM for %zd bytes.", dest_sz); ESODBC_MUX_UNLOCK(&dest_mux); return "<OOM>"; } } # endif /* WITH_EXTENDED_BUFF_LOG */ to = dest; to_end = dest + dest_sz; from = buff->str; from_end = buff->str + buff->cnt; int i = 0; while (to < to_end && from < from_end) { n = sprintf(to, "%.2X", (uint8_t)*from ++); if (n < 0) { ERRN("failed to print serialized CBOR object"); return NULL; } to += (size_t)n; } /* add the 0-terminator */ if (to < to_end) { /* still space for it? */ *to ++ = '\0'; } else { /* == */ dest[dest_sz - 1] = '\0'; /* overwrite last position */ } # ifdef WITH_EXTENDED_BUFF_LOG end: ESODBC_MUX_UNLOCK(&dest_mux); # endif /* WITH_EXTENDED_BUFF_LOG */ return dest; } /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */