Skip to content

Commit

Permalink
[ODBC-72] Fix and testcase. Fixing the case, when while getting WCHAR
Browse files Browse the repository at this point in the history
data in parts, surrogate pair gets divided into different chunks, i.e
first SQLWCHAR of the pair falls on the end of buffer.
  • Loading branch information
lawrinn committed Mar 2, 2017
1 parent 8c998ae commit 3f3ec84
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 61 deletions.
18 changes: 11 additions & 7 deletions ma_platform_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ SQLWCHAR *MADB_ConvertToWchar(char *Ptr, SQLLEN PtrLength, Client_Charset* cc)
return WStr;
}

/* {{{ MADB_ConvertFromWChar */
/* {{{ MADB_ConvertFromWChar
Length gets number of written bytes including TN (if WstrCharLen == -1 or SQL_NTS or if WstrCharLen includes
TN in the Wstr) */
char *MADB_ConvertFromWChar(SQLWCHAR *Wstr, SQLINTEGER WstrCharLen, SQLULEN *Length/*Bytes*/, Client_Charset *cc, BOOL *Error)
{
char *AscStr;
Expand Down Expand Up @@ -142,7 +144,7 @@ char *MADB_ConvertFromWChar(SQLWCHAR *Wstr, SQLINTEGER WstrCharLen, SQLULEN *Len
}
/* }}} */


/* Required Length without or with TN(if IsNull is TRUE, or AnsiLength == -1 or SQL_NTS) is put to LenghtIndicator*/
int MADB_ConvertAnsi2Unicode(Client_Charset *cc, char *AnsiString, SQLLEN AnsiLength,
SQLWCHAR *UnicodeString, SQLLEN UnicodeLength,
SQLLEN *LengthIndicator, BOOL IsNull, MADB_Error *Error)
Expand All @@ -168,7 +170,7 @@ int MADB_ConvertAnsi2Unicode(Client_Charset *cc, char *AnsiString, SQLLEN AnsiLe
IsNull= 1;

/* calculate required length */
RequiredLength= MultiByteToWideChar(cc->CodePage, 0, AnsiString, IsNull ? -IsNull : (int)AnsiLength, NULL, 0);
RequiredLength= MultiByteToWideChar(cc->CodePage, 0, AnsiString, IsNull ? -1 : (int)AnsiLength, NULL, 0);

/* Set LengthIndicator */
if (LengthIndicator)
Expand All @@ -179,7 +181,7 @@ int MADB_ConvertAnsi2Unicode(Client_Charset *cc, char *AnsiString, SQLLEN AnsiLe
if (RequiredLength > UnicodeLength)
Tmp= (SQLWCHAR *)malloc(RequiredLength * sizeof(SQLWCHAR));

RequiredLength= MultiByteToWideChar(cc->CodePage, 0, AnsiString, IsNull ? -IsNull : (int)AnsiLength, Tmp, (int)RequiredLength);
RequiredLength= MultiByteToWideChar(cc->CodePage, 0, AnsiString, IsNull ? -1 : (int)AnsiLength, Tmp, (int)RequiredLength);
if (RequiredLength < 1)
{
if (Error)
Expand All @@ -202,7 +204,10 @@ int MADB_ConvertAnsi2Unicode(Client_Charset *cc, char *AnsiString, SQLLEN AnsiLe
return rc;
}


/* Returns required length for result string with(if dest and dest length are provided)
or without terminating NULL(otherwise). If cc is NULL, or not initialized(CodePage is 0),
then simply SrcLength is returned.
If Dest is not NULL, and DestLenth is 0, then error*/
SQLLEN MADB_SetString(Client_Charset* cc, void *Dest, SQLULEN DestLength,
char *Src, SQLLEN SrcLength/*bytes*/, MADB_Error *Error)
{
Expand Down Expand Up @@ -243,9 +248,8 @@ SQLLEN MADB_SetString(Client_Charset* cc, void *Dest, SQLULEN DestLength,

if (!cc || !cc->CodePage)
{
size_t len= SrcLength;
strncpy_s((char *)Dest, DestLength, Src ? Src : "", _TRUNCATE);
if (Error && len >= DestLength)
if (Error && SrcLength >= DestLength)
MADB_SetError(Error, MADB_ERR_01004, NULL, 0);
return SrcLength;
}
Expand Down
165 changes: 114 additions & 51 deletions ma_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -1840,6 +1840,22 @@ SQLUSMALLINT MADB_MapToRowStatus(SQLRETURN rc)
return SQL_ROW_SUCCESS;
}


void ResetDescIntBuffers(MADB_Desc *Desc)
{
MADB_DescRecord *Rec;
SQLSMALLINT i;

for (i= 0; i < Desc->Header.Count; ++i)
{
Rec= MADB_DescGetInternalRecord(Desc, i, MADB_DESC_READ);
if (Rec)
{
MADB_FREE(Rec->InternalBuffer);
}
}
}

/* For first row we just take its result as initial.
For the rest, if all rows SQL_SUCCESS or SQL_ERROR - aggregated result is SQL_SUCCESS or SQL_ERROR, respectively
Otherwise - SQL_SUCCESS_WITH_INFO */
Expand Down Expand Up @@ -1999,6 +2015,8 @@ SQLRETURN MADB_StmtFetch(MADB_Stmt *Stmt, my_bool KeepPosition)
MYF(MY_ZEROFILL) | MYF(MY_ALLOW_ZERO_PTR));
memset(Stmt->Lengths, 0, sizeof(long) * mysql_stmt_field_count(Stmt->stmt));

ResetDescIntBuffers(Stmt->Ird);

if (KeepPosition && Stmt->Options.CursorType != SQL_CURSOR_FORWARD_ONLY && SaveCursor)
{
Stmt->stmt->result_cursor= SaveCursor;
Expand Down Expand Up @@ -2458,7 +2476,8 @@ SQLRETURN MADB_StmtGetData(SQLHSTMT StatementHandle,
Bind.length= &Bind.length_value;
Bind.is_null= &IsNull;

switch(OdbcType) {
switch(OdbcType)
{
case SQL_DATE:
case SQL_C_TYPE_DATE:
{
Expand Down Expand Up @@ -2544,71 +2563,117 @@ SQLRETURN MADB_StmtGetData(SQLHSTMT StatementHandle,
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
{
char *ClientValue;
size_t CharLength= 0, SrcLength;
char *ClientValue= NULL;
size_t CharLength= 0;

if (!(ClientValue = (char *)MADB_CALLOC(Stmt->stmt->fields[Offset].max_length + 1)))
/* Kinda this it not 1st call for this value, and we have it nice and recoded */
if (IrdRec->InternalBuffer == NULL/* && Stmt->Lengths[Offset] == 0*/)
{
MADB_SetError(&Stmt->Error, MADB_ERR_HY001, NULL, 0);
return Stmt->Error.ReturnValue;
}
Bind.buffer= ClientValue;
Bind.buffer_type= MYSQL_TYPE_STRING;
Bind.buffer_length= Stmt->stmt->fields[Offset].max_length + 1;
if (!(ClientValue = (char *)MADB_CALLOC(Stmt->stmt->fields[Offset].max_length + 1)))
{
MADB_SetError(&Stmt->Error, MADB_ERR_HY001, NULL, 0);
return Stmt->Error.ReturnValue;
}
Bind.buffer= ClientValue;
Bind.buffer_type= MYSQL_TYPE_STRING;
Bind.buffer_length= Stmt->stmt->fields[Offset].max_length + 1;

if (mysql_stmt_fetch_column(Stmt->stmt, &Bind, Offset, Stmt->CharOffset[Offset]))
{
MADB_SetNativeError(&Stmt->Error, SQL_HANDLE_STMT, Stmt->stmt);
return Stmt->Error.ReturnValue;
}

/* reset row_ptr */
Stmt->stmt->bind[Offset].row_ptr= SavePtr;

/* check total length: if not enough space, we need to calculate new CharOffset for next fetch */
if (Stmt->stmt->fields[Offset].max_length)
{
size_t ReqBuffOctetLen;
/* Size in chars */
CharLength= MbstrCharLen(ClientValue, Stmt->stmt->fields[Offset].max_length - Stmt->CharOffset[Offset],
Stmt->Connection->charset.cs_info);
/* MbstrCharLen gave us length in characters. For encoding of each character we might need
2 SQLWCHARs in case of UTF16, or 1 SQLWCHAR in case of UTF32. Probably we need calcualate better
number of required SQLWCHARs */
ReqBuffOctetLen= (CharLength + 1)*2*sizeof(SQLWCHAR);

if (BufferLength)
{
/* Buffer is not big enough. Alocating InternalBuffer.
MADB_SetString would do that anyway if - allocate buffer fitting the whole wide string,
and then copied its part to the application's buffer */
if (ReqBuffOctetLen > (size_t)BufferLength)
{
IrdRec->InternalBuffer= (char*)MADB_CALLOC(ReqBuffOctetLen);

if (IrdRec->InternalBuffer == 0)
{
MADB_FREE(ClientValue);
return MADB_SetError(&Stmt->Error, MADB_ERR_HY001, NULL, 0);
}

CharLength= MADB_SetString(&Stmt->Connection->charset, IrdRec->InternalBuffer, (SQLINTEGER)ReqBuffOctetLen,
ClientValue, Stmt->stmt->fields[Offset].max_length - Stmt->CharOffset[Offset], &Stmt->Error);
}
else
{
/* Application's buffer is big enough - writing directly there */
CharLength= MADB_SetString(&Stmt->Connection->charset, TargetValuePtr, (SQLINTEGER)(BufferLength / sizeof(SQLWCHAR)),
ClientValue, Stmt->stmt->fields[Offset].max_length - Stmt->CharOffset[Offset], &Stmt->Error);
}
}

if (mysql_stmt_fetch_column(Stmt->stmt, &Bind, Offset, Stmt->CharOffset[Offset]))
if (!Stmt->CharOffset[Offset])
{
Stmt->Lengths[Offset]= CharLength*sizeof(SQLWCHAR);
}
}
}
else /* IrdRec->InternalBuffer == NULL && Stmt->Lengths[Offset] == 0 */
{
MADB_SetNativeError(&Stmt->Error, SQL_HANDLE_STMT, Stmt->stmt);
return Stmt->Error.ReturnValue;
}
if (!Stmt->CharOffset[Offset])
Stmt->Lengths[Offset]= MIN(*Bind.length, Stmt->stmt->fields[Offset].max_length);
/* reset row_ptr */
Stmt->stmt->bind[Offset].row_ptr= SavePtr;
SrcLength= Stmt->stmt->fields[Offset].max_length;

/* check total length: if not enough space, we need to calculate new CharOffset for next fetch */
if (Stmt->stmt->fields[Offset].max_length)
CharLength= SqlwcsLen((SQLWCHAR*)((char*)IrdRec->InternalBuffer + Stmt->CharOffset[Offset]));
}

if (StrLen_or_IndPtr)
{
CharLength= MbstrCharLen(ClientValue, Stmt->stmt->fields[Offset].max_length - Stmt->CharOffset[Offset],
Stmt->Connection->charset.cs_info);
*StrLen_or_IndPtr= CharLength * sizeof(SQLWCHAR);
}

if (!BufferLength)
{
MADB_FREE(ClientValue);
if (StrLen_or_IndPtr)
*StrLen_or_IndPtr= CharLength * sizeof(SQLWCHAR);
MADB_SetError(&Stmt->Error, MADB_ERR_01004, NULL, 0);
return Stmt->Error.ReturnValue;
}


// memset(TargetValuePtr, 0, MIN((size_t)BufferLength, (SrcLength+1) * sizeof(SQLWCHAR) ));
if (Stmt->stmt->fields[Offset].max_length)
CharLength= MADB_SetString(&Stmt->Connection->charset, TargetValuePtr, (SQLINTEGER)(BufferLength / sizeof(SQLWCHAR)),
ClientValue, Stmt->stmt->fields[Offset].max_length - Stmt->CharOffset[Offset], &Stmt->Error);
return MADB_SetError(&Stmt->Error, MADB_ERR_01004, NULL, 0);
}

if (StrLen_or_IndPtr)
if (IrdRec->InternalBuffer)
{
*StrLen_or_IndPtr= CharLength * sizeof(SQLWCHAR);
/* If we have more place than only for the TN */
if (BufferLength > sizeof(SQLWCHAR))
{
memcpy(TargetValuePtr, (char*)IrdRec->InternalBuffer + Stmt->CharOffset[Offset],
MIN(BufferLength - sizeof(SQLWCHAR), CharLength*sizeof(SQLWCHAR)));
}
/* Terminating Null */
*(SQLWCHAR*)((char*)TargetValuePtr + MIN(BufferLength - sizeof(SQLWCHAR), CharLength*sizeof(SQLWCHAR)))= 0;
}
if (CharLength > BufferLength / sizeof(SQLWCHAR))

if (CharLength >= BufferLength / sizeof(SQLWCHAR))
{
/* Calculate new offset and substract 1 byte for null termination. Since we fill the buffer with all characters we can -1 char for NULL */
CharLength= BufferLength / sizeof(SQLWCHAR);
Stmt->CharOffset[Offset]+= (unsigned long)MbstrOctetLen(ClientValue, &CharLength, Stmt->Connection->charset.cs_info) - 1;

MADB_SetError(&Stmt->Error, MADB_ERR_01004, NULL, 0);
/* Calculate new offset and substract 1 byte for null termination */
Stmt->CharOffset[Offset]+= BufferLength - sizeof(SQLWCHAR);
MADB_FREE(ClientValue);
return Stmt->Error.ReturnValue;

return MADB_SetError(&Stmt->Error, MADB_ERR_01004, NULL, 0);
}
else
{
Stmt->CharOffset[Offset]= Stmt->stmt->fields[Offset].max_length;
if (StrLen_or_IndPtr)
*StrLen_or_IndPtr= CharLength * sizeof(SQLWCHAR);
Stmt->CharOffset[Offset]= Stmt->Lengths[Offset];
MADB_FREE(IrdRec->InternalBuffer);
}

MADB_FREE(ClientValue);
Stmt->stmt->bind[Offset].row_ptr= SavePtr;
}
Expand Down Expand Up @@ -2677,7 +2742,8 @@ SQLRETURN MADB_StmtGetData(SQLHSTMT StatementHandle,
MADB_SetNativeError(&Stmt->Error, SQL_HANDLE_STMT, Stmt->stmt);
return Stmt->Error.ReturnValue;
}
/* Dirty temporary hack before we know what is going on */
/* Dirty temporary hack before we know what is going on. Yes, there is nothing more eternal, than temporary
It's not that bad, after all */
if ((long)*Bind.length == -1)
*Bind.length= 0;
/* end of dirty hack */
Expand All @@ -2698,7 +2764,6 @@ SQLRETURN MADB_StmtGetData(SQLHSTMT StatementHandle,
p[BufferLength-1]= 0;
}
}


if (StrLen_or_IndPtr)
{
Expand Down Expand Up @@ -2750,7 +2815,6 @@ SQLRETURN MADB_StmtGetData(SQLHSTMT StatementHandle,
/* Bind.fetch_result(&Bind, &Stmt->stmt->fields[Offset], &Stmt->stmt->bind[Offset].row_ptr); */
Stmt->stmt->bind[Offset].row_ptr= SavePtr;
}
break;
} /* End of switch(OdbcType) */

/* Marking fixed length fields to be able to return SQL_NO_DATA on subsequent calls, as standard prescribes
Expand All @@ -2766,8 +2830,7 @@ SQLRETURN MADB_StmtGetData(SQLHSTMT StatementHandle,
{
if (!StrLen_or_IndPtr)
{
MADB_SetError(&Stmt->Error, MADB_ERR_22002, NULL, 0);
return Stmt->Error.ReturnValue;
return MADB_SetError(&Stmt->Error, MADB_ERR_22002, NULL, 0);
}
*StrLen_or_IndPtr= SQL_NULL_DATA;
}
Expand Down
2 changes: 1 addition & 1 deletion ma_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ SQLLEN MbstrOctetLen(char *str, SQLLEN *CharLen, CHARSET_INFO *cs)
{
while (inChars > 0 || inChars < 0 && *str)
{
result+= cs->mb_charlen(*str);
result+= cs->mb_charlen(0 + *str);
--inChars;
str+= cs->mb_charlen(*str);
}
Expand Down
10 changes: 10 additions & 0 deletions odbc_3_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,7 @@ SQLRETURN SQL_API SQLGetData(SQLHSTMT StatementHandle,
{
MADB_Stmt *Stmt= (MADB_Stmt*)StatementHandle;
unsigned int i;
MADB_DescRecord *IrdRec;

if (StatementHandle== SQL_NULL_HSTMT)
return SQL_INVALID_HANDLE;
Expand Down Expand Up @@ -1816,8 +1817,17 @@ SQLRETURN SQL_API SQLGetData(SQLHSTMT StatementHandle,

/* reset offsets for other columns. Doing that here since "internal" calls should not do that */
for (i=0; i < mysql_stmt_field_count(Stmt->stmt); i++)
{
if (i != Col_or_Param_Num - 1)
{
IrdRec= MADB_DescGetInternalRecord(Stmt->Ird, i, MADB_DESC_READ);
if (IrdRec)
{
MADB_FREE(IrdRec->InternalBuffer);
}
Stmt->CharOffset[i]= 0;
}
}

return Stmt->Methods->GetData(StatementHandle, Col_or_Param_Num, TargetType, TargetValuePtr, BufferLength, StrLen_or_IndPtr, FALSE);
}
Expand Down
Loading

0 comments on commit 3f3ec84

Please sign in to comment.