#include "Python.h"
-static int pytdbpack_calc_reqd_len(char *format_str,
- PyObject *val_seq);
+static PyObject * pytdbpack_number(char ch, PyObject *val_iter, PyObject *packed_list);
+static PyObject * pytdbpack_str_850(PyObject *val_iter, PyObject *packed_list);
+static PyObject * pytdbpack_buffer(PyObject *val_iter, PyObject *packed_list);
-static PyObject *pytdbpack_unpack_item(char,
- char **pbuf,
- int *plen);
+static PyObject *pytdbpack_unpack_item(char, char **pbuf, int *plen, PyObject *);
-static PyObject *pytdbpack_pack_data(const char *format_str,
+static PyObject *pytdbpack_data(const char *format_str,
PyObject *val_seq,
- unsigned char *buf);
-
+ PyObject *val_list);
+static void pack_le_uint32(unsigned long val_long, unsigned char *pbuf);
static PyObject *pytdbpack_bad_type(char ch,
Python strings are used to specify the format of data to be packed or
unpacked.
-Strings in TDBs are typically stored in DOS codepages. The caller of this
-module must make appropriate translations if necessary, typically to and from
-Unicode objects.
+Strings are always stored in codepage 850. Unicode objects are translated
+to cp850; plain strings are assumed to be in latin-1 and are also
+translated.
+
+This may be a problem in the future if it is different to the Samba codepage.
+It might be better to have the caller do the conversion, but that would conflict
+with existing CMI code.
tdbpack format strings:
- 'f': NULL-terminated string in DOS codepage
+ 'f': NULL-terminated string in codepage 850
'P': same as 'f'
- 'd': 4 byte little-endian number
+ 'd': 4 byte little-endian unsigned number
- 'w': 2 byte little-endian number
+ 'w': 2 byte little-endian unsigned number
'P': \"Pointer\" value -- in the subset of DCERPC used by Samba, this is
really just an \"exists\" or \"does not exist\" flag. The boolean
";
-static char const pytdbpack_pack_doc[] =
+static char const pytdbpack_doc[] =
"pack(format, values) -> buffer
Pack Python objects into Samba binary format according to format string.
+
/*
- Game plan is to first of all walk through the arguments and calculate the
- total length that will be required. We allocate a Python string of that
- size, then walk through again and fill it in.
-
- We just borrow references to all the passed arguments, since none of them
- need to be permanently stored. We transfer ownership to the returned
- object.
- */
+ * Pack objects to bytes.
+ *
+ * All objects are first individually encoded onto a list, and then the list
+ * of strings is concatenated. This is faster than concatenating strings,
+ * and reasonably simple to code.
+ */
static PyObject *
-pytdbpack_pack(PyObject *self,
+pytdbpack(PyObject *self,
PyObject *args)
{
char *format_str;
- PyObject *val_seq, *fast_seq, *buf_str;
- int reqd_len;
- char *packed_buf;
+ PyObject *val_seq, *val_iter = NULL,
+ *packed_list = NULL, *packed_str = NULL,
+ *empty_str = NULL;
/* TODO: Test passing wrong types or too many arguments */
if (!PyArg_ParseTuple(args, "sO", &format_str, &val_seq))
return NULL;
- /* Convert into a list or tuple (if not already one), so that we can
- * index more easily. */
- fast_seq = PySequence_Fast(val_seq,
- __FUNCTION__ ": argument 2 must be sequence");
- if (!fast_seq)
- return NULL;
-
- reqd_len = pytdbpack_calc_reqd_len(format_str, fast_seq);
- if (reqd_len == -1) /* exception was thrown */
- return NULL;
+ if (!(val_iter = PyObject_GetIter(val_seq)))
+ goto out;
- /* Allocate space.
-
- This design causes an unnecessary copying of the data when Python
- constructs an object, and that might possibly be avoided by using a
- Buffer object of some kind instead. I'm not doing that for now
- though. */
- packed_buf = malloc(reqd_len);
- if (!packed_buf) {
- PyErr_Format(PyExc_MemoryError,
- "%s: couldn't allocate %d bytes for packed buffer",
- __FUNCTION__, reqd_len);
- return NULL;
- }
-
- if (!pytdbpack_pack_data(format_str, fast_seq, packed_buf)) {
- free(packed_buf);
- return NULL;
- }
+ /* Create list to hold strings until we're done, then join them all. */
+ if (!(packed_list = PyList_New(0)))
+ goto out;
- buf_str = PyString_FromStringAndSize(packed_buf, reqd_len);
- free(packed_buf); /* get rid of tmp buf */
+ if (!pytdbpack_data(format_str, val_iter, packed_list))
+ goto out;
+
+ /* this function is not officially documented but it works */
+ if (!(empty_str = PyString_InternFromString("")))
+ goto out;
- return buf_str;
+ packed_str = _PyString_Join(empty_str, packed_list);
+
+ out:
+ Py_XDECREF(empty_str);
+ Py_XDECREF(val_iter);
+ Py_XDECREF(packed_list);
+
+ return packed_str;
}
+/*
+ Pack data according to FORMAT_STR from the elements of VAL_SEQ into
+ PACKED_BUF.
-static PyObject *
-pytdbpack_unpack(PyObject *self,
- PyObject *args)
+ The string has already been checked out, so we know that VAL_SEQ is large
+ enough to hold the packed data, and that there are enough value items.
+ (However, their types may not have been thoroughly checked yet.)
+
+ In addition, val_seq is a Python Fast sequence.
+
+ Returns NULL for error (with exception set), or None.
+*/
+PyObject *
+pytdbpack_data(const char *format_str,
+ PyObject *val_iter,
+ PyObject *packed_list)
{
- char *format_str, *packed_str, *ppacked;
- PyObject *val_list = NULL, *ret_tuple = NULL;
- PyObject *rest_string = NULL;
- int format_len, packed_len;
- int i;
- char last_format = '#';
-
- /* get arguments */
- if (!PyArg_ParseTuple(args, "ss#", &format_str, &packed_str, &packed_len))
- return NULL;
+ int format_i, val_i = 0;
+
+ for (format_i = 0, val_i = 0; format_str[format_i]; format_i++) {
+ char ch = format_str[format_i];
+
+ switch (ch) {
+ /* dispatch to the appropriate packer for this type,
+ which should pull things off the iterator, and
+ append them to the packed_list */
+ case 'w':
+ case 'd':
+ case 'p':
+ if (!(packed_list = pytdbpack_number(ch, val_iter, packed_list)))
+ return NULL;
+ break;
- format_len = strlen(format_str);
-
- /* allocate list to hold results */
- val_list = PyList_New(format_len);
- if (!val_list)
- goto failed;
- ret_tuple = PyTuple_New(2);
- if (!ret_tuple)
- goto failed;
-
- /* For every object, unpack. */
- for (ppacked = packed_str, i = 0; i < format_len; i++) {
- PyObject *val_obj;
- char format;
-
- format = format_str[i];
- if (format == '$') {
- if (i == 0) {
- PyErr_Format(PyExc_ValueError,
- "%s: '$' may not be first character in format",
- __FUNCTION__);
- goto failed;
- }
- else {
- format = last_format; /* repeat */
- }
+ case 'f':
+ case 'P':
+ if (!(packed_list = pytdbpack_str_850(val_iter, packed_list)))
+ return NULL;
+ break;
+
+ case 'B':
+ if (!(packed_list = pytdbpack_buffer(val_iter, packed_list)))
+ return NULL;
+ break;
+
+ default:
+ PyErr_Format(PyExc_ValueError,
+ "%s: format character '%c' is not supported",
+ __FUNCTION__, ch);
+ return NULL;
}
+ }
- val_obj = pytdbpack_unpack_item(format,
- &ppacked,
- &packed_len);
- if (!val_obj)
- goto failed;
+ return packed_list;
+}
+
+
+static PyObject *
+pytdbpack_number(char ch, PyObject *val_iter, PyObject *packed_list)
+{
+ unsigned long val_long;
+ PyObject *val_obj = NULL, *long_obj = NULL, *result_obj = NULL;
+ PyObject *new_list = NULL;
+ unsigned char pack_buf[4];
+
+ if (!(val_obj = PyIter_Next(val_iter)))
+ goto out;
- PyList_SET_ITEM(val_list, i, val_obj);
- last_format = format;
+ if (!(long_obj = PyNumber_Long(val_obj))) {
+ pytdbpack_bad_type(ch, "Number", val_obj);
+ goto out;
}
- /* save leftovers for next time */
- rest_string = PyString_FromStringAndSize(ppacked, packed_len);
- if (!rest_string)
- goto failed;
+ val_long = PyLong_AsUnsignedLong(long_obj);
+ pack_le_uint32(val_long, pack_buf);
- /* return (values, rest) tuple; give up references to them */
- PyTuple_SET_ITEM(ret_tuple, 0, val_list);
- val_list = NULL;
- PyTuple_SET_ITEM(ret_tuple, 1, rest_string);
- val_list = NULL;
- return ret_tuple;
+ /* pack as 32-bit; if just packing a 'w' 16-bit word then only take
+ the first two bytes. */
+
+ if (!(result_obj = PyString_FromStringAndSize(pack_buf, ch == 'w' ? 2 : 4)))
+ goto out;
- failed:
- /* handle failure: deallocate anything */
- Py_XDECREF(val_list);
- Py_XDECREF(ret_tuple);
- Py_XDECREF(rest_string);
- return NULL;
+ if (PyList_Append(packed_list, result_obj) != -1)
+ new_list = packed_list;
+
+ out:
+ Py_XDECREF(val_obj);
+ Py_XDECREF(long_obj);
+ Py_XDECREF(result_obj);
+
+ return new_list;
}
/*
- Internal routine that calculates how many bytes will be required to
- encode the values in the format.
+ * Take one string from the iterator val_iter, convert it to 8-bit CP850, and
+ * return it.
+ *
+ * If the input is neither a string nor Unicode, an exception is raised.
+ *
+ * If the input is Unicode, then it is converted to CP850.
+ *
+ * If the input is a String, then it is converted to Unicode using the default
+ * decoding method, and then converted to CP850. This in effect gives
+ * conversion from latin-1 (currently the PSA's default) to CP850, without
+ * needing a custom translation table.
+ *
+ * I hope this approach avoids being too fragile w.r.t. being passed either
+ * Unicode or String objects.
+ */
+static PyObject *
+pytdbpack_str_850(PyObject *val_iter, PyObject *packed_list)
+{
+ PyObject *val_obj = NULL;
+ PyObject *unicode_obj = NULL;
+ PyObject *cp850_str = NULL;
+ PyObject *nul_str = NULL;
+ PyObject *new_list = NULL;
- Also checks that the value list is the right size for the format list.
+ if (!(val_obj = PyIter_Next(val_iter)))
+ goto out;
- Returns number of bytes (may be 0), or -1 if there's something wrong, in
- which case a Python exception has been raised.
+ if (PyUnicode_Check(val_obj)) {
+ unicode_obj = val_obj;
+ }
+ else {
+ /* string */
+ if (!(unicode_obj = PyString_AsDecodedObject(val_obj, NULL, NULL)))
+ goto out;
+ Py_XDECREF(val_obj);
+ val_obj = NULL;
+ }
- Arguments:
+ if (!(cp850_str = PyUnicode_AsEncodedString(unicode_obj, "cp850", NULL)))
+ goto out;
- val_seq: a Fast Sequence (list or tuple), being all the values
-*/
-static int
-pytdbpack_calc_reqd_len(char *format_str,
- PyObject *val_seq)
-{
- int len = 0;
- char *p;
- int val_i;
- int val_len;
+ if (!nul_str)
+ /* this is constant and often-used; hold it forever */
+ if (!(nul_str = PyString_FromStringAndSize("", 1)))
+ goto out;
- val_len = PySequence_Length(val_seq);
- if (val_len == -1)
- return -1;
+ if ((PyList_Append(packed_list, cp850_str) != -1)
+ && (PyList_Append(packed_list, nul_str) != -1))
+ new_list = packed_list;
- for (p = format_str, val_i = 0; *p; p++, val_i++) {
- char ch = *p;
+ out:
+ Py_XDECREF(unicode_obj);
+ Py_XDECREF(cp850_str);
- if (val_i >= val_len) {
- PyErr_Format(PyExc_IndexError,
- "%s: value list is too short for format string",
- __FUNCTION__);
- return -1;
- }
+ return new_list;
+}
- /* borrow a reference to the item */
- if (ch == 'd' || ch == 'p')
- len += 4;
- else if (ch == 'w')
- len += 2;
- else if (ch == 'f' || ch == 'P') {
- /* nul-terminated 8-bit string */
- int item_len;
- PyObject *str_obj;
-
- str_obj = PySequence_GetItem(val_seq, val_i);
- if (!str_obj)
- return -1;
-
- if (!PyString_Check(str_obj) || ((item_len = PyString_Size(str_obj)) == -1)) {
- pytdbpack_bad_type(ch, "String", str_obj);
- return -1;
- }
-
- len += 1 + item_len;
- }
- else if (ch == 'B') {
- /* length-preceded byte buffer: n bytes, plus a preceding
- * word */
- PyObject *len_obj;
- long len_val;
-
- len_obj = PySequence_GetItem(val_seq, val_i);
- val_i++; /* skip over buffer */
-
- if (!PyNumber_Check(len_obj)) {
- pytdbpack_bad_type(ch, "Number", len_obj);
- return -1;
- }
-
- len_val = PyInt_AsLong(len_obj);
- if (len_val < 0) {
- PyErr_Format(PyExc_ValueError,
- "%s: format 'B' requires positive integer", __FUNCTION__);
- return -1;
- }
-
- len += 4 + len_val;
- }
- else {
- PyErr_Format(PyExc_ValueError,
- "%s: format character '%c' is not supported",
- __FUNCTION__, ch);
-
- return -1;
- }
+
+/*
+ * Pack (LENGTH, BUFFER) pair onto the list.
+ *
+ * The buffer must already be a String, not Unicode, because it contains 8-bit
+ * untranslated data. In some cases it will actually be UTF_16_LE data.
+ */
+static PyObject *
+pytdbpack_buffer(PyObject *val_iter, PyObject *packed_list)
+{
+ PyObject *val_obj;
+ PyObject *new_list = NULL;
+
+ /* pull off integer and stick onto list */
+ if (!(packed_list = pytdbpack_number('d', val_iter, packed_list)))
+ return NULL;
+
+ /* this assumes that the string is the right length; the old code did the same. */
+ if (!(val_obj = PyIter_Next(val_iter)))
+ return NULL;
+
+ if (!PyString_Check(val_obj)) {
+ pytdbpack_bad_type('B', "String", val_obj);
+ goto out;
}
+
+ if (PyList_Append(packed_list, val_obj) != -1)
+ new_list = packed_list;
- return len;
+ out:
+ Py_XDECREF(val_obj);
+ return new_list;
}
realize this is kind of dumb because we'll almost always be on x86, but
being safe is important.
*/
-static void pack_int32(unsigned long val_long, unsigned char **pbuf)
+static void pack_le_uint32(unsigned long val_long, unsigned char *pbuf)
{
- (*pbuf)[0] = val_long & 0xff;
- (*pbuf)[1] = (val_long >> 8) & 0xff;
- (*pbuf)[2] = (val_long >> 16) & 0xff;
- (*pbuf)[3] = (val_long >> 24) & 0xff;
- (*pbuf) += 4;
+ pbuf[0] = val_long & 0xff;
+ pbuf[1] = (val_long >> 8) & 0xff;
+ pbuf[2] = (val_long >> 16) & 0xff;
+ pbuf[3] = (val_long >> 24) & 0xff;
}
}
+
+static PyObject *
+pytdbpack_unpack(PyObject *self,
+ PyObject *args)
+{
+ char *format_str, *packed_str, *ppacked;
+ PyObject *val_list = NULL, *ret_tuple = NULL;
+ PyObject *rest_string = NULL;
+ int format_len, packed_len;
+ char last_format = '#'; /* invalid */
+ int i;
+
+ /* get arguments */
+ if (!PyArg_ParseTuple(args, "ss#", &format_str, &packed_str, &packed_len))
+ return NULL;
+
+ format_len = strlen(format_str);
+
+ /* Allocate list to hold results. Initially empty, and we append
+ results as we go along. */
+ val_list = PyList_New(0);
+ if (!val_list)
+ goto failed;
+ ret_tuple = PyTuple_New(2);
+ if (!ret_tuple)
+ goto failed;
+
+ /* For every object, unpack. */
+ for (ppacked = packed_str, i = 0; i < format_len && format_str[i] != '$'; i++) {
+ last_format = format_str[i];
+ /* packed_len is reduced in place */
+ if (!pytdbpack_unpack_item(format_str[i], &ppacked, &packed_len, val_list))
+ goto failed;
+ }
+
+ /* If the last character was '$', keep going until out of space */
+ if (format_str[i] == '$') {
+ if (i == 0) {
+ PyErr_Format(PyExc_ValueError,
+ "%s: '$' may not be first character in format",
+ __FUNCTION__);
+ return NULL;
+ }
+ while (packed_len > 0)
+ if (!pytdbpack_unpack_item(last_format, &ppacked, &packed_len, val_list))
+ goto failed;
+ }
+
+ /* save leftovers for next time */
+ rest_string = PyString_FromStringAndSize(ppacked, packed_len);
+ if (!rest_string)
+ goto failed;
+
+ /* return (values, rest) tuple; give up references to them */
+ PyTuple_SET_ITEM(ret_tuple, 0, val_list);
+ val_list = NULL;
+ PyTuple_SET_ITEM(ret_tuple, 1, rest_string);
+ val_list = NULL;
+ return ret_tuple;
+
+ failed:
+ /* handle failure: deallocate anything. XDECREF forms handle NULL
+ pointers for objects that haven't been allocated yet. */
+ Py_XDECREF(val_list);
+ Py_XDECREF(ret_tuple);
+ Py_XDECREF(rest_string);
+ return NULL;
+}
+
+
static void
unpack_err_too_short(void)
{
static PyObject *
-unpack_int32(char **pbuf, int *plen)
+unpack_uint32(char **pbuf, int *plen)
{
- long v;
+ unsigned long v;
unsigned char *b;
if (*plen < 4) {
(*pbuf) += 4;
(*plen) -= 4;
- return PyInt_FromLong(v);
+ return PyLong_FromUnsignedLong(v);
}
static PyObject *
-unpack_buffer(char **pbuf, int *plen)
+unpack_buffer(char **pbuf, int *plen, PyObject *val_list)
{
/* first get 32-bit len */
long slen;
unsigned char *b;
unsigned char *start;
+ PyObject *str_obj = NULL, *len_obj = NULL;
if (*plen < 4) {
unpack_err_too_short();
(*pbuf) += slen;
(*plen) -= slen;
- return PyString_FromStringAndSize(start, slen);
+ if (!(len_obj = PyInt_FromLong(slen)))
+ goto failed;
+
+ if (PyList_Append(val_list, len_obj) == -1)
+ goto failed;
+
+ if (!(str_obj = PyString_FromStringAndSize(start, slen)))
+ goto failed;
+
+ if (PyList_Append(val_list, str_obj) == -1)
+ goto failed;
+
+ return val_list;
+
+ failed:
+ Py_XDECREF(len_obj); /* handles NULL */
+ Py_XDECREF(str_obj);
+ return NULL;
}
*PBUF is advanced, and *PLEN reduced to reflect the amount of data that has
been consumed.
- Returns a reference to the unpacked Python object, or NULL for failure.
+ Returns a reference to None, or NULL for failure.
*/
static PyObject *pytdbpack_unpack_item(char ch,
char **pbuf,
- int *plen)
+ int *plen,
+ PyObject *val_list)
{
+ PyObject *result;
+
if (ch == 'w') { /* 16-bit int */
- return unpack_int16(pbuf, plen);
+ result = unpack_int16(pbuf, plen);
}
else if (ch == 'd' || ch == 'p') { /* 32-bit int */
/* pointers can just come through as integers */
- return unpack_int32(pbuf, plen);
+ result = unpack_uint32(pbuf, plen);
}
else if (ch == 'f' || ch == 'P') { /* nul-term string */
- return unpack_string(pbuf, plen);
+ result = unpack_string(pbuf, plen);
}
else if (ch == 'B') { /* length, buffer */
- return unpack_buffer(pbuf, plen);
+ return unpack_buffer(pbuf, plen, val_list);
}
else {
PyErr_Format(PyExc_ValueError,
return NULL;
}
-}
-
-
-
-/*
- Pack data according to FORMAT_STR from the elements of VAL_SEQ into
- PACKED_BUF.
-
- The string has already been checked out, so we know that VAL_SEQ is large
- enough to hold the packed data, and that there are enough value items.
- (However, their types may not have been thoroughly checked yet.)
-
- In addition, val_seq is a Python Fast sequence.
-
- Returns NULL for error (with exception set), or None.
-*/
-PyObject *
-pytdbpack_pack_data(const char *format_str,
- PyObject *val_seq,
- unsigned char *packed)
-{
- int i;
-
- for (i = 0; format_str[i]; i++) {
- char ch = format_str[i];
- PyObject *val_obj;
-
- /* borrow a reference to the item */
- val_obj = PySequence_GetItem(val_seq, i);
- if (!val_obj)
- return NULL;
+ /* otherwise OK */
+ if (!result)
+ return NULL;
+ if (PyList_Append(val_list, result) == -1)
+ return NULL;
+
+ return val_list;
+}
- if (ch == 'w') {
- unsigned long val_long = PyInt_AsLong(val_obj);
- (packed)[0] = val_long & 0xff;
- (packed)[1] = (val_long >> 8) & 0xff;
- (packed) += 2;
- }
- else if (ch == 'd') {
- /* 4-byte LE number */
- pack_int32(PyInt_AsLong(val_obj), &packed);
- }
- else if (ch == 'p') {
- /* "Pointer" value -- in the subset of DCERPC used by Samba,
- this is really just an "exists" or "does not exist"
- flag. */
- pack_int32(PyObject_IsTrue(val_obj), &packed);
- }
- else if (ch == 'f' || ch == 'P') {
- int size;
- char *sval;
- size = PySequence_Length(val_obj);
- if (size < 0)
- return NULL;
- sval = PyString_AsString(val_obj);
- if (!sval)
- return NULL;
- pack_bytes(size+1, sval, &packed); /* include nul */
- }
- else if (ch == 'B') {
- long size;
- char *sval;
- size = PyInt_AsLong(val_obj);
- pack_int32(size, &packed);
-
- val_obj = PySequence_GetItem(val_seq, ++i);
- if (!val_obj)
- return NULL;
-
- sval = PyString_AsString(val_obj);
- if (!sval)
- return NULL;
-
- pack_bytes(size, sval, &packed); /* do not include nul */
- }
- else {
- /* this ought to be caught while calculating the length, but
- just in case. */
- PyErr_Format(PyExc_ValueError,
- "%s: format character '%c' is not supported",
- __FUNCTION__, ch);
-
- return NULL;
- }
- }
-
- return Py_None;
-}
static PyMethodDef pytdbpack_methods[] = {
- { "pack", pytdbpack_pack, METH_VARARGS, (char *) pytdbpack_pack_doc },
+ { "pack", pytdbpack, METH_VARARGS, (char *) pytdbpack_doc },
{ "unpack", pytdbpack_unpack, METH_VARARGS, (char *) pytdbpack_unpack_doc },
};