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