/* -*- show-trailing-whitespace: t; indent-tabs: t -*-
 * Copyright (c) 2003,2004,2005,2006 David Lichteblau
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include "common.h"

t_print_binary_mode print_binary_mode = PRINT_UTF8;

static void
write_backslashed(FILE *s, char *ptr, int n)
{
	int i;
	for (i = 0; i < n; i++) {
		char c = ptr[i];
		if (c == '\n' || c == '\\') fputc('\\', s);
		fputc(c, s);
	}
	if (ferror(s)) syserr();
}

static int
utf8_string_p(unsigned char *str, int n)
{
	int i = 0;
	while (i < n) {
		unsigned char c = str[i++];
		if (c >= 0xfe)
			return 0;
		if (c >= 0xfc) {
			unsigned char d;
			if ((n - i < 5)
			    || ((d=str[i++]) ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (c < 0xfd && d < 0x84))
				return 0;
		} else if (c >= 0xf8) {
			unsigned char d;
			if ((n - i < 4)
			    || ((d=str[i++]) ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (c < 0xf9 && d < 0x88))
				return 0;
		} else if (c >= 0xf0) {
			unsigned char d;
			if ((n - i < 3)
			    || ((d=str[i++]) ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (str[i++] ^ 0x80) >= 0x40
			    || (c < 0xf1 && d < 0x90))
				return 0;
		} else if (c >= 0xe0) {
			unsigned char d, e;
			unsigned code;
			if ((n - i < 2)
			    || ((d=str[i++]) ^ 0x80) >= 0x40
			    || ((e=str[i++]) ^ 0x80) >= 0x40
			    || (c < 0xe1 && d < 0xa0))
				return 0;
			code = ((int) c & 0x0f) << 12
				| ((int) d ^ 0x80) << 6
				| ((int) e ^ 0x80);
			if ((0xd800 <= code) && (code <= 0xdfff)
			    || code == 0xfffe || code == 0xffff)
				return 0;
		} else if (c >= 0x80) {
			unsigned char d;
			if ((n - i < 1)
			    || ((d=str[i++]) ^ 0x80) >= 0x40
			    || (c < 0xc2))
				return 0;
		} else if (c == 0)
			return 0;
	}
	return 1;
}

static int
readable_string_p(char *str, int n)
{
	int i;
	for (i = 0; i < n; i++) {
		char c = str[i];
		if (c < 32 && c != '\n' && c != '\t')
			return 0;
	}
	return 1;
}

static int
safe_string_p(char *str, int n)
{
	unsigned char c;
	int i;

	if (n == 0) return 1;

	c = str[0];
	if ((c == ' ') || (c == ':') || (c == '<'))
		return 0;

	for (i = 0; i < n; i++) {
		c = str[i];
		if ((c == '\0') || (c == '\r') || (c == '\n') || (c >= 0x80))
			return 0;
	}
	return 1;
}

static void
print_attrval(FILE *s, char *str, int len, int prefernocolon)
{
	int readablep;
	switch (print_binary_mode) {
	case PRINT_ASCII:
		readablep = readable_string_p(str, len);
		break;
	case PRINT_UTF8:
		readablep = utf8_string_p((unsigned char *) str, len);
		break;
	case PRINT_JUNK:
		readablep = 1;
		break;
	default:
		abort();
	}

	if (!readablep) {
		fputs(":: ", s);
		print_base64((unsigned char *) str, len, s);
	} else if (prefernocolon) {
		fputc(' ', s);
		write_backslashed(s, str, len);
	} else if (!safe_string_p(str, len)) {
		fputs(":; ", s);
		write_backslashed(s, str, len);
	} else {
		fputs(": ", s);
		fwrite(str, 1, len, s);
	}
}

static void
print_attribute(FILE *s, tattribute *attribute)
{
	GPtrArray *values = attribute_values(attribute);
	int j;

	for (j = 0; j < values->len; j++) {
		GArray *av = g_ptr_array_index(values, j);
		fputs(attribute_ad(attribute), s);
		print_attrval(s, av->data, av->len, 0);
		fputc('\n', s);
	}
	if (ferror(s)) syserr();
}

static void
print_entroid_bottom(FILE *s, tentroid *entroid)
{
	int i;
	LDAPAttributeType *at;
	for (i = 0; i < entroid->must->len; i++) {
		at = g_ptr_array_index(entroid->must, i);
		fprintf(s, "# required attribute not shown: %s\n",
			attributetype_name(at));
	}
	for (i = 0; i < entroid->may->len; i++) {
		at = g_ptr_array_index(entroid->may, i);
		fprintf(s, "#%s: \n", attributetype_name(at));
	}
}

void
print_ldapvi_entry(FILE *s, tentry *entry, char *key, tentroid *entroid)
{
	GPtrArray *attributes = entry_attributes(entry);
	int i;

	fputc('\n', s);
	fputs(key ? key : "entry", s);
	fputc(' ', s);
	fputs(entry_dn(entry), s);
	fputc('\n', s);
	if (ferror(s)) syserr();

	if (entroid)
		fputs(entroid->comment->str, s);
	for (i = 0; i < attributes->len; i++) {
		tattribute *attribute = g_ptr_array_index(attributes, i);
		char *ad = attribute_ad(attribute);
		if ( entroid && !entroid_remove_ad(entroid, ad))
			fprintf(s, "# WARNING: %s not allowed by schema\n",
				ad);
		print_attribute(s, attribute);
	}
	if (entroid)
		print_entroid_bottom(s, entroid);
}

static void
print_ldapvi_ldapmod(FILE *s, LDAPMod *mod)
{
	struct berval **values = mod->mod_bvalues;

	switch (mod->mod_op & ~LDAP_MOD_BVALUES) {
	case LDAP_MOD_ADD: fputs("add", s); break;
	case LDAP_MOD_DELETE: fputs("delete", s); break;
	case LDAP_MOD_REPLACE: fputs("replace", s); break;
	default: abort();
	}
	print_attrval(s, mod->mod_type, strlen(mod->mod_type), 0);
	fputc('\n', s);
	for (; *values; values++) {
		struct berval *value = *values;
		print_attrval(s, value->bv_val, value->bv_len, 0);
		fputc('\n', s);
	}
	if (ferror(s)) syserr();
}

void
print_ldapvi_modify(FILE *s, char *dn, LDAPMod **mods)
{
	fputs("\nmodify", s);
	print_attrval(s, dn, strlen(dn), 1);
	fputc('\n', s);

	for (; *mods; mods++)
		print_ldapvi_ldapmod(s, *mods);
	if (ferror(s)) syserr();
}

void
print_ldapvi_rename(FILE *s, char *olddn, char *newdn, int deleteoldrdn)
{
	fputs("\nrename", s);
	print_attrval(s, olddn, strlen(olddn), 1);
	fputs(deleteoldrdn ? "\nreplace" : "\nadd", s);
	print_attrval(s, newdn, strlen(newdn), 0);
	fputc('\n', s);
	if (ferror(s)) syserr();
}

static GString *
rdns2gstring(char **ptr)
{
	GString *result = g_string_new("");
	if (*ptr)
		g_string_append(result, *ptr);
	ptr++;
	for (; *ptr; ptr++) {
		g_string_append_c(result, ',');
		g_string_append(result, *ptr);
	}
	return result;
}

/* simple version of _rename without new superior */
void
print_ldapvi_modrdn(FILE *s, char *olddn, char *newrdn, int deleteoldrdn)
{
	char **newrdns = ldap_explode_dn(olddn, 0);
	GString *newdn;
	char *tmp;

	fputs("\nrename", s);
	print_attrval(s, olddn, strlen(olddn), 1);
	fputs(deleteoldrdn ? "\nreplace" : "\nadd", s);

	/* fixme, siehe notes */
	tmp = *newrdns;
	*newrdns = newrdn;
	newdn = rdns2gstring(newrdns);
	print_attrval(s, newdn->str, newdn->len, 0);
	fputc('\n', s);
	g_string_free(newdn, 1);
	*newrdns = tmp;

	if (ferror(s)) syserr();
	ldap_value_free(newrdns);
}

void
print_ldapvi_add(FILE *s, char *dn, LDAPMod **mods)
{
	fputs("\nadd", s);
	print_attrval(s, dn, strlen(dn), 1);
	fputc('\n', s);

	for (; *mods; mods++) {
		LDAPMod *mod = *mods;
		struct berval **values = mod->mod_bvalues;
		for (; *values; values++) {
			struct berval *value = *values;
			fputs(mod->mod_type, s);
			print_attrval(s, value->bv_val, value->bv_len, 0);
			fputc('\n', s);
		}
	}
	if (ferror(s)) syserr();
}

void
print_ldapvi_delete(FILE *s, char *dn)
{
	fputs("\ndelete", s);
	print_attrval(s, dn, strlen(dn), 1);
	fputc('\n', s);
	if (ferror(s)) syserr();
}

static void
print_ldif_line(FILE *s, char *ad, char *str, int len)
{
	if (len == -1)
		len = strlen(str);
	fputs(ad, s);
	if (safe_string_p(str, len)) {
		fputs(": ", s);
		fwrite(str, len, 1, s);
	} else {
		fputs(":: ", s);
		print_base64((unsigned char *) str, len, s);
	}
	fputs("\n", s);
}

static void
print_ldif_bervals(FILE *s, char *ad, struct berval **values)
{
	for (; *values; values++) {
		struct berval *value = *values;
		print_ldif_line(s, ad, value->bv_val, value->bv_len);
	}
	if (ferror(s)) syserr();
}

void
print_ldif_modify(FILE *s, char *dn, LDAPMod **mods)
{
	fputc('\n', s);
	print_ldif_line(s, "dn", dn, -1);
	fputs("changetype: modify\n", s);

	for (; *mods; mods++) {
		LDAPMod *mod = *mods;

		switch (mod->mod_op & ~LDAP_MOD_BVALUES) {
		case LDAP_MOD_ADD: fputs("add: ", s); break;
		case LDAP_MOD_DELETE: fputs("delete: ", s); break;
		case LDAP_MOD_REPLACE: fputs("replace: ", s); break;
		default: abort();
		}
		fputs(mod->mod_type, s);
		fputc('\n', s);

		print_ldif_bervals(s, mod->mod_type, mod->mod_bvalues);
		fputs("-\n", s);
	}
	if (ferror(s)) syserr();
}

void
print_ldif_add(FILE *s, char *dn, LDAPMod **mods)
{
	fputc('\n', s);
	print_ldif_line(s, "dn", dn, -1);
	fputs("changetype: add\n", s);

	for (; *mods; mods++) {
		LDAPMod *mod = *mods;
		print_ldif_bervals(s, mod->mod_type, mod->mod_bvalues);
	}
	if (ferror(s)) syserr();
}

void
print_ldif_rename(FILE *s, char *olddn, char *newdn, int deleteoldrdn)
{
	char **newrdns = ldap_explode_dn(newdn, 0);
	int isRootDSE = !*newrdns;
	GString *sup;

	fputc('\n', s);
	print_ldif_line(s, "dn", olddn, -1);
	fputs("changetype: modrdn\n", s);

	print_ldif_line(s, "newrdn", isRootDSE ? "" : *newrdns, -1);

	fprintf(s, "deleteoldrdn: %d\n", !!deleteoldrdn);

	if (isRootDSE || !newrdns[1])
		fputs("newsuperior:\n", s);
	else {
		sup = rdns2gstring(newrdns + 1);
		print_ldif_line(s, "newsuperior", sup->str, sup->len);
		g_string_free(sup, 1);
	}

	if (ferror(s)) syserr();
	ldap_value_free(newrdns);
}

/* simple version of _rename without new superior */
void
print_ldif_modrdn(FILE *s, char *olddn, char *newrdn, int deleteoldrdn)
{
	fputc('\n', s);
	print_ldif_line(s, "dn", olddn, -1);
	fputs("changetype: modrdn\n", s);
	print_ldif_line(s, "newrdn", newrdn, -1);
	fprintf(s, "deleteoldrdn: %d\n", !!deleteoldrdn);
	if (ferror(s)) syserr();
}

void
print_ldif_delete(FILE *s, char *dn)
{
	fputc('\n', s);
	print_ldif_line(s, "dn", dn, -1);
	fputs("changetype: delete\n", s);
	if (ferror(s)) syserr();
}

void
print_ldapvi_message(FILE *s, LDAP *ld, LDAPMessage *entry, int key,
		    tentroid *entroid)
{
	char *dn, *ad;
	BerElement *ber;

	fprintf(s, "\n%d", key);
	dn = ldap_get_dn(ld, entry);
	print_attrval(s, dn, strlen(dn), 1);
	ldap_memfree(dn);
	fputc('\n', s);
	if (entroid)
		fputs(entroid->comment->str, s);

	for (ad = ldap_first_attribute(ld, entry, &ber);
	     ad;
	     ad = ldap_next_attribute(ld, entry, ber))
	{
		struct berval **values = ldap_get_values_len(ld, entry, ad);
		struct berval **ptr;

		if (!values) continue;
		if (entroid)
			entroid_remove_ad(entroid, ad);

		for (ptr = values; *ptr; ptr++) {
			fputs(ad, s);
			print_attrval(s, (*ptr)->bv_val, (*ptr)->bv_len, 0);
			fputc('\n', s);
		}
		ldap_memfree(ad);
		ldap_value_free_len(values);
	}
	ber_free(ber, 0);

	if (entroid)
		print_entroid_bottom(s, entroid);
	if (ferror(s)) syserr();
}

void
print_ldif_entry(FILE *s, tentry *entry, char *key, tentroid *entroid)
{
	int i;
	GPtrArray *attributes = entry_attributes(entry);

	fputc('\n', s);
	print_ldif_line(s, "dn", entry_dn(entry), -1);
	if (key)
		fprintf(s, "ldapvi-key: %s\n", key);
	if (entroid)
		fputs(entroid->comment->str, s);
	for (i = 0; i < attributes->len; i++) {
		tattribute *attribute = g_ptr_array_index(attributes, i);
		char *ad = attribute_ad(attribute);
		GPtrArray *values = attribute_values(attribute);
		int j;

		if ( entroid && !entroid_remove_ad(entroid, ad))
			fprintf(s, "# WARNING: %s not allowed by schema\n",
				ad);

		for (j = 0; j < values->len; j++) {
			GArray *av = g_ptr_array_index(values, j);
			print_ldif_line(s, ad, av->data, av->len);
		}
	}
	if (entroid)
		print_entroid_bottom(s, entroid);
}

void
print_ldif_message(FILE *s, LDAP *ld, LDAPMessage *entry, int key,
		   tentroid *entroid)
{
	char *dn, *ad;
	BerElement *ber;

	fputc('\n', s);
	if (entroid)
		fputs(entroid->comment->str, s);

	dn = ldap_get_dn(ld, entry);
	print_ldif_line(s, "dn", dn, -1);
	ldap_memfree(dn);

	if (key != -1)
		fprintf(s, "ldapvi-key: %d\n", key);

	for (ad = ldap_first_attribute(ld, entry, &ber);
	     ad;
	     ad = ldap_next_attribute(ld, entry, ber))
	{
		struct berval **values = ldap_get_values_len(ld, entry, ad);
		if (entroid) entroid_remove_ad(entroid, ad);
		print_ldif_bervals(s, ad, values);
		ldap_memfree(ad);
		ldap_value_free_len(values);
	}
	ber_free(ber, 0);

	if (entroid)
		print_entroid_bottom(s, entroid);
	if (ferror(s)) syserr();
}
