Skip to content

Commit b6a2a5e

Browse files
committed
Add support for flags in ldap.dn2str()
In C `dn2str()` supports `flags` which works by providing `LDAP_DN_FORMAT_UFN`, `LDAP_DN_FORMAT_AD_CANONICAL`. 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 e75c24d commit b6a2a5e

3 files changed

Lines changed: 180 additions & 11 deletions

File tree

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 not dn:
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: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "berval.h"
77
#include "constants.h"
88
#include "options.h"
9+
#include <stdio.h>
910

1011
/* ldap_initialize */
1112

@@ -160,6 +161,145 @@ l_ldap_str2dn(PyObject *unused, PyObject *args)
160161
return result;
161162
}
162163

164+
/* ldap_dn2str */
165+
166+
static PyObject *
167+
l_ldap_dn2str(PyObject *unused, PyObject *args)
168+
{
169+
struct berval str;
170+
LDAPDN dn = NULL;
171+
LDAPRDN rdn = NULL;
172+
int flags = 0;
173+
PyObject *result = NULL, *tmp = NULL, *dn_list = NULL;
174+
int res, i, j;
175+
char *type_error_message = "expected List[List[Tuple[str, str, int]]]";
176+
177+
/*
178+
* From a list-equivalent of AVA structures; namely:
179+
* ((('a', 'b', 1), ('c', 'd', 1)), (('e', 'f', 1),)), build
180+
* a DN string such as "a=b,c=d;e=f".
181+
* The integers are a bit combination of the AVA_* flags
182+
*/
183+
if (!PyArg_ParseTuple(args, "Oi:dn2str", &dn_list, &flags))
184+
return NULL;
185+
Py_XDECREF(args);
186+
Py_INCREF(dn_list);
187+
188+
PyObject *iter = PyObject_GetIter(dn_list);
189+
if (!iter) {
190+
PyErr_SetString(PyExc_TypeError, type_error_message);
191+
goto failed;
192+
}
193+
194+
Py_ssize_t nrdns = PyObject_Length(dn_list);
195+
if (nrdns == -1) {
196+
// can't happen
197+
goto failed;
198+
}
199+
dn = malloc(sizeof(LDAPRDN *) * (nrdns + 1));
200+
201+
i = 0;
202+
while (1) {
203+
PyObject *inext = PyIter_Next(iter);
204+
if (!inext) {
205+
break;
206+
}
207+
208+
if (PyList_Check(inext)) {
209+
inext = PyList_AsTuple(inext);
210+
}
211+
if (!PyTuple_Check(inext)) {
212+
PyErr_SetString(PyExc_TypeError, type_error_message);
213+
Py_XDECREF(iter);
214+
Py_XDECREF(inext);
215+
goto failed;
216+
}
217+
218+
PyObject *iiter = PyObject_GetIter(inext);
219+
220+
Py_ssize_t navas = PyObject_Length(inext);
221+
rdn = malloc(sizeof(LDAPRDN) * (navas + 1));
222+
223+
j = 0;
224+
while (1) {
225+
PyObject *next = PyIter_Next(iiter);
226+
if (!next) {
227+
break;
228+
}
229+
230+
if (PyList_Check(next)) {
231+
next = PyList_AsTuple(next);
232+
}
233+
if (!PyTuple_Check(next) || PyTuple_Size(next) != 3) {
234+
PyErr_SetString(PyExc_TypeError, type_error_message);
235+
Py_XDECREF(iter);
236+
Py_XDECREF(iiter);
237+
Py_XDECREF(next);
238+
goto failed;
239+
}
240+
241+
PyObject *name, *value, *encoding;
242+
243+
name = PyTuple_GetItem(next, 0);
244+
value = PyTuple_GetItem(next, 1);
245+
encoding = PyTuple_GetItem(next, 2);
246+
247+
if (!PyUnicode_Check(name) || !PyUnicode_Check(value) || !PyLong_Check(encoding)) {
248+
PyErr_SetString(PyExc_TypeError, type_error_message);
249+
Py_XDECREF(iter);
250+
Py_XDECREF(iiter);
251+
Py_XDECREF(next);
252+
Py_XDECREF(name);
253+
Py_XDECREF(value);
254+
Py_XDECREF(encoding);
255+
goto failed;
256+
}
257+
258+
LDAPAVA *ava = malloc(sizeof(LDAPAVA));
259+
260+
ava->la_attr.bv_val = (char *) PyUnicode_AsUTF8AndSize(name, (long int*) &ava->la_attr.bv_len);
261+
ava->la_value.bv_val = (char *) PyUnicode_AsUTF8AndSize(value, (long int*) &ava->la_value.bv_len);
262+
ava->la_flags = (int)PyLong_AsLong(encoding);
263+
264+
Py_XDECREF(next);
265+
// TODO: segfault when activating the following:
266+
//Py_XDECREF(name);
267+
//Py_XDECREF(value);
268+
//Py_XDECREF(encoding);
269+
270+
rdn[j] = ava;
271+
j++;
272+
}
273+
Py_XDECREF(iiter);
274+
Py_XDECREF(inext);
275+
rdn[j] = NULL;
276+
277+
dn[i] = rdn;
278+
i++;
279+
}
280+
dn[i] = NULL;
281+
282+
res = ldap_dn2bv(dn, &str, flags);
283+
if (res != LDAP_SUCCESS)
284+
return LDAPerr(res); // TODO: no attr set
285+
286+
tmp = PyUnicode_FromString(str.bv_val);
287+
if (!tmp)
288+
goto failed;
289+
290+
result = tmp;
291+
//Py_XDECREF(tmp);
292+
tmp = NULL;
293+
294+
failed:
295+
Py_XDECREF(tmp);
296+
Py_XDECREF(iter);
297+
Py_XDECREF(dn_list);
298+
//Py_XDECREF(result);
299+
// ldap_dnfree(dn);
300+
return result;
301+
}
302+
163303
/* ldap_set_option (global options) */
164304

165305
static PyObject *
@@ -196,6 +336,7 @@ static PyMethodDef methods[] = {
196336
{"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS},
197337
#endif
198338
{"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS},
339+
{"dn2str", (PyCFunction)l_ldap_dn2str, METH_VARARGS},
199340
{"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS},
200341
{"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS},
201342
{NULL, NULL}

Tests/t_ldap_dn.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,42 @@ def test_dn2str(self):
158158
[('dc', 'example', 1)],
159159
[('dc', 'com', 1)]
160160
]),
161-
'uid=test\\, 42,ou=Testing,dc=example,dc=com'
161+
'uid=test\\2C 42,ou=Testing,dc=example,dc=com'
162162
)
163163
self.assertEqual(
164164
ldap.dn.dn2str([
165165
[('cn', 'äöüÄÖÜß', 4)],
166166
[('dc', 'example', 1)],
167167
[('dc', 'com', 1)]
168168
]),
169-
'cn=äöüÄÖÜß,dc=example,dc=com'
169+
r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com'
170+
)
171+
self.assertEqual(
172+
ldap.dn.dn2str([
173+
[('uid', 'test42', 1)],
174+
[('ou', 'Testing', 1)],
175+
[('dc', 'example', 1)],
176+
[('dc', 'com', 1)]
177+
], ldap.DN_FORMAT_AD_CANONICAL),
178+
'example.com/Testing/test42'
179+
)
180+
self.assertEqual(
181+
ldap.dn.dn2str([
182+
[('uid', 'test42', 1)],
183+
[('ou', 'Testing', 1)],
184+
[('dc', 'example', 1)],
185+
[('dc', 'com', 1)]
186+
], ldap.DN_FORMAT_UFN),
187+
'test42, Testing, example.com'
188+
)
189+
self.assertEqual(
190+
ldap.dn.dn2str([
191+
[('uid', 'test42', 1)],
192+
[('ou', 'Testing', 1)],
193+
[('dc', 'example', 1)],
194+
[('dc', 'com', 1)]
195+
], ldap.DN_FORMAT_DCE),
196+
'/dc=com/dc=example/ou=Testing/uid=test42'
170197
)
171198

172199
def test_explode_dn(self):

0 commit comments

Comments
 (0)