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