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