/* See https://www.python-ldap.org/ for details. */

#include "common.h"
#include "message.h"
#include "berval.h"
#include "ldapcontrol.h"
#include "constants.h"

/*
 * Converts an LDAP message into a Python structure.
 *
 * On success, returns a list of dictionaries.
 * On failure, returns NULL, and sets an error.
 *
 * The message m is always freed, regardless of return value.
 *
 * If add_ctrls is non-zero, per-entry/referral/partial/intermediate
 * controls will be added as a third item to each entry tuple
 *
 * If add_intermediates is non-zero, intermediate/partial results will
 * be returned
 */
PyObject *
LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermediates)
{
    /* we convert an LDAP message into a python structure.
     * It is always a list of dictionaries.
     * We always free m.
     */

     PyObject *result, *pyctrls = 0;
     LDAPMessage* entry;
     LDAPControl **serverctrls = 0;
     int rc;

     result = PyList_New(0);
     if (result == NULL) {
        ldap_msgfree( m );
	return NULL;
     }

     for(entry = ldap_first_entry(ld,m);
         entry != NULL;
	 entry = ldap_next_entry(ld,entry))
     {
	 char *dn;
	 char *attr;
	 BerElement *ber = NULL;
	 PyObject* entrytuple; 
	 PyObject* attrdict; 
	 PyObject* pydn;

	 dn = ldap_get_dn( ld, entry );
	 if (dn == NULL)  {
	     Py_DECREF(result);
             ldap_msgfree( m );
	     return LDAPerror( ld, "ldap_get_dn" );
	 }

	 attrdict = PyDict_New();
	 if (attrdict == NULL) {
		Py_DECREF(result);
		ldap_msgfree( m );
		ldap_memfree(dn);
		return NULL;
	 }

	 rc = ldap_get_entry_controls( ld, entry, &serverctrls );
	 if (rc) {
	    Py_DECREF(result);
	    ldap_msgfree( m );
	    ldap_memfree(dn);
	    return LDAPerror( ld, "ldap_get_entry_controls" );
	 }

	 /* convert serverctrls to list of tuples */
	 if ( ! ( pyctrls = LDAPControls_to_List( serverctrls ) ) ) {
	    int err = LDAP_NO_MEMORY;
	    ldap_set_option( ld, LDAP_OPT_ERROR_NUMBER, &err );
	    Py_DECREF(result);
	    ldap_msgfree( m );
	    ldap_memfree(dn);
	    ldap_controls_free(serverctrls);
	    return LDAPerror( ld, "LDAPControls_to_List" );
	 }
	 ldap_controls_free(serverctrls);

  	 /* Fill attrdict with lists */
	 for( attr = ldap_first_attribute( ld, entry, &ber );
	      attr != NULL;
	      attr = ldap_next_attribute( ld, entry, ber )
	 ) {
	     PyObject* valuelist;
	     PyObject* pyattr;
	     struct berval **bvals;

	     pyattr = PyUnicode_FromString(attr);

	     bvals = ldap_get_values_len( ld, entry, attr );

	     /* Find which list to append to */
	     if ( PyDict_Contains( attrdict, pyattr ) ) {
		 valuelist = PyDict_GetItem( attrdict, pyattr );
	     } else {
		 valuelist = PyList_New(0);
		 if (valuelist != NULL && PyDict_SetItem(attrdict,
		     pyattr, valuelist) == -1) {
			Py_DECREF(valuelist);
			valuelist = NULL;	/* catch error later */
		 }
	     }

	     if (valuelist == NULL) {
		Py_DECREF(pyattr);
		Py_DECREF(attrdict);
		Py_DECREF(result);
		if (ber != NULL)
		    ber_free(ber, 0);
		ldap_msgfree( m );
		ldap_memfree(attr);
		ldap_memfree(dn);
		Py_XDECREF(pyctrls);
		return NULL;
	     }

	     if (bvals != NULL) {
	        Py_ssize_t i;
		for (i=0; bvals[i]; i++) {
		    PyObject *valuestr;

		    valuestr = LDAPberval_to_object(bvals[i]);
		    if (PyList_Append( valuelist, valuestr ) == -1) {
		        Py_DECREF(pyattr);
			Py_DECREF(attrdict);
			Py_DECREF(result);
			Py_DECREF(valuestr);
			Py_DECREF(valuelist);
			if (ber != NULL)
			    ber_free(ber, 0);
			ldap_msgfree( m );
			ldap_memfree(attr);
			ldap_memfree(dn);
			Py_XDECREF(pyctrls);
			return NULL;
		    }
		    Py_DECREF(valuestr);
	    	}
		ldap_value_free_len(bvals);
	     }
	     Py_DECREF(pyattr);
	     Py_DECREF( valuelist );
	     ldap_memfree(attr);
	 }

	 pydn = PyUnicode_FromString(dn);
	 if (pydn == NULL) {
	     Py_DECREF(result);
	     ldap_msgfree( m );
	     ldap_memfree(dn);
	     return NULL;
	 }

	 if (add_ctrls) {
	    entrytuple = Py_BuildValue("(OOO)", pydn, attrdict, pyctrls);
	 } else {
	    entrytuple = Py_BuildValue("(OO)", pydn, attrdict);
	 }
	 Py_DECREF(pydn);
	 ldap_memfree(dn);
	 Py_DECREF(attrdict);
	 Py_XDECREF(pyctrls);
	 PyList_Append(result, entrytuple);
	 Py_DECREF(entrytuple);
	 if (ber != NULL)
		 ber_free(ber, 0);
     }
     for(entry = ldap_first_reference(ld,m);
	 entry != NULL;
	 entry = ldap_next_reference(ld,entry))
     {
	 char **refs = NULL;
	 PyObject* entrytuple;
	 PyObject* reflist = PyList_New(0);

	 if (reflist == NULL)  {
	     Py_DECREF(result);
	     ldap_msgfree( m );
	     return NULL;
	 }
	 if (ldap_parse_reference(ld, entry, &refs, &serverctrls, 0) != LDAP_SUCCESS) {
             Py_DECREF(reflist);
	     Py_DECREF(result);
	     ldap_msgfree( m );
	     return LDAPerror( ld, "ldap_parse_reference" );
	 }
	 /* convert serverctrls to list of tuples */
	 if ( ! ( pyctrls = LDAPControls_to_List( serverctrls ) ) ) {
	     int err = LDAP_NO_MEMORY;
	     ldap_set_option( ld, LDAP_OPT_ERROR_NUMBER, &err );
             Py_DECREF(reflist);
	     Py_DECREF(result);
	     ldap_msgfree( m );
	     ldap_controls_free(serverctrls);
	     return LDAPerror( ld, "LDAPControls_to_List" );
	 }
	 ldap_controls_free(serverctrls);
	 if (refs) {
	     Py_ssize_t i;
	     for (i=0; refs[i] != NULL; i++) {
                 /* A referal is a distinguishedName => unicode */
		 PyObject *refstr = PyUnicode_FromString(refs[i]);
		 PyList_Append(reflist, refstr);
		 Py_DECREF(refstr);
	     }
	     ber_memvfree( (void **) refs );
	 }
	 if (add_ctrls) {
	    entrytuple = Py_BuildValue("(sOO)", NULL, reflist, pyctrls);
	 } else {
	    entrytuple = Py_BuildValue("(sO)", NULL, reflist);
	 }
	 Py_DECREF(reflist);
	 Py_XDECREF(pyctrls);
	 PyList_Append(result, entrytuple);
	 Py_DECREF(entrytuple);
     }
     if (add_intermediates) {
	for(entry = ldap_first_message(ld,m);
	    entry != NULL;
	    entry = ldap_next_message(ld,entry))
	   {
	      /* list of tuples */
	      /* each tuple is OID, Berval, controllist */
	      if ( LDAP_RES_INTERMEDIATE == ldap_msgtype( entry ) ) {
		 PyObject* valtuple;
		 PyObject *valuestr;
		 char *retoid = 0;
		 PyObject *pyoid;
		 struct berval *retdata = 0;

		 if (ldap_parse_intermediate( ld, entry, &retoid, &retdata, &serverctrls, 0 ) != LDAP_SUCCESS) {
		    Py_DECREF(result);
		    ldap_msgfree( m );
		    return LDAPerror( ld, "ldap_parse_intermediate" );
		 }
		 /* convert serverctrls to list of tuples */
		 if ( ! ( pyctrls = LDAPControls_to_List( serverctrls ) ) ) {
		    int err = LDAP_NO_MEMORY;
		    ldap_set_option( ld, LDAP_OPT_ERROR_NUMBER, &err );
		    Py_DECREF(result);
		    ldap_msgfree( m );
		    ldap_controls_free(serverctrls);
		    ldap_memfree( retoid );
		    ber_bvfree( retdata );
		    return LDAPerror( ld, "LDAPControls_to_List" );
		 }
		 ldap_controls_free(serverctrls);

		 valuestr = LDAPberval_to_object(retdata);
		 ber_bvfree( retdata );
		 pyoid = PyUnicode_FromString(retoid);
		 ldap_memfree( retoid );
		 if (pyoid == NULL)  {
			Py_DECREF(result);
			ldap_msgfree( m );
			return NULL;
		 }
		 valtuple = Py_BuildValue("(OOO)", pyoid,
					  valuestr ? valuestr : Py_None,
					  pyctrls);
		 Py_DECREF(pyoid);
		 Py_DECREF(valuestr);
		 Py_XDECREF(pyctrls);
		 PyList_Append(result, valtuple);
		 Py_DECREF(valtuple);
	      }
	   }
     }
     ldap_msgfree( m );
     return result;
}
