*
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* Copyright (C) 2020-2021 The Psycopg Team
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
* Python 2.7.1. The function is altered to be used with both Python 2 strings
* and Python 3 bytes and is stripped of the support of formats different than
* 's'. Original license follows.
*
* PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
* --------------------------------------------
*
* 1. This LICENSE AGREEMENT is between the Python Software Foundation
* ("PSF"), and the Individual or Organization ("Licensee") accessing and
* otherwise using this software ("Python") in source or binary form and
* its associated documentation.
*
* 2. Subject to the terms and conditions of this License Agreement, PSF hereby
* grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
* analyze, test, perform and/or display publicly, prepare derivative works,
* distribute, and otherwise use Python alone or in any derivative version,
* provided, however, that PSF's License Agreement and PSF's notice of copyright,
* i.e., "Copyright (c) 2001-2019, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
* Python Software Foundation; All Rights Reserved" are retained in Python alone or
* in any derivative version prepared by Licensee.
*
* 3. In the event Licensee prepares a derivative work that is based on
* or incorporates Python or any part thereof, and wants to make
* the derivative work available to others as provided herein, then
* Licensee hereby agrees to include in any such work a brief summary of
* the changes made to Python.
*
* 4. PSF is making Python available to Licensee on an "AS IS"
* basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
* IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
* DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
* FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
* INFRINGE ANY THIRD PARTY RIGHTS.
*
* 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
* FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
* A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
* OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
*
* 6. This License Agreement will automatically terminate upon a material
* breach of its terms and conditions.
*
* 7. Nothing in this License Agreement shall be deemed to create any
* relationship of agency, partnership, or joint venture between PSF and
* Licensee. This License Agreement does not grant permission to use PSF
* trademarks or trade name in a trademark sense to endorse or promote
* products or services of Licensee, or any third party.
*
* 8. By copying, installing or otherwise using Python, Licensee
* agrees to be bound by the terms and conditions of this License
* Agreement.
*/
#include <string.h>
#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"
#include "pyport.h"
BORROWED Py_LOCAL_INLINE(PyObject *)
getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx) {
Py_ssize_t argidx = *p_argidx;
if (argidx < arglen) {
(*p_argidx)++;
if (arglen < 0) return args;
else return PyTuple_GetItem(args, argidx);
}
return NULL;
}
for function getnextarg:
I delete the line including 'raise error', for making this func a iterator
just used to fill in the arguments array
*/
STEALS(1)
Py_LOCAL_INLINE(PyObject *)
resize_bytes(PyObject *b, Py_ssize_t newsize) {
if (0 == _Bytes_Resize(&b, newsize)) return b;
else return NULL;
}
PyObject *Bytes_Format(PyObject *format, PyObject *args, char place_holder) {
char *fmt, *res;
Py_ssize_t arglen, argidx;
Py_ssize_t reslen, rescnt, fmtcnt;
int args_owned = 0;
PyObject *result;
PyObject *dict = NULL;
PyObject *args_value = NULL;
char **args_list = NULL;
char *args_buffer = NULL;
Py_ssize_t * args_len = NULL;
int args_id = 0;
int index_type = 0;
int *arg_usecnt = NULL;
if (format == NULL || !Bytes_Check(format) || args == NULL) {
PyErr_SetString(PyExc_SystemError, "bad argument to internal function");
return NULL;
}
fmt = Bytes_AS_STRING(format);
fmtcnt = Bytes_GET_SIZE(format);
reslen = rescnt = 1;
while (reslen <= fmtcnt) {
reslen *= 2;
rescnt *= 2;
}
result = Bytes_FromStringAndSize((char *)NULL, reslen);
if (result == NULL) return NULL;
res = Bytes_AS_STRING(result);
if (PyTuple_Check(args)) {
arglen = PyTuple_GET_SIZE(args);
argidx = 0;
}
else {
arglen = -1;
argidx = -2;
}
if (Py_TYPE(args)->tp_as_mapping && !PyTuple_Check(args) && !PyObject_TypeCheck(args, &Bytes_Type)) {
dict = args;
}
while (--fmtcnt >= 0) {
if (*fmt != '%') {
if (--rescnt < 0) {
rescnt = reslen;
reslen *= 2;
if (!(result = resize_bytes(result, reslen))) {
return NULL;
}
res = Bytes_AS_STRING(result) + reslen - rescnt;
--rescnt;
}
*res++ = *fmt++;
}
else {
fmt++;
if (*fmt == '(') {
char *keystart;
Py_ssize_t keylen;
PyObject *key;
int pcount = 1;
Py_ssize_t length = 0;
if (dict == NULL) {
PyErr_SetString(PyExc_TypeError, "format requires a mapping");
goto error;
}
++fmt;
--fmtcnt;
keystart = fmt;
while (pcount > 0 && --fmtcnt >= 0) {
if (*fmt == ')') --pcount;
else if (*fmt == '(') ++pcount;
fmt++;
}
keylen = fmt - keystart - 1;
if (fmtcnt < 0 || pcount > 0 || *(fmt++) != 's') {
PyErr_SetString(PyExc_ValueError, "incomplete format key");
goto error;
}
--fmtcnt;
key = Text_FromUTF8AndSize(keystart, keylen);
if (key == NULL) goto error;
if (args_owned) {
Py_DECREF(args);
args_owned = 0;
}
args = PyObject_GetItem(dict, key);
Py_DECREF(key);
if (args == NULL) goto error;
if (!Bytes_CheckExact(args)) {
PyErr_Format(PyExc_ValueError, "only bytes values expected, got %s", Py_TYPE(args)->tp_name);
goto error;
}
args_buffer = Bytes_AS_STRING(args);
length = Bytes_GET_SIZE(args);
if (rescnt < length) {
while (rescnt < length) {
rescnt += reslen;
reslen *= 2;
}
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
}
res = Bytes_AS_STRING(result) + reslen - rescnt;
Py_MEMCPY(res, args_buffer, length);
rescnt -= length;
res += length;
args_owned = 1;
arglen = -1;
argidx = -2;
} else {
char c = *fmt;
if (!rescnt) {
rescnt += reslen;
reslen *= 2;
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result) + reslen - rescnt;
}
if (c == '%' || c == ' ') {
*res = '%';
--rescnt;
++res;
++fmt;
}
}
}
}
if (dict || (arglen < 0) || (argidx < 0)) {
if (args_owned) Py_DECREF(args);
if (!(result = resize_bytes(result, reslen - rescnt))) return NULL;
if (place_holder != '%') {
PyErr_SetString(PyExc_TypeError, "place holder only expect %% when using dict");
goto error;
}
return result;
}
args_list = (char **)malloc(sizeof(char *) * arglen);
if (args_list == NULL) {
PyErr_SetString(PyExc_ValueError, "the variable args_list is a null pointer");
goto error;
}
memset(args_list, 0, sizeof(char *) * arglen);
args_len = (Py_ssize_t *)malloc(sizeof(Py_ssize_t) * arglen);
if (args_len == NULL) {
PyErr_SetString(PyExc_ValueError, "the variable args_len is a null pointer");
goto error;
}
while ((args_value = getnextarg(args, arglen, &argidx)) != NULL) {
Py_ssize_t length = 0;
if (!Bytes_CheckExact(args_value)) {
PyErr_Format(PyExc_ValueError, "only bytes values expected, got %s", Py_TYPE(args_value)->tp_name);
goto error;
}
Py_INCREF(args_value);
args_buffer = Bytes_AS_STRING(args_value);
length = Bytes_GET_SIZE(args_value);
args_len[argidx - 1] = length;
args_list[argidx - 1] = (char *)malloc(sizeof(char) * (length + 1));
if (args_list[argidx - 1] == NULL) {
PyErr_SetString(PyExc_ValueError, "the variable args_list[argidx - 1] is a null pointer");
goto error;
}
Py_MEMCPY(args_list[argidx - 1], args_buffer, length);
args_list[argidx - 1][length] = '\0';
Py_XDECREF(args_value);
}
arg_usecnt = (int *)malloc(sizeof(int) * arglen);
if (arg_usecnt == NULL) {
PyErr_SetString(PyExc_ValueError, "the variable arg_usecnt is a null pointer");
goto error;
}
memset(arg_usecnt, 0, sizeof(int) * arglen);
fmt = Bytes_AS_STRING(format);
fmtcnt = Bytes_GET_SIZE(format);
reslen = rescnt = 1;
while (reslen <= fmtcnt) {
reslen *= 2;
rescnt *= 2;
}
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result);
memset(res, 0, sizeof(char) * reslen);
while (*fmt != '\0') {
if (*fmt != place_holder) {
if (!rescnt) {
rescnt += reslen;
reslen *= 2;
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result) + reslen - rescnt;
}
*(res++) = *(fmt++);
--rescnt;
continue;
}
if (*fmt == '%') {
char c = *(++fmt);
if (c == '\0') {
PyErr_SetString(PyExc_ValueError, "incomplete format");
goto error;
}
else if (c == '%' || c == ' ') {
if (!rescnt) {
rescnt += reslen;
reslen *= 2;
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result) + reslen - rescnt;
}
*res = '%';
--rescnt;
++res;
++fmt;
}
else if (c == 's') {
if (args_id >= arglen) {
PyErr_SetString(PyExc_TypeError, "arguments not enough during string formatting");
goto error;
}
if (rescnt < args_len[args_id]) {
while (rescnt < args_len[args_id]) {
rescnt += reslen;
reslen *= 2;
}
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result) + reslen - rescnt;
}
Py_MEMCPY(res, args_list[args_id], args_len[args_id]);
rescnt -= args_len[args_id];
res += args_len[args_id];
++arg_usecnt[args_id];
++args_id;
++fmt;
}
else {
PyErr_Format(PyExc_ValueError, "unsupported format character '%c' (0x%x) "
"at index " FORMAT_CODE_PY_SSIZE_T,
c, c,
(Py_ssize_t)(fmt - 1 - Bytes_AS_STRING(format)));
goto error;
}
continue;
}
if (*fmt == '$') {
char c = *(++fmt);
if (c == '\0') {
PyErr_SetString(PyExc_ValueError, "incomplete format");
goto error;
}
else if (c == '$') {
if (!rescnt) {
rescnt += reslen;
reslen *= 2;
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result) + reslen - rescnt;
}
*res = c;
--rescnt;
++res;
++fmt;
}
else if (isdigit(c)) {
int index = 0;
index_type = 1;
while (isdigit(*fmt)) {
index = index * 10 + (*fmt) -'0';
++fmt;
}
if ((index > arglen) || (index <= 0)) {
PyErr_SetString(PyExc_ValueError, "invalid index");
goto error;
}
--index;
if (rescnt < args_len[index]) {
while (rescnt < args_len[index]) {
rescnt += reslen;
reslen *= 2;
}
if ((result = resize_bytes(result, reslen)) == NULL) goto error;
res = Bytes_AS_STRING(result) + reslen - rescnt;
}
Py_MEMCPY(res, args_list[index], args_len[index]);
rescnt -= args_len[index];
res += args_len[index];
++arg_usecnt[index];
}
else {
PyErr_Format(PyExc_ValueError, "unsupported format character '%c' (0x%x) "
"at index " FORMAT_CODE_PY_SSIZE_T,
c, c,
(Py_ssize_t)(fmt - 1 - Bytes_AS_STRING(format)));
goto error;
}
}
}
if ((args_id < arglen) && (!dict) && (!index_type)) {
PyErr_SetString(PyExc_TypeError, "not all arguments converted during string formatting");
goto error;
}
for (args_id = 0; args_id < arglen; ++args_id) {
if (!arg_usecnt[args_id]) {
PyErr_SetString(PyExc_TypeError, "not all arguments converted during string formatting");
goto error;
}
}
if (args_list != NULL) {
while (--argidx >= 0) free(args_list[argidx]);
free(args_list), free(args_len), free(arg_usecnt);
}
if (args_owned) Py_DECREF(args);
if (!(result = resize_bytes(result, reslen - rescnt))) return NULL;
return result;
error:
if (args_list != NULL) {
while (--argidx >= 0) free(args_list[argidx]);
free(args_list), free(args_len), free(arg_usecnt);
}
Py_DECREF(result);
if (args_owned) Py_DECREF(args);
return NULL;
}