@@ -160,6 +160,221 @@ l_ldap_str2dn(PyObject *unused, PyObject *args)
160
160
return result ;
161
161
}
162
162
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
+
163
378
/* ldap_set_option (global options) */
164
379
165
380
static PyObject *
@@ -196,6 +411,7 @@ static PyMethodDef methods[] = {
196
411
{"initialize_fd" , (PyCFunction )l_ldap_initialize_fd , METH_VARARGS },
197
412
#endif
198
413
{"str2dn" , (PyCFunction )l_ldap_str2dn , METH_VARARGS },
414
+ {"dn2str" , (PyCFunction )l_ldap_dn2str , METH_VARARGS },
199
415
{"set_option" , (PyCFunction )l_ldap_set_option , METH_VARARGS },
200
416
{"get_option" , (PyCFunction )l_ldap_get_option , METH_VARARGS },
201
417
{NULL , NULL }
0 commit comments