Skip to content

Commit a3362b1

Browse files
committed
feat(ldap.dn): Add support for different formats in ldap.dn2str() via flags
In C `dn2str()` supports `flags` which works by providing one of `LDAP_DN_FORMAT_UFN`, `LDAP_DN_FORMAT_AD_CANONICAL`, `LDAP_DN_FORMAT_DCE`, `LDAP_DN_FORMAT_LDAPV3`. These symbols do exist in Python, but could not be used ultimately because the Python counterpart was pure Python and did not pass to `dn2str(3)`. Fix #257
1 parent f8e1d6e commit a3362b1

File tree

2 files changed

+223
-9
lines changed

2 files changed

+223
-9
lines changed

Lib/ldap/dn.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,19 @@ def str2dn(dn,flags=0):
4848
return ldap.functions._ldap_function_call(None,_ldap.str2dn,dn,flags)
4949

5050

51-
def dn2str(dn):
51+
def dn2str(dn, flags=_ldap.DN_FORMAT_LDAPV3):
5252
"""
5353
This function takes a decomposed DN as parameter and returns
54-
a single string. It's the inverse to str2dn() but will always
55-
return a DN in LDAPv3 format compliant to RFC 4514.
54+
a single string. It's the inverse to str2dn() but will by default always
55+
return a DN in LDAPv3 format compliant to RFC 4514 if not otherwise specified
56+
via flags.
57+
58+
See also the OpenLDAP man-page ldap_dn2str(3)
5659
"""
57-
return ','.join([
58-
'+'.join([
59-
'='.join((atype,escape_dn_chars(avalue or '')))
60-
for atype,avalue,dummy in rdn])
61-
for rdn in dn
62-
])
60+
if dn is None:
61+
return ''
62+
return ldap.functions._ldap_function_call(None, _ldap.dn2str, dn, flags)
63+
6364

6465
def explode_dn(dn, notypes=False, flags=0):
6566
"""

Modules/functions.c

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,218 @@ l_ldap_str2dn(PyObject *unused, PyObject *args)
160160
return result;
161161
}
162162

163+
/* ldap_dn2str */
164+
165+
static void
166+
_free_dn_structure(LDAPDN dn)
167+
{
168+
if (dn == NULL)
169+
return;
170+
171+
for (LDAPRDN * rdn = dn; *rdn != NULL; rdn++) {
172+
for (LDAPAVA ** avap = *rdn; *avap != NULL; avap++) {
173+
LDAPAVA *ava = *avap;
174+
175+
if (ava->la_attr.bv_val) {
176+
free(ava->la_attr.bv_val);
177+
}
178+
if (ava->la_value.bv_val) {
179+
free(ava->la_value.bv_val);
180+
}
181+
free(ava);
182+
}
183+
free(*rdn);
184+
}
185+
free(dn);
186+
}
187+
188+
/*
189+
* Convert a Python list-of-list-of-(str, str, int) into an LDAPDN and
190+
* call ldap_dn2bv to build a DN string.
191+
*
192+
* Python signature: dn2str(dn: list[list[tuple[str, str, int]]], flags: int) -> str
193+
* Returns the DN string on success, or raises TypeError or RuntimeError on error.
194+
*/
195+
static PyObject *
196+
l_ldap_dn2str(PyObject *self, PyObject *args)
197+
{
198+
PyObject *dn_list = NULL;
199+
int flags = 0;
200+
LDAPDN dn = NULL;
201+
BerValue str = { 0, NULL };
202+
PyObject *py_rdn_seq = NULL, *py_ava_item = NULL;
203+
PyObject *py_name = NULL, *py_value = NULL, *py_encoding = NULL;
204+
PyObject *result = NULL;
205+
Py_ssize_t nrdns = 0, navas = 0;
206+
int i = 0, j = 0;
207+
int ldap_err;
208+
209+
const char *type_error_message = "expected list[list[tuple[str, str, int]]]";
210+
211+
if (!PyArg_ParseTuple(args, "Oi:dn2str", &dn_list, &flags)) {
212+
return NULL;
213+
}
214+
215+
if (!PySequence_Check(dn_list)) {
216+
PyErr_SetString(PyExc_TypeError, type_error_message);
217+
return NULL;
218+
}
219+
220+
nrdns = PySequence_Size(dn_list);
221+
if (nrdns < 0) {
222+
PyErr_SetString(PyExc_TypeError, type_error_message);
223+
return NULL;
224+
}
225+
226+
/* Allocate array of LDAPRDN pointers (+1 for NULL terminator) */
227+
dn = (LDAPRDN *) calloc((size_t)nrdns + 1, sizeof(LDAPRDN));
228+
if (dn == NULL) {
229+
PyErr_NoMemory();
230+
return NULL;
231+
}
232+
233+
for (i = 0; i < nrdns; i++) {
234+
py_rdn_seq = PySequence_GetItem(dn_list, i); /* New reference */
235+
if (py_rdn_seq == NULL) {
236+
goto error_cleanup;
237+
}
238+
if (!PySequence_Check(py_rdn_seq)) {
239+
PyErr_SetString(PyExc_TypeError, type_error_message);
240+
goto error_cleanup;
241+
}
242+
243+
navas = PySequence_Size(py_rdn_seq);
244+
if (navas < 0) {
245+
PyErr_SetString(PyExc_TypeError, type_error_message);
246+
goto error_cleanup;
247+
}
248+
249+
/* Allocate array of LDAPAVA* pointers (+1 for NULL terminator) */
250+
LDAPAVA **rdn = (LDAPAVA **)calloc((size_t)navas + 1, sizeof(LDAPAVA *));
251+
if (rdn == NULL) {
252+
PyErr_NoMemory();
253+
goto error_cleanup;
254+
}
255+
256+
for (j = 0; j < navas; j++) {
257+
py_ava_item = PySequence_GetItem(py_rdn_seq, j); /* New reference */
258+
if (py_ava_item == NULL) {
259+
goto error_cleanup;
260+
}
261+
/* Expect a 3‐tuple: (name: str, value: str, encoding: int) */
262+
if (!PyTuple_Check(py_ava_item) || PyTuple_Size(py_ava_item) != 3) {
263+
PyErr_SetString(PyExc_TypeError, type_error_message);
264+
goto error_cleanup;
265+
}
266+
267+
py_name = PyTuple_GetItem(py_ava_item, 0); /* Borrowed reference */
268+
py_value = PyTuple_GetItem(py_ava_item, 1); /* Borrowed reference */
269+
py_encoding = PyTuple_GetItem(py_ava_item, 2); /* Borrowed reference */
270+
271+
if (!PyUnicode_Check(py_name) || !PyUnicode_Check(py_value) || !PyLong_Check(py_encoding)) {
272+
PyErr_SetString(PyExc_TypeError, type_error_message);
273+
goto error_cleanup;
274+
}
275+
276+
Py_ssize_t name_len = 0, value_len = 0;
277+
const char *name_utf8 = PyUnicode_AsUTF8AndSize(py_name, &name_len);
278+
const char *value_utf8 = PyUnicode_AsUTF8AndSize(py_value, &value_len);
279+
if (name_utf8 == NULL || value_utf8 == NULL) {
280+
goto error_cleanup;
281+
}
282+
283+
LDAPAVA *ava = (LDAPAVA *) calloc(1, sizeof(LDAPAVA));
284+
285+
if (ava == NULL) {
286+
PyErr_NoMemory();
287+
goto error_cleanup;
288+
}
289+
290+
ava->la_attr.bv_val = (char *)malloc((size_t)name_len + 1);
291+
if (ava->la_attr.bv_val == NULL) {
292+
free(ava);
293+
PyErr_NoMemory();
294+
goto error_cleanup;
295+
}
296+
memcpy(ava->la_attr.bv_val, name_utf8, (size_t)name_len);
297+
ava->la_attr.bv_val[name_len] = '\0';
298+
ava->la_attr.bv_len = (ber_len_t) name_len;
299+
300+
ava->la_value.bv_val = (char *)malloc((size_t)value_len + 1);
301+
if (ava->la_value.bv_val == NULL) {
302+
free(ava->la_attr.bv_val);
303+
free(ava);
304+
PyErr_NoMemory();
305+
goto error_cleanup;
306+
}
307+
memcpy(ava->la_value.bv_val, value_utf8, (size_t)value_len);
308+
ava->la_value.bv_val[value_len] = '\0';
309+
ava->la_value.bv_len = (ber_len_t) value_len;
310+
311+
ava->la_flags = (int)PyLong_AsLong(py_encoding);
312+
if (PyErr_Occurred()) {
313+
/* Encoding conversion failed */
314+
free(ava->la_attr.bv_val);
315+
free(ava->la_value.bv_val);
316+
free(ava);
317+
goto error_cleanup;
318+
}
319+
320+
rdn[j] = ava;
321+
Py_DECREF(py_ava_item);
322+
py_ava_item = NULL;
323+
}
324+
325+
/* Null‐terminate the RDN */
326+
rdn[navas] = NULL;
327+
328+
dn[i] = rdn;
329+
Py_DECREF(py_rdn_seq);
330+
py_rdn_seq = NULL;
331+
}
332+
333+
/* Null‐terminate the DN */
334+
dn[nrdns] = NULL;
335+
336+
/* Call ldap_dn2bv to build a DN string */
337+
ldap_err = ldap_dn2bv(dn, &str, flags);
338+
if (ldap_err != LDAP_SUCCESS) {
339+
PyErr_SetString(PyExc_RuntimeError, ldap_err2string(ldap_err));
340+
goto error_cleanup;
341+
}
342+
343+
result = PyUnicode_FromString(str.bv_val);
344+
if (result == NULL) {
345+
goto error_cleanup;
346+
}
347+
348+
/* Free the memory allocated by ldap_dn2bv */
349+
ldap_memfree(str.bv_val);
350+
str.bv_val = NULL;
351+
352+
/* Free our local DN structure */
353+
_free_dn_structure(dn);
354+
dn = NULL;
355+
356+
return result;
357+
358+
error_cleanup:
359+
/* Free any partially built DN structure */
360+
_free_dn_structure(dn);
361+
dn = NULL;
362+
363+
/* If ldap_dn2bv allocated something, free it */
364+
if (str.bv_val) {
365+
ldap_memfree(str.bv_val);
366+
str.bv_val = NULL;
367+
}
368+
369+
/* Cleanup Python temporaries */
370+
Py_XDECREF(py_ava_item);
371+
Py_XDECREF(py_rdn_seq);
372+
return NULL;
373+
}
374+
163375
/* ldap_set_option (global options) */
164376

165377
static PyObject *
@@ -196,6 +408,7 @@ static PyMethodDef methods[] = {
196408
{"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS},
197409
#endif
198410
{"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS},
411+
{"dn2str", (PyCFunction)l_ldap_dn2str, METH_VARARGS},
199412
{"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS},
200413
{"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS},
201414
{NULL, NULL}

0 commit comments

Comments
 (0)