mirror of
https://github.com/postgres/postgres.git
synced 2025-06-27 23:21:58 +03:00
Update 3.0 protocol support to match recent agreements about how to
handle multiple 'formats' for data I/O. Restructure CommandDest and DestReceiver stuff one more time (it's finally starting to look a bit clean though). Code now matches latest 3.0 protocol document as far as message formats go --- but there is no support for binary I/O yet.
This commit is contained in:
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.134 2003/04/26 20:22:59 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.135 2003/05/08 18:16:37 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -55,250 +55,10 @@ static void parseInput(PGconn *conn);
|
||||
static void handleSendFailure(PGconn *conn);
|
||||
static void handleSyncLoss(PGconn *conn, char id, int msgLength);
|
||||
static int getRowDescriptions(PGconn *conn);
|
||||
static int getAnotherTuple(PGconn *conn, int binary);
|
||||
static int getAnotherTuple(PGconn *conn);
|
||||
static int getParameterStatus(PGconn *conn);
|
||||
static int getNotify(PGconn *conn);
|
||||
|
||||
/* ---------------
|
||||
* Escaping arbitrary strings to get valid SQL strings/identifiers.
|
||||
*
|
||||
* Replaces "\\" with "\\\\" and "'" with "''".
|
||||
* length is the length of the buffer pointed to by
|
||||
* from. The buffer at to must be at least 2*length + 1 characters
|
||||
* long. A terminating NUL character is written.
|
||||
* ---------------
|
||||
*/
|
||||
|
||||
size_t
|
||||
PQescapeString(char *to, const char *from, size_t length)
|
||||
{
|
||||
const char *source = from;
|
||||
char *target = to;
|
||||
unsigned int remaining = length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
switch (*source)
|
||||
{
|
||||
case '\\':
|
||||
*target = '\\';
|
||||
target++;
|
||||
*target = '\\';
|
||||
/* target and remaining are updated below. */
|
||||
break;
|
||||
|
||||
case '\'':
|
||||
*target = '\'';
|
||||
target++;
|
||||
*target = '\'';
|
||||
/* target and remaining are updated below. */
|
||||
break;
|
||||
|
||||
default:
|
||||
*target = *source;
|
||||
/* target and remaining are updated below. */
|
||||
}
|
||||
source++;
|
||||
target++;
|
||||
remaining--;
|
||||
}
|
||||
|
||||
/* Write the terminating NUL character. */
|
||||
*target = '\0';
|
||||
|
||||
return target - to;
|
||||
}
|
||||
|
||||
/*
|
||||
* PQescapeBytea - converts from binary string to the
|
||||
* minimal encoding necessary to include the string in an SQL
|
||||
* INSERT statement with a bytea type column as the target.
|
||||
*
|
||||
* The following transformations are applied
|
||||
* '\0' == ASCII 0 == \\000
|
||||
* '\'' == ASCII 39 == \'
|
||||
* '\\' == ASCII 92 == \\\\
|
||||
* anything >= 0x80 ---> \\ooo (where ooo is an octal expression)
|
||||
*/
|
||||
unsigned char *
|
||||
PQescapeBytea(const unsigned char *bintext, size_t binlen, size_t *bytealen)
|
||||
{
|
||||
const unsigned char *vp;
|
||||
unsigned char *rp;
|
||||
unsigned char *result;
|
||||
size_t i;
|
||||
size_t len;
|
||||
|
||||
/*
|
||||
* empty string has 1 char ('\0')
|
||||
*/
|
||||
len = 1;
|
||||
|
||||
vp = bintext;
|
||||
for (i = binlen; i > 0; i--, vp++)
|
||||
{
|
||||
if (*vp == 0 || *vp >= 0x80)
|
||||
len += 5; /* '5' is for '\\ooo' */
|
||||
else if (*vp == '\'')
|
||||
len += 2;
|
||||
else if (*vp == '\\')
|
||||
len += 4;
|
||||
else
|
||||
len++;
|
||||
}
|
||||
|
||||
rp = result = (unsigned char *) malloc(len);
|
||||
if (rp == NULL)
|
||||
return NULL;
|
||||
|
||||
vp = bintext;
|
||||
*bytealen = len;
|
||||
|
||||
for (i = binlen; i > 0; i--, vp++)
|
||||
{
|
||||
if (*vp == 0 || *vp >= 0x80)
|
||||
{
|
||||
(void) sprintf(rp, "\\\\%03o", *vp);
|
||||
rp += 5;
|
||||
}
|
||||
else if (*vp == '\'')
|
||||
{
|
||||
rp[0] = '\\';
|
||||
rp[1] = '\'';
|
||||
rp += 2;
|
||||
}
|
||||
else if (*vp == '\\')
|
||||
{
|
||||
rp[0] = '\\';
|
||||
rp[1] = '\\';
|
||||
rp[2] = '\\';
|
||||
rp[3] = '\\';
|
||||
rp += 4;
|
||||
}
|
||||
else
|
||||
*rp++ = *vp;
|
||||
}
|
||||
*rp = '\0';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* PQunescapeBytea - converts the null terminated string representation
|
||||
* of a bytea, strtext, into binary, filling a buffer. It returns a
|
||||
* pointer to the buffer which is NULL on error, and the size of the
|
||||
* buffer in retbuflen. The pointer may subsequently be used as an
|
||||
* argument to the function free(3). It is the reverse of PQescapeBytea.
|
||||
*
|
||||
* The following transformations are reversed:
|
||||
* '\0' == ASCII 0 == \000
|
||||
* '\'' == ASCII 39 == \'
|
||||
* '\\' == ASCII 92 == \\
|
||||
*
|
||||
* States:
|
||||
* 0 normal 0->1->2->3->4
|
||||
* 1 \ 1->5
|
||||
* 2 \0 1->6
|
||||
* 3 \00
|
||||
* 4 \000
|
||||
* 5 \'
|
||||
* 6 \\
|
||||
*/
|
||||
unsigned char *
|
||||
PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen)
|
||||
{
|
||||
size_t buflen;
|
||||
unsigned char *buffer,
|
||||
*bp;
|
||||
const unsigned char *sp;
|
||||
unsigned int state = 0;
|
||||
|
||||
if (strtext == NULL)
|
||||
return NULL;
|
||||
buflen = strlen(strtext); /* will shrink, also we discover if
|
||||
* strtext */
|
||||
buffer = (unsigned char *) malloc(buflen); /* isn't NULL terminated */
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
for (bp = buffer, sp = strtext; *sp != '\0'; bp++, sp++)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (*sp == '\\')
|
||||
state = 1;
|
||||
*bp = *sp;
|
||||
break;
|
||||
case 1:
|
||||
if (*sp == '\'') /* state=5 */
|
||||
{ /* replace \' with 39 */
|
||||
bp--;
|
||||
*bp = '\'';
|
||||
buflen--;
|
||||
state = 0;
|
||||
}
|
||||
else if (*sp == '\\') /* state=6 */
|
||||
{ /* replace \\ with 92 */
|
||||
bp--;
|
||||
*bp = '\\';
|
||||
buflen--;
|
||||
state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isdigit(*sp))
|
||||
state = 2;
|
||||
else
|
||||
state = 0;
|
||||
*bp = *sp;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (isdigit(*sp))
|
||||
state = 3;
|
||||
else
|
||||
state = 0;
|
||||
*bp = *sp;
|
||||
break;
|
||||
case 3:
|
||||
if (isdigit(*sp)) /* state=4 */
|
||||
{
|
||||
int v;
|
||||
|
||||
bp -= 3;
|
||||
sscanf(sp - 2, "%03o", &v);
|
||||
*bp = v;
|
||||
buflen -= 3;
|
||||
state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
*bp = *sp;
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer = realloc(buffer, buflen);
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
|
||||
*retbuflen = buflen;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PQfreemem - safely frees memory allocated
|
||||
*
|
||||
* Needed mostly by Win32, unless multithreaded DLL (/MD in VC6)
|
||||
* Used for freeing memory from PQescapeByte()a/PQunescapeBytea()
|
||||
*/
|
||||
void PQfreemem(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------
|
||||
* Space management for PGresult.
|
||||
@ -909,7 +669,7 @@ parseInput(PGconn *conn)
|
||||
return;
|
||||
}
|
||||
if (msgLength > 30000 &&
|
||||
!(id == 'T' || id == 'D' || id == 'B' || id == 'd'))
|
||||
!(id == 'T' || id == 'D' || id == 'd'))
|
||||
{
|
||||
handleSyncLoss(conn, id, msgLength);
|
||||
return;
|
||||
@ -1074,11 +834,11 @@ parseInput(PGconn *conn)
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'D': /* ASCII data tuple */
|
||||
case 'D': /* Data Row */
|
||||
if (conn->result != NULL)
|
||||
{
|
||||
/* Read another tuple of a normal query response */
|
||||
if (getAnotherTuple(conn, FALSE))
|
||||
if (getAnotherTuple(conn))
|
||||
return;
|
||||
}
|
||||
else
|
||||
@ -1090,30 +850,18 @@ parseInput(PGconn *conn)
|
||||
conn->inCursor += msgLength;
|
||||
}
|
||||
break;
|
||||
case 'B': /* Binary data tuple */
|
||||
if (conn->result != NULL)
|
||||
{
|
||||
/* Read another tuple of a normal query response */
|
||||
if (getAnotherTuple(conn, TRUE))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
|
||||
libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n"));
|
||||
DONOTICE(conn, noticeWorkspace);
|
||||
/* Discard the unexpected message */
|
||||
conn->inCursor += msgLength;
|
||||
}
|
||||
break;
|
||||
case 'G': /* Start Copy In */
|
||||
if (pqGetc(&conn->copy_is_binary, conn))
|
||||
return;
|
||||
/* XXX we currently ignore the rest of the message */
|
||||
conn->inCursor = conn->inStart + 5 + msgLength;
|
||||
conn->asyncStatus = PGASYNC_COPY_IN;
|
||||
break;
|
||||
case 'H': /* Start Copy Out */
|
||||
if (pqGetc(&conn->copy_is_binary, conn))
|
||||
return;
|
||||
/* XXX we currently ignore the rest of the message */
|
||||
conn->inCursor = conn->inStart + 5 + msgLength;
|
||||
conn->asyncStatus = PGASYNC_COPY_OUT;
|
||||
conn->copy_already_done = 0;
|
||||
break;
|
||||
@ -1229,13 +977,15 @@ getRowDescriptions(PGconn *conn)
|
||||
int typid;
|
||||
int typlen;
|
||||
int atttypmod;
|
||||
int format;
|
||||
|
||||
if (pqGets(&conn->workBuffer, conn) ||
|
||||
pqGetInt(&tableid, 4, conn) ||
|
||||
pqGetInt(&columnid, 2, conn) ||
|
||||
pqGetInt(&typid, 4, conn) ||
|
||||
pqGetInt(&typlen, 2, conn) ||
|
||||
pqGetInt(&atttypmod, 4, conn))
|
||||
pqGetInt(&atttypmod, 4, conn) ||
|
||||
pqGetInt(&format, 2, conn))
|
||||
{
|
||||
PQclear(result);
|
||||
return EOF;
|
||||
@ -1247,13 +997,14 @@ getRowDescriptions(PGconn *conn)
|
||||
*/
|
||||
columnid = (int) ((int16) columnid);
|
||||
typlen = (int) ((int16) typlen);
|
||||
format = (int) ((int16) format);
|
||||
|
||||
result->attDescs[i].name = pqResultStrdup(result,
|
||||
conn->workBuffer.data);
|
||||
result->attDescs[i].typid = typid;
|
||||
result->attDescs[i].typlen = typlen;
|
||||
result->attDescs[i].atttypmod = atttypmod;
|
||||
/* XXX todo: save tableid/columnid too */
|
||||
/* XXX todo: save tableid/columnid, format too */
|
||||
}
|
||||
|
||||
/* Success! */
|
||||
@ -1262,7 +1013,7 @@ getRowDescriptions(PGconn *conn)
|
||||
}
|
||||
|
||||
/*
|
||||
* parseInput subroutine to read a 'B' or 'D' (row data) message.
|
||||
* parseInput subroutine to read a 'D' (row data) message.
|
||||
* We add another tuple to the existing PGresult structure.
|
||||
* Returns: 0 if completed message, EOF if error or not enough data yet.
|
||||
*
|
||||
@ -1272,23 +1023,14 @@ getRowDescriptions(PGconn *conn)
|
||||
*/
|
||||
|
||||
static int
|
||||
getAnotherTuple(PGconn *conn, int binary)
|
||||
getAnotherTuple(PGconn *conn)
|
||||
{
|
||||
PGresult *result = conn->result;
|
||||
int nfields = result->numAttributes;
|
||||
PGresAttValue *tup;
|
||||
|
||||
/* the backend sends us a bitmap of which attributes are null */
|
||||
char std_bitmap[64]; /* used unless it doesn't fit */
|
||||
char *bitmap = std_bitmap;
|
||||
int i;
|
||||
size_t nbytes; /* the number of bytes in bitmap */
|
||||
char bmap; /* One byte of the bitmap */
|
||||
int bitmap_index; /* Its index */
|
||||
int bitcnt; /* number of bits examined in current byte */
|
||||
int tupnfields; /* # fields from tuple */
|
||||
int vlen; /* length of the current field value */
|
||||
|
||||
result->binary = binary;
|
||||
int i;
|
||||
|
||||
/* Allocate tuple space if first time for this data message */
|
||||
if (conn->curTuple == NULL)
|
||||
@ -1301,61 +1043,50 @@ getAnotherTuple(PGconn *conn, int binary)
|
||||
}
|
||||
tup = conn->curTuple;
|
||||
|
||||
/* Get the null-value bitmap */
|
||||
nbytes = (nfields + BYTELEN - 1) / BYTELEN;
|
||||
/* malloc() only for unusually large field counts... */
|
||||
if (nbytes > sizeof(std_bitmap))
|
||||
bitmap = (char *) malloc(nbytes);
|
||||
/* Get the field count and make sure it's what we expect */
|
||||
if (pqGetInt(&tupnfields, 2, conn))
|
||||
return EOF;
|
||||
|
||||
if (pqGetnchar(bitmap, nbytes, conn))
|
||||
goto EOFexit;
|
||||
if (tupnfields != nfields)
|
||||
{
|
||||
/* Replace partially constructed result with an error result */
|
||||
pqClearAsyncResult(conn);
|
||||
printfPQExpBuffer(&conn->errorMessage,
|
||||
libpq_gettext("unexpected field count in D message\n"));
|
||||
saveErrorResult(conn);
|
||||
conn->asyncStatus = PGASYNC_READY;
|
||||
/* Discard the failed message by pretending we read it */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Scan the fields */
|
||||
bitmap_index = 0;
|
||||
bmap = bitmap[bitmap_index];
|
||||
bitcnt = 0;
|
||||
|
||||
for (i = 0; i < nfields; i++)
|
||||
{
|
||||
if (!(bmap & 0200))
|
||||
/* get the value length */
|
||||
if (pqGetInt(&vlen, 4, conn))
|
||||
return EOF;
|
||||
if (vlen == -1)
|
||||
{
|
||||
/* if the field value is absent, make it a null string */
|
||||
/* null field */
|
||||
tup[i].value = result->null_field;
|
||||
tup[i].len = NULL_LEN;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
if (vlen < 0)
|
||||
vlen = 0;
|
||||
if (tup[i].value == NULL)
|
||||
{
|
||||
/* get the value length (the first four bytes are for length) */
|
||||
if (pqGetInt(&vlen, 4, conn))
|
||||
goto EOFexit;
|
||||
if (binary == 0)
|
||||
vlen = vlen - 4;
|
||||
if (vlen < 0)
|
||||
vlen = 0;
|
||||
tup[i].value = (char *) pqResultAlloc(result, vlen + 1, false);
|
||||
if (tup[i].value == NULL)
|
||||
{
|
||||
tup[i].value = (char *) pqResultAlloc(result, vlen + 1, (bool) binary);
|
||||
if (tup[i].value == NULL)
|
||||
goto outOfMemory;
|
||||
}
|
||||
tup[i].len = vlen;
|
||||
/* read in the value */
|
||||
if (vlen > 0)
|
||||
if (pqGetnchar((char *) (tup[i].value), vlen, conn))
|
||||
goto EOFexit;
|
||||
/* we have to terminate this ourselves */
|
||||
tup[i].value[vlen] = '\0';
|
||||
goto outOfMemory;
|
||||
}
|
||||
/* advance the bitmap stuff */
|
||||
bitcnt++;
|
||||
if (bitcnt == BYTELEN)
|
||||
{
|
||||
bitmap_index++;
|
||||
bmap = bitmap[bitmap_index];
|
||||
bitcnt = 0;
|
||||
}
|
||||
else
|
||||
bmap <<= 1;
|
||||
tup[i].len = vlen;
|
||||
/* read in the value */
|
||||
if (vlen > 0)
|
||||
if (pqGetnchar((char *) (tup[i].value), vlen, conn))
|
||||
return EOF;
|
||||
/* we have to terminate this ourselves */
|
||||
tup[i].value[vlen] = '\0';
|
||||
}
|
||||
|
||||
/* Success! Store the completed tuple in the result */
|
||||
@ -1364,8 +1095,6 @@ getAnotherTuple(PGconn *conn, int binary)
|
||||
/* and reset for a new message */
|
||||
conn->curTuple = NULL;
|
||||
|
||||
if (bitmap != std_bitmap)
|
||||
free(bitmap);
|
||||
return 0;
|
||||
|
||||
outOfMemory:
|
||||
@ -1380,13 +1109,8 @@ outOfMemory:
|
||||
libpq_gettext("out of memory\n"));
|
||||
conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
|
||||
conn->asyncStatus = PGASYNC_READY;
|
||||
/* Discard the failed message --- good idea? */
|
||||
conn->inStart = conn->inEnd;
|
||||
|
||||
EOFexit:
|
||||
if (bitmap != std_bitmap)
|
||||
free(bitmap);
|
||||
return EOF;
|
||||
/* Discard the failed message by pretending we read it */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -2129,10 +1853,11 @@ PQfn(PGconn *conn,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
|
||||
pqPuts("", conn) < 0 || /* useless string */
|
||||
pqPutInt(fnid, 4, conn) < 0 || /* function id */
|
||||
pqPutInt(nargs, 4, conn) < 0) /* # of args */
|
||||
if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
|
||||
pqPutInt(fnid, 4, conn) < 0 || /* function id */
|
||||
pqPutInt(1, 2, conn) < 0 || /* # of format codes */
|
||||
pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
|
||||
pqPutInt(nargs, 2, conn) < 0) /* # of args */
|
||||
{
|
||||
handleSendFailure(conn);
|
||||
return NULL;
|
||||
@ -2145,10 +1870,12 @@ PQfn(PGconn *conn,
|
||||
handleSendFailure(conn);
|
||||
return NULL;
|
||||
}
|
||||
if (args[i].len == -1)
|
||||
continue; /* it's NULL */
|
||||
|
||||
if (args[i].isint)
|
||||
{
|
||||
if (pqPutInt(args[i].u.integer, 4, conn))
|
||||
if (pqPutInt(args[i].u.integer, args[i].len, conn))
|
||||
{
|
||||
handleSendFailure(conn);
|
||||
return NULL;
|
||||
@ -2164,6 +1891,12 @@ PQfn(PGconn *conn,
|
||||
}
|
||||
}
|
||||
|
||||
if (pqPutInt(1, 2, conn) < 0) /* result format code: BINARY */
|
||||
{
|
||||
handleSendFailure(conn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pqPutMsgEnd(conn) < 0 ||
|
||||
pqFlush(conn))
|
||||
{
|
||||
@ -2204,7 +1937,7 @@ PQfn(PGconn *conn,
|
||||
break;
|
||||
}
|
||||
if (msgLength > 30000 &&
|
||||
!(id == 'T' || id == 'D' || id == 'B' || id == 'd' || id == 'V'))
|
||||
!(id == 'T' || id == 'D' || id == 'd' || id == 'V'))
|
||||
{
|
||||
handleSyncLoss(conn, id, msgLength);
|
||||
break;
|
||||
@ -2243,16 +1976,13 @@ PQfn(PGconn *conn,
|
||||
switch (id)
|
||||
{
|
||||
case 'V': /* function result */
|
||||
if (pqGetc(&id, conn))
|
||||
if (pqGetInt(actual_result_len, 4, conn))
|
||||
continue;
|
||||
if (id == 'G')
|
||||
if (*actual_result_len != -1)
|
||||
{
|
||||
/* function returned nonempty value */
|
||||
if (pqGetInt(actual_result_len, 4, conn))
|
||||
continue;
|
||||
if (result_is_int)
|
||||
{
|
||||
if (pqGetInt(result_buf, 4, conn))
|
||||
if (pqGetInt(result_buf, *actual_result_len, conn))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@ -2262,24 +1992,9 @@ PQfn(PGconn *conn,
|
||||
conn))
|
||||
continue;
|
||||
}
|
||||
if (pqGetc(&id, conn)) /* get the last '0' */
|
||||
continue;
|
||||
}
|
||||
if (id == '0')
|
||||
{
|
||||
/* correctly finished function result message */
|
||||
status = PGRES_COMMAND_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The backend violates the protocol. */
|
||||
printfPQExpBuffer(&conn->errorMessage,
|
||||
libpq_gettext("protocol error: id=0x%x\n"),
|
||||
id);
|
||||
saveErrorResult(conn);
|
||||
conn->inStart += 5 + msgLength;
|
||||
return prepareAsyncResult(conn);
|
||||
}
|
||||
/* correctly finished function result message */
|
||||
status = PGRES_COMMAND_OK;
|
||||
break;
|
||||
case 'E': /* error return */
|
||||
if (pqGetErrorNotice(conn, true))
|
||||
@ -2744,6 +2459,18 @@ PQflush(PGconn *conn)
|
||||
return (pqFlush(conn));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PQfreemem - safely frees memory allocated
|
||||
*
|
||||
* Needed mostly by Win32, unless multithreaded DLL (/MD in VC6)
|
||||
* Used for freeing memory from PQescapeByte()a/PQunescapeBytea()
|
||||
*/
|
||||
void PQfreemem(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* PQfreeNotify - free's the memory associated with a PGnotify
|
||||
*
|
||||
@ -2760,3 +2487,232 @@ PQfreeNotify(PGnotify *notify)
|
||||
{
|
||||
PQfreemem(notify);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------
|
||||
* Escaping arbitrary strings to get valid SQL strings/identifiers.
|
||||
*
|
||||
* Replaces "\\" with "\\\\" and "'" with "''".
|
||||
* length is the length of the buffer pointed to by
|
||||
* from. The buffer at to must be at least 2*length + 1 characters
|
||||
* long. A terminating NUL character is written.
|
||||
* ---------------
|
||||
*/
|
||||
|
||||
size_t
|
||||
PQescapeString(char *to, const char *from, size_t length)
|
||||
{
|
||||
const char *source = from;
|
||||
char *target = to;
|
||||
unsigned int remaining = length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
switch (*source)
|
||||
{
|
||||
case '\\':
|
||||
*target = '\\';
|
||||
target++;
|
||||
*target = '\\';
|
||||
/* target and remaining are updated below. */
|
||||
break;
|
||||
|
||||
case '\'':
|
||||
*target = '\'';
|
||||
target++;
|
||||
*target = '\'';
|
||||
/* target and remaining are updated below. */
|
||||
break;
|
||||
|
||||
default:
|
||||
*target = *source;
|
||||
/* target and remaining are updated below. */
|
||||
}
|
||||
source++;
|
||||
target++;
|
||||
remaining--;
|
||||
}
|
||||
|
||||
/* Write the terminating NUL character. */
|
||||
*target = '\0';
|
||||
|
||||
return target - to;
|
||||
}
|
||||
|
||||
/*
|
||||
* PQescapeBytea - converts from binary string to the
|
||||
* minimal encoding necessary to include the string in an SQL
|
||||
* INSERT statement with a bytea type column as the target.
|
||||
*
|
||||
* The following transformations are applied
|
||||
* '\0' == ASCII 0 == \\000
|
||||
* '\'' == ASCII 39 == \'
|
||||
* '\\' == ASCII 92 == \\\\
|
||||
* anything >= 0x80 ---> \\ooo (where ooo is an octal expression)
|
||||
*/
|
||||
unsigned char *
|
||||
PQescapeBytea(const unsigned char *bintext, size_t binlen, size_t *bytealen)
|
||||
{
|
||||
const unsigned char *vp;
|
||||
unsigned char *rp;
|
||||
unsigned char *result;
|
||||
size_t i;
|
||||
size_t len;
|
||||
|
||||
/*
|
||||
* empty string has 1 char ('\0')
|
||||
*/
|
||||
len = 1;
|
||||
|
||||
vp = bintext;
|
||||
for (i = binlen; i > 0; i--, vp++)
|
||||
{
|
||||
if (*vp == 0 || *vp >= 0x80)
|
||||
len += 5; /* '5' is for '\\ooo' */
|
||||
else if (*vp == '\'')
|
||||
len += 2;
|
||||
else if (*vp == '\\')
|
||||
len += 4;
|
||||
else
|
||||
len++;
|
||||
}
|
||||
|
||||
rp = result = (unsigned char *) malloc(len);
|
||||
if (rp == NULL)
|
||||
return NULL;
|
||||
|
||||
vp = bintext;
|
||||
*bytealen = len;
|
||||
|
||||
for (i = binlen; i > 0; i--, vp++)
|
||||
{
|
||||
if (*vp == 0 || *vp >= 0x80)
|
||||
{
|
||||
(void) sprintf(rp, "\\\\%03o", *vp);
|
||||
rp += 5;
|
||||
}
|
||||
else if (*vp == '\'')
|
||||
{
|
||||
rp[0] = '\\';
|
||||
rp[1] = '\'';
|
||||
rp += 2;
|
||||
}
|
||||
else if (*vp == '\\')
|
||||
{
|
||||
rp[0] = '\\';
|
||||
rp[1] = '\\';
|
||||
rp[2] = '\\';
|
||||
rp[3] = '\\';
|
||||
rp += 4;
|
||||
}
|
||||
else
|
||||
*rp++ = *vp;
|
||||
}
|
||||
*rp = '\0';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* PQunescapeBytea - converts the null terminated string representation
|
||||
* of a bytea, strtext, into binary, filling a buffer. It returns a
|
||||
* pointer to the buffer which is NULL on error, and the size of the
|
||||
* buffer in retbuflen. The pointer may subsequently be used as an
|
||||
* argument to the function free(3). It is the reverse of PQescapeBytea.
|
||||
*
|
||||
* The following transformations are reversed:
|
||||
* '\0' == ASCII 0 == \000
|
||||
* '\'' == ASCII 39 == \'
|
||||
* '\\' == ASCII 92 == \\
|
||||
*
|
||||
* States:
|
||||
* 0 normal 0->1->2->3->4
|
||||
* 1 \ 1->5
|
||||
* 2 \0 1->6
|
||||
* 3 \00
|
||||
* 4 \000
|
||||
* 5 \'
|
||||
* 6 \\
|
||||
*/
|
||||
unsigned char *
|
||||
PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen)
|
||||
{
|
||||
size_t buflen;
|
||||
unsigned char *buffer,
|
||||
*bp;
|
||||
const unsigned char *sp;
|
||||
unsigned int state = 0;
|
||||
|
||||
if (strtext == NULL)
|
||||
return NULL;
|
||||
buflen = strlen(strtext); /* will shrink, also we discover if
|
||||
* strtext */
|
||||
buffer = (unsigned char *) malloc(buflen); /* isn't NULL terminated */
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
for (bp = buffer, sp = strtext; *sp != '\0'; bp++, sp++)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (*sp == '\\')
|
||||
state = 1;
|
||||
*bp = *sp;
|
||||
break;
|
||||
case 1:
|
||||
if (*sp == '\'') /* state=5 */
|
||||
{ /* replace \' with 39 */
|
||||
bp--;
|
||||
*bp = '\'';
|
||||
buflen--;
|
||||
state = 0;
|
||||
}
|
||||
else if (*sp == '\\') /* state=6 */
|
||||
{ /* replace \\ with 92 */
|
||||
bp--;
|
||||
*bp = '\\';
|
||||
buflen--;
|
||||
state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isdigit(*sp))
|
||||
state = 2;
|
||||
else
|
||||
state = 0;
|
||||
*bp = *sp;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (isdigit(*sp))
|
||||
state = 3;
|
||||
else
|
||||
state = 0;
|
||||
*bp = *sp;
|
||||
break;
|
||||
case 3:
|
||||
if (isdigit(*sp)) /* state=4 */
|
||||
{
|
||||
int v;
|
||||
|
||||
bp -= 3;
|
||||
sscanf(sp - 2, "%03o", &v);
|
||||
*bp = v;
|
||||
buflen -= 3;
|
||||
state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
*bp = *sp;
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer = realloc(buffer, buflen);
|
||||
if (buffer == NULL)
|
||||
return NULL;
|
||||
|
||||
*retbuflen = buflen;
|
||||
return buffer;
|
||||
}
|
||||
|
Reference in New Issue
Block a user