Skip to content

Commit cfa6a15

Browse files
committed
feat(ldap.dn): Add support for different formats in ldap.dn.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 dbf100f commit cfa6a15

File tree

2 files changed

+225
-3
lines changed

2 files changed

+225
-3
lines changed

Lib/ldap/dn.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,25 @@ 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=0):
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
"""
60+
if flags:
61+
return ldap.functions._ldap_function_call(None, _ldap.dn2str, dn, flags)
5762
return ','.join([
5863
'+'.join([
5964
'='.join((atype,escape_dn_chars(avalue or '')))
6065
for atype,avalue,dummy in rdn])
6166
for rdn in dn
6267
])
6368

69+
6470
def explode_dn(dn, notypes=False, flags=0):
6571
"""
6672
explode_dn(dn [, notypes=False [, flags=0]]) -> list

Modules/functions.c

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,221 @@ 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+
LDAPAVA *ava;
202+
LDAPAVA **rdn;
203+
BerValue str = { 0, NULL };
204+
PyObject *py_rdn_seq = NULL, *py_ava_item = NULL;
205+
PyObject *py_name = NULL, *py_value = NULL, *py_encoding = NULL;
206+
PyObject *result = NULL;
207+
Py_ssize_t nrdns = 0, navas = 0, name_len = 0, value_len = 0;
208+
int i = 0, j = 0;
209+
int ldap_err;
210+
211+
const char *type_error_message = "expected list[list[tuple[str, str, int]]]";
212+
213+
if (!PyArg_ParseTuple(args, "Oi:dn2str", &dn_list, &flags)) {
214+
return NULL;
215+
}
216+
217+
if (!PySequence_Check(dn_list)) {
218+
PyErr_SetString(PyExc_TypeError, type_error_message);
219+
return NULL;
220+
}
221+
222+
nrdns = PySequence_Size(dn_list);
223+
if (nrdns < 0) {
224+
PyErr_SetString(PyExc_TypeError, type_error_message);
225+
return NULL;
226+
}
227+
228+
/* Allocate array of LDAPRDN pointers (+1 for NULL terminator) */
229+
dn = (LDAPRDN *) calloc((size_t)nrdns + 1, sizeof(LDAPRDN));
230+
if (dn == NULL) {
231+
PyErr_NoMemory();
232+
return NULL;
233+
}
234+
235+
for (i = 0; i < nrdns; i++) {
236+
py_rdn_seq = PySequence_GetItem(dn_list, i); /* New reference */
237+
if (py_rdn_seq == NULL) {
238+
goto error_cleanup;
239+
}
240+
if (!PySequence_Check(py_rdn_seq)) {
241+
PyErr_SetString(PyExc_TypeError, type_error_message);
242+
goto error_cleanup;
243+
}
244+
245+
navas = PySequence_Size(py_rdn_seq);
246+
if (navas < 0) {
247+
PyErr_SetString(PyExc_TypeError, type_error_message);
248+
goto error_cleanup;
249+
}
250+
251+
/* Allocate array of LDAPAVA* pointers (+1 for NULL terminator) */
252+
rdn = (LDAPAVA **)calloc((size_t)navas + 1, sizeof(LDAPAVA *));
253+
if (rdn == NULL) {
254+
PyErr_NoMemory();
255+
goto error_cleanup;
256+
}
257+
258+
for (j = 0; j < navas; j++) {
259+
py_ava_item = PySequence_GetItem(py_rdn_seq, j); /* New reference */
260+
if (py_ava_item == NULL) {
261+
goto error_cleanup;
262+
}
263+
/* Expect a 3‐tuple: (name: str, value: str, encoding: int) */
264+
if (!PyTuple_Check(py_ava_item) || PyTuple_Size(py_ava_item) != 3) {
265+
PyErr_SetString(PyExc_TypeError, type_error_message);
266+
goto error_cleanup;
267+
}
268+
269+
py_name = PyTuple_GetItem(py_ava_item, 0); /* Borrowed reference */
270+
py_value = PyTuple_GetItem(py_ava_item, 1); /* Borrowed reference */
271+
py_encoding = PyTuple_GetItem(py_ava_item, 2); /* Borrowed reference */
272+
273+
if (!PyUnicode_Check(py_name) || !PyUnicode_Check(py_value) || !PyLong_Check(py_encoding)) {
274+
PyErr_SetString(PyExc_TypeError, type_error_message);
275+
goto error_cleanup;
276+
}
277+
278+
name_len = 0;
279+
value_len = 0;
280+
const char *name_utf8 = PyUnicode_AsUTF8AndSize(py_name, &name_len);
281+
const char *value_utf8 = PyUnicode_AsUTF8AndSize(py_value, &value_len);
282+
if (name_utf8 == NULL || value_utf8 == NULL) {
283+
goto error_cleanup;
284+
}
285+
286+
ava = (LDAPAVA *) calloc(1, sizeof(LDAPAVA));
287+
288+
if (ava == NULL) {
289+
PyErr_NoMemory();
290+
goto error_cleanup;
291+
}
292+
293+
ava->la_attr.bv_val = (char *)malloc((size_t)name_len + 1);
294+
if (ava->la_attr.bv_val == NULL) {
295+
free(ava);
296+
PyErr_NoMemory();
297+
goto error_cleanup;
298+
}
299+
memcpy(ava->la_attr.bv_val, name_utf8, (size_t)name_len);
300+
ava->la_attr.bv_val[name_len] = '\0';
301+
ava->la_attr.bv_len = (ber_len_t) name_len;
302+
303+
ava->la_value.bv_val = (char *)malloc((size_t)value_len + 1);
304+
if (ava->la_value.bv_val == NULL) {
305+
free(ava->la_attr.bv_val);
306+
free(ava);
307+
PyErr_NoMemory();
308+
goto error_cleanup;
309+
}
310+
memcpy(ava->la_value.bv_val, value_utf8, (size_t)value_len);
311+
ava->la_value.bv_val[value_len] = '\0';
312+
ava->la_value.bv_len = (ber_len_t) value_len;
313+
314+
ava->la_flags = (int)PyLong_AsLong(py_encoding);
315+
if (PyErr_Occurred()) {
316+
/* Encoding conversion failed */
317+
free(ava->la_attr.bv_val);
318+
free(ava->la_value.bv_val);
319+
free(ava);
320+
goto error_cleanup;
321+
}
322+
323+
rdn[j] = ava;
324+
Py_DECREF(py_ava_item);
325+
py_ava_item = NULL;
326+
}
327+
328+
/* Null‐terminate the RDN */
329+
rdn[navas] = NULL;
330+
331+
dn[i] = rdn;
332+
Py_DECREF(py_rdn_seq);
333+
py_rdn_seq = NULL;
334+
}
335+
336+
/* Null‐terminate the DN */
337+
dn[nrdns] = NULL;
338+
339+
/* Call ldap_dn2bv to build a DN string */
340+
ldap_err = ldap_dn2bv(dn, &str, flags);
341+
if (ldap_err != LDAP_SUCCESS) {
342+
PyErr_SetString(PyExc_RuntimeError, ldap_err2string(ldap_err));
343+
goto error_cleanup;
344+
}
345+
346+
result = PyUnicode_FromString(str.bv_val);
347+
if (result == NULL) {
348+
goto error_cleanup;
349+
}
350+
351+
/* Free the memory allocated by ldap_dn2bv */
352+
ldap_memfree(str.bv_val);
353+
str.bv_val = NULL;
354+
355+
/* Free our local DN structure */
356+
_free_dn_structure(dn);
357+
dn = NULL;
358+
359+
return result;
360+
361+
error_cleanup:
362+
/* Free any partially built DN structure */
363+
_free_dn_structure(dn);
364+
dn = NULL;
365+
366+
/* If ldap_dn2bv allocated something, free it */
367+
if (str.bv_val) {
368+
ldap_memfree(str.bv_val);
369+
str.bv_val = NULL;
370+
}
371+
372+
/* Cleanup Python temporaries */
373+
Py_XDECREF(py_ava_item);
374+
Py_XDECREF(py_rdn_seq);
375+
return NULL;
376+
}
377+
163378
/* ldap_set_option (global options) */
164379

165380
static PyObject *
@@ -196,6 +411,7 @@ static PyMethodDef methods[] = {
196411
{"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS},
197412
#endif
198413
{"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS},
414+
{"dn2str", (PyCFunction)l_ldap_dn2str, METH_VARARGS},
199415
{"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS},
200416
{"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS},
201417
{NULL, NULL}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy