package com.idega.core.ldap.client.naming;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import com.idega.core.ldap.client.cbutil.CBBase64;
import com.idega.core.ldap.client.cbutil.CBUtility;
/**
* collection of static utility ftns. for
* writing and reading ldif files. Currently does not
* handle URLs properly, but will do base64 encoding
* quite happily given half a chance...
*/
public class LdifUtility
{
static boolean debug = false;
private Hashtable params = null; // list of expandable strings for the ldif file, used during file parsing
// e.g. KEY: <base_dn>, KEY VALUE: "o=eTrust, cn=Users"
private String filedir = null;
public LdifUtility() {}
/**
* Constructor
* @param params - hashtable with the list of string that will have to be suvstituted in the ldif file
* @param filedir - ldif file directory, used to find the input files specified in the ldif stream
*/
public LdifUtility(Hashtable params, String filedir)
{
this.params = params;
this.filedir = filedir;
}
/**
* Set the ldif filepath - used to find input files
* @param filedir file path
*/
public void setFileDir(String filedir)
{
this.filedir = filedir + "\\";
}
/**
* Set the ldif file parameters
* @param params list of parameters
*/
public void setParams(Hashtable params)
{
this.params = params;
}
/**
* This is used to write a value that is *probably* normal
* string encoded, but *may* need to be base64 encoded.
* It also takes a boolean parameter that forces base64 encoding.
* Otherwise, it
* checks the string against the requirements of draft-good-ldap-ldif-04
* (initial character sane, subsequent characters not null, CR or LF),
* and returns the appropriate string, with appropriate ': ' or ':: '
* prefix.
* @param o the object to be ldif encoded
* @return the ldif encoding (possibly base64) with appropriate colons.
*/
public String ldifEncode(Object o, int offset, boolean forceBase64Encoding)
{
if (forceBase64Encoding == false) {
return ldifEncode(o, offset);
}
String ret = ":: ";
if (o.getClass().isArray())
{
try
{
byte b[] = (byte[]) o;
ret += CBBase64.binaryToString(b, offset+3);
return ret;
}
catch (ClassCastException e)
{
System.out.println("unable to cast array to byte array.");
}
}
// it's not a byte array; force it to a string, read as bytes,
// and code those. This will work in most cases, but will
// fail badly for binary data that has not been encoded properly
// already; e.g. a gif file should (probably) be translated to a
// byte array before being passed to this ftn.
ret += CBBase64.binaryToString(o.toString().getBytes(), offset + 3);
return ret;
}
/**
* This is used to write a value that is *probably* normal
* string encoded, but *may* need to be base64 encoded. It
* checks the string against the requirements of draft-good-ldap-ldif-04
* (initial character sane, subsequent characters not null, CR or LF),
* and returns the appropriate string, with appropriate ': ' or ':: '
* prefix.
* @param o the object to be ldif encoded
* @return the ldif encoding (possibly base64) with appropriate colons.
*/
public String ldifEncode(Object o, int offset)
{
boolean base64Encode = false;
if ((o instanceof String) == false)
{
if (debug == true) {
System.out.println("found a " + o.getClass().toString());
}
if (o.getClass().isArray())
{
try
{
byte b[] = (byte[]) o;
String ret = ":: " + CBBase64.binaryToString(b, offset + 3);
if (debug == true) {
System.out.println("phenomenal - identified and wrote '" + ret + "'");
}
return ret;
}
catch (ClassCastException e)
{
if (debug == true) {
System.out.println("unable to cast array to byte array.");
}
}
}
return o.toString();
}
else // we have a string
{
String s = o.toString();
int len = s.length();
if (len==0) {
return ": "; // this shouldn't really happen; null attributes should be culled before we get here...
}
char test[] = new char[len];
s.getChars(0,len,test,0);
// run the rfc tests to see if this is a good and virtuous string
if ("\n\r :<".indexOf(s.charAt(0)) != -1) {
base64Encode = true;
}
else
{
for (int i=0; i<len; i++)
{
//System.out.println("checking: " + i + ": " + test[i] + " = " + CBUtility.charToHex(test[i]));
if (test[i]>126 || test[i]<32) // check for sane intermediate chars
{
base64Encode = true; // (may be unicode international string)
break;
}
}
}
if (s.charAt(s.length()-1)==' ') {
base64Encode = true;
}
if (base64Encode)
{
try
{
s = CBBase64.binaryToString(s.getBytes("UTF8"), offset+3);
}
catch (UnsupportedEncodingException e) // why would we get this when utf8 is mandatory across all java platforms?
{
CBUtility.log("error utf8 encoding strings..." + e);
s = CBBase64.binaryToString(s.getBytes(), offset+3);
}
return ":: " + s;
}
else {
return ": " + s; // return unmodified string.
}
}
}
/**
* Writes a single ldif entry...
*
*/
/**
* retrieves a single entry from the directory and writes it
* out to an ldif file. Note that ldif header 'version 1' must
* be written elsewhere...
* @param dn the ldap escaped dn of the entry being written
* @param saveFile the file to write the entry to
* @param originalPrefix an optional portion of the dn to update
* @param replacementPrefix an optional replacement for a portion of the dn
* @param atts the attributes of teh entry
*/
public void writeLdifEntry(String dn, FileWriter saveFile, String originalPrefix, String replacementPrefix, Attributes atts)
throws NamingException, IOException
{
if (atts == null)
{
CBUtility.log("no attributes available for " + dn);
return;
}
/**
* Prefix replacement magic. If we are moving the tree during
* the save, and a different prefix has been given (i.e. the
* originalPrefix and replacementPrefix variables aren't zero)
* we switch the relavent portion of the saved dn, substituting
* the portion of the dn that contains the original prefix with
* the replacement.
* e.g. cn=Fredo,o=FrogFarm,c=au, with original prefix o=FrogFarm,c=au
* and replacement o=FreeFrogs,c=au, becomes cn=Fredo,o=FreeFrogs,c=au
*/
if ((originalPrefix != null) && (dn.endsWith(originalPrefix))) // which it jolly well should...
{
if (debug == true) {
System.out.println("original DN = '" + dn + "'");
}
dn = dn.substring(0,dn.length()-originalPrefix.length()) + replacementPrefix;
if (debug == true) {
System.out.println("after replacement DN = '" + dn + "'");
}
}
Attribute oc; // we treat the object class attribute
oc = atts.get("oc"); // specially to ensure it is first after the dn.
if (oc != null) // XXX do a name conversion...
{
if (oc instanceof DXAttribute) {
((DXAttribute)oc).setName("objectClass");
}
}
else {
oc = atts.get("objectclass"); // so keep looking...
}
if (oc == null) {
oc = atts.get("objectClass"); // this really bites.
}
if (oc == null)
{
if (dn.endsWith("cn=schema")) {
oc = new BasicAttribute("oc","schema");
}
}
if (oc == null)
{
CBUtility.log("unable to identify object class for " + dn + " - skipping entry");
return;
}
if (debug) {
System.out.println("dn" + ldifEncode(dn, 2));
}
else {
saveFile.write("dn" + ldifEncode(dn,2) + "\n");
}
NamingEnumeration ocs = oc.getAll();
while (ocs.hasMore())
{
if (debug) {
System.out.println(oc.getID() + ": " + ocs.next());
}
else {
saveFile.write(oc.getID() + ldifEncode(ocs.next(), oc.getID().length()) + "\n");
}
}
NamingEnumeration allAtts = atts.getAll();
String attName;
//String attValue;
Attribute currentAtt;
while (allAtts.hasMore())
{
currentAtt = (Attribute) allAtts.next();
boolean binary = false;
if (currentAtt instanceof DXAttribute) {
binary = ((DXAttribute)currentAtt).isBinary();
}
attName = currentAtt.getID();
/*
* Make sure we don't print out 'dn' or objectclass attributes twice
*/
if ((attName.equals("dn")==false)&&(attName.equals(oc.getID())==false))
{
NamingEnumeration values = currentAtt.getAll();
while (values.hasMore())
{
Object value = values.next();
if (value != null)
{
//BY THE TIME IT GETS HERE THE UTF-8 IS HISTORY...
if (debug)
{
System.out.println("value class = " + value.getClass().toString() + " : " + value);
System.out.println(attName + ": " + value.toString());
}
else
{
if (binary) {
saveFile.write(attName + ldifEncode(value, attName.length(), true) + "\n");
}
else {
saveFile.write(attName + ldifEncode(value, attName.length()) + "\n");
}
}
}
}
}
}
if (!debug)
{
saveFile.write("\n");
saveFile.flush();
}
}
/**
* Parse an attribute: value line of an ldif file, and place
* the attribute value pair in an Attributes object.
* @param parseableLine a complete ldif text line (unwrapped) to parse
* @param newEntry the partially created entry, which is modified by this
* method.
*/
public void ldifDecode(String parseableLine, DXEntry newEntry)
{
boolean isBinary=false;
int breakpos = parseableLine.indexOf(':');
if (breakpos < 0)
{CBUtility.log("Error - illegal line in ldif file\n" + parseableLine,0); return;}
String attribute = parseableLine.substring(0, breakpos);
Object value = null;
int attLen = attribute.length();
// auto-translate 'oc' to 'objectClass'
if (attribute.equals("oc")) {
attribute = "objectClass";
}
int startpos = 2;
if (parseableLine.length() <= breakpos+1) // empty value
{
value = "";
}
else if (parseableLine.charAt(breakpos+1)==':') // check for base64 encoded binary
{
value = getBase64Value(parseableLine, attLen, startpos, attribute); // may return string or byte array!
if (value instanceof String == false) {
isBinary = true;
}
}
else
{
if (parseableLine.charAt(attLen+1)!=' ') {
startpos = 1;
}
value = parseableLine.substring(attLen+startpos);
// expand the value parameters, including the urls
value = expandValueParams(value);
}
if ("dn".equalsIgnoreCase(attribute))
{
if (value instanceof String)
{
DN dn = new DN((String)value);
if (dn.error()) {
CBUtility.log("Error trying to initialise ldif DN: \n"+dn.getError());
}
else {
newEntry.putDN(dn);
}
}
else // this code should no longer be triggered, as utf8 conversion is done when data first read...
{
try
{
DN dn = new DN(new String((byte[])value, "UTF8"));
if (dn.error()) {
CBUtility.log("Error trying to initialise ldif DN: \n"+dn.getError());
}
else {
newEntry.putDN(dn);
}
}
catch (UnsupportedEncodingException e) {} // can't happen?: UTF8 is mandatory...
}
}
else if (attribute != null)
{
Attribute existing = newEntry.get(attribute);
if (existing == null)
{
DXAttribute att = new DXAttribute(attribute, value);
att.setBinary(isBinary);
newEntry.put(att);
}
else
{
existing.add(value);
newEntry.put(existing);
}
}
}
/**
*
*/
private Object getBase64Value(String parseableLine, int attLen, int startpos, String attribute)
{
byte[] rawBinaryData;
if (parseableLine.charAt(attLen+2) == ' ') {
startpos = 3;
}
rawBinaryData = CBBase64.stringToBinary(parseableLine.substring(attribute.length()+startpos));
// a bit dodgy - we try to guess whether the binary data is UTF-8, or is really binary...
// we should probably do some schema checking here, but instead we'll try to make an educated
// guess...
// Create a short array to test for utf-8 ishness... (we don't want to test all of large text files)
byte[] testBytes;
if (rawBinaryData.length > 256)
{
testBytes = new byte[256];
System.arraycopy(rawBinaryData, 0, testBytes, 0, 256);
}
else {
testBytes = rawBinaryData;
}
/*
* Make a (slightly ad-hoc) check to see if it is actually a utf-8 string *pretending* to by bytes...
*/
if (CBUtility.isUTF8(testBytes))
{
try
{
return new String(rawBinaryData, "UTF-8");
}
catch (Exception e) // as per String constructor doco, behaviour is 'unspecified' if the above fails...
{
// drop through to return the raw binary data instead...
}
}
return rawBinaryData;
}
/**
* Read an entry from LDIF text. Attribute/value pairs are read until
* a blank line is encountered.
*
* @param readText a buffered Reader to read lines of ldif text from...
* @return the read entry, as a DXAttributes object
* @throws InterruptedIOException if the user hits cancel on the progress bar
*/
public DXEntry readLdifEntry(BufferedReader textReader)
throws IOException
{
DXEntry entry = new DXEntry();
String line = "";
String oldLine = "";
/* This is a little tricky. Because lines may be extended by line wrapping,
* we need to read ahead a line until we're sure that we've finished any
* possible wrapping, and only then (when we've already read the 'next' line)
* can we process the old line.
*/
while ((line = textReader.readLine()) != null)
{
if (line.length()>0 && line.charAt(0)==' ') // line wrap
{
line = oldLine + line.substring(1); // extend the value...
}
else if (oldLine.length()>1 && oldLine.charAt(0) == '#')
{
// comment... do nothing.
}
else if (oldLine.length()>2)
{
ldifDecode(oldLine, entry);
}
if (line==null||line.equals("")) // end of entry...
{
return entry;
}
oldLine = line;
}
if (entry.getDN().size()>0) // dn check is for unexpectedly truncated files
{
// unusual - end of file reached, and the file *doesn't* have
// a blank line at the end - hence a special case while we write
// the last entry
if (oldLine != null && oldLine.trim().length()>0) {
ldifDecode(oldLine, entry);
}
return entry; // should be last entry
}
return null; // finished reading everything...
}
/**
* This method expands the strings inside the ldif file
* that match the list of expandable strings in params list.
* @param value value to be expanded
* @return expanded object
*/
public Object expandValueParams(Object value)
{
if (this.params != null)
{
Enumeration keys = this.params.keys();
while (keys.hasMoreElements())
{
String key = (String)keys.nextElement();
String keyvalue = (String)this.params.get(key);
// check for the key
String oldValue = (String)value;
int index = oldValue.indexOf(key);
if (index > -1)
{
String newValue = oldValue.substring(0, index) + keyvalue +
oldValue.substring(index+key.length(), oldValue.length());
System.out.println(newValue);
value = newValue;
}
}
}
// load the file if the value is a url
if (this.filedir != null)
{
// check if it is a file, i.e. look for "< file:"
String oldValue = (String)value;
String match = "< file://";
int index = (oldValue.toLowerCase()).indexOf(match);
if (index > -1)
{
String filename = this.filedir + oldValue.substring(index+9, oldValue.length());
File file = new File(filename);
try
{
FileInputStream input = new FileInputStream(file);
int length = (int)file.length();
if (length > 0)
{
byte[] bytes = new byte[length];
int read = input.read(bytes);
if (read > 0) {
value = bytes;
}
}
input.close();
}
catch(IOException e)
{
System.out.println("Error opening the file!" + e);
}
}
}
return value;
}
}