Commit e14dca17 authored by Clayton Donley's avatar Clayton Donley
Browse files

Improvements and bug fixes for JLDAP:

* ITS 3425 JLDAP - Use DSMLv2 as you would LDAP
	A DSMLv2 Connection may be assigned to an LDAPConnection and synchronous operations being supported

* ITS 3436 JLDAP - DSMLWriter Improvements
	The writer now generates valid XML.  Quotes are escaped in XML attributes & un-encodable values are base64'ed

* ITS 3437 JLDAP - DSMLHandler Improvements
	Added support for better error handling

* ITS 3438 JLDAP - DSMLReader enhancements
	1.  InputStream assumes UTF-8
	2.  Support for extracting errors generated by the handler
	3.  Support for determining if resume on error

* ITS 3451 JLDAP - Normalizing RDN does not take escaped characters into accoun
	Normalized DN's now escape all components
parent c44896d7
/* **************************************************************************
*
* Copyright (C) 2004 Octet String, Inc. All Rights Reserved.
*
* THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
* TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
* TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
* AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
* IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
* OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
* PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM OCTET STRING, INC.,
* COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
******************************************************************************/
package com.novell.ldap;
import java.io.IOException;
import com.novell.ldap.util.LDAPReader;
/**
* @author Marc Boorshtein
*
* Used as a drop-in replacement for LDAPSerchResults
*/
public class DSMLSearchResults extends LDAPSearchResults {
/** The returned controls */
LDAPControl[] controls;
/** The response pointer */
int msgRespPtr;
/** The Reader */
private LDAPReader reader;
/** The last read message */
LDAPMessage lastread;
/** Are there no results? */
boolean empty;
/** Have any results been read? */
boolean wasRead;
public DSMLSearchResults(LDAPReader reader) {
this.reader = reader;
this.empty = false;
this.wasRead = true;
}
public DSMLSearchResults() {
this.empty = true;
this.wasRead = true;
}
/* (non-Javadoc)
* @see com.novell.ldap.LDAPSearchResults#abandon()
*/
void abandon() {
}
/* (non-Javadoc)
* @see com.novell.ldap.LDAPSearchResults#getCount()
*/
public int getCount() {
return 0;
}
/* (non-Javadoc)
* @see com.novell.ldap.LDAPSearchResults#getResponseControls()
*/
public LDAPControl[] getResponseControls() {
return controls;
}
/* (non-Javadoc)
* @see com.novell.ldap.LDAPSearchResults#hasMore()
*/
public boolean hasMore() {
if (empty) return true;
if (! wasRead) return (this.lastread != null);
this.wasRead = false;
try {
this.lastread = this.reader.readMessage();
if (lastread instanceof LDAPResponse) {
if (lastread.getType() == LDAPMessage.SEARCH_RESULT) {
lastread = this.reader.readMessage();
if (lastread instanceof LDAPResponse) {
this.lastread = null;
return false;
} else {
return this.lastread != null;
}
} else {
this.lastread = null;
return false;
}
} else {
return this.lastread != null;
}
} catch (LDAPException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/* (non-Javadoc)
* @see com.novell.ldap.LDAPSearchResults#next()
*/
public LDAPEntry next() throws LDAPException {
if (empty) return null;
this.wasRead = true;
if (this.lastread != null) {
if (lastread instanceof LDAPSearchResultReference) {
LDAPReferralException ref = new LDAPReferralException("Referral",LDAPException.REFERRAL,"Referral encountered ");
ref.setReferrals(((LDAPSearchResultReference) lastread).getReferrals());
throw ref;
}
return ((LDAPSearchResult) this.lastread).getEntry();
} else {
return null;
}
}
}
This diff is collapsed.
......@@ -62,6 +62,11 @@ public class LDAPSearchResults
* @param cons The LDAPSearchConstraints associated with this search
*/
/* package */
LDAPSearchResults() {
//Required so that a DSML version of this cladd can be utilized
}
LDAPSearchResults( LDAPConnection conn,
LDAPSearchQueue queue,
LDAPSearchConstraints cons)
......
......@@ -13,6 +13,7 @@
* THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
******************************************************************************/
package com.novell.ldap.util;
import com.novell.ldap.LDAPDN;
import com.novell.ldap.util.RDN;
import java.util.Vector;
import java.util.ArrayList;
......@@ -252,7 +253,15 @@ public class DN extends Object
nextChar = dnString.charAt(++currIndex);
if (isHexDigit(nextChar))
{
tokenBuf[tokenIndex++] = hexToChar(currChar, nextChar);
char tmpc = hexToChar(currChar, nextChar);
if (this.needsEscape(tmpc)) {
tokenBuf[tokenIndex++] = '\\';
tokenBuf[tokenIndex++] = tmpc;
}
else {
tokenBuf[tokenIndex++] = tmpc;
}
trailingSpaceCount = 0;
}
else
......@@ -337,7 +346,13 @@ public class DN extends Object
nextChar = dnString.charAt(++currIndex);
if (isHexDigit(nextChar))
{
tokenBuf[tokenIndex++] = hexToChar(currChar, nextChar);
char tmpc = hexToChar(currChar, nextChar);
if (this.needsEscape(tmpc)) {
tokenBuf[tokenIndex++] = '\\';
}
tokenBuf[tokenIndex++] = tmpc;
trailingSpaceCount = 0;
}
else
......@@ -550,9 +565,9 @@ public class DN extends Object
String dn = "";
if (length < 1)
return null;
dn = rdnList.get(0).toString();
dn = LDAPDN.escapeRDN(rdnList.get(0).toString());
for (int i=1; i<length; i++)
dn += "," + rdnList.get(i).toString();
dn += "," + LDAPDN.escapeRDN(rdnList.get(i).toString());
return dn;
}
......
......@@ -149,6 +149,8 @@ class DSMLHandler
private static final int LDAP_RESPONSE = 36; //Generic Response Type.
private static final int RESULT_CODE = 37; //<resultCode>
private static final int ERROR_MESSAGE = 38; //<errorMessage>
private static final int ERROR_RESPONSE = 53; //<errorResponse>
private static final int MESSAGE = 54; //<errorResponse>
private static final int REFERRAL_LIST = 39; //<referral>
//For Search Response
......@@ -203,6 +205,10 @@ class DSMLHandler
//Search Ids
private String searchResponseid;
private String errorType;
private ArrayList errors = new ArrayList();
static { //Initialize requestTags
requestTags = new java.util.HashMap(35, (float) 0.25);
//Load factor of 0.25 optimizes for speed rather than size.
......@@ -248,6 +254,8 @@ class DSMLHandler
requestTags.put("addResponse", new Integer(ADD_RESPONSE));
requestTags.put("resultCode", new Integer(RESULT_CODE));
requestTags.put("errorMessage", new Integer(ERROR_MESSAGE));
requestTags.put("message", new Integer(MESSAGE));
requestTags.put("errorResponse", new Integer(ERROR_RESPONSE));
requestTags.put("referral", new Integer(REFERRAL_LIST));
//Search Response Objects
......@@ -363,7 +371,16 @@ class DSMLHandler
state = LDAP_RESPONSE;
//Handling as a generic LdapResponse.
parseTagAttributes(LDAP_RESPONSE, attrs);
} else {
} else if (tag == ERROR_RESPONSE) {
//Process Compare Response.
responsetype = LDAPMessage.ABANDON_REQUEST;
state = ERROR_RESPONSE;
//Handling as a generic LdapResponse.
parseTagAttributes(ERROR_RESPONSE, attrs);
}
else {
throw new SAXException("invalid tag: " + strSName);
}
......@@ -449,7 +466,7 @@ class DSMLHandler
responsecode = (new Integer(attrs.getValue("code"))).intValue();
responseDesc = attrs.getValue("descr");
} else if (tag == ERROR_MESSAGE) {
} else if (tag == ERROR_MESSAGE || tag == MESSAGE) {
//nothing to do, just cleanup value.
if (value == null) {
value = new StringBuffer();
......@@ -471,6 +488,16 @@ class DSMLHandler
state = tag;
}
break;
case ERROR_RESPONSE :
if (value == null) {
value = new StringBuffer();
} else {
//cleanup value.
value.delete(0, value.length());
}
state = tag;
break;
case SEARCH_REQUEST :
if ((isParallel == true && isUnordered == true)
&& requestID == null) {
......@@ -696,6 +723,10 @@ class DSMLHandler
throws SAXException {
switch (tag) {
case ERROR_RESPONSE:
this.errorType = attrs.getValue("type");
break;
case BATCH_RESPONSE :
batchRequestID = attrs.getValue("requestID");
break;
......@@ -876,6 +907,8 @@ class DSMLHandler
|| state == X_VALUE
|| state == VALUE
|| state == ERROR_MESSAGE
|| state == MESSAGE
|| state == ERROR_RESPONSE
|| state == EXTENDED_RESPONSE_NAME
|| state == EXTENDED_RESPONSE_RESPONSE
|| state == REFERRAL_LIST
......@@ -905,6 +938,21 @@ class DSMLHandler
String[] referalarr = null;
try {
switch (tag) {
case ERROR_RESPONSE:
if (this.errorMessage.indexOf(':') != -1) {
String num,msg;
num = errorMessage.substring(0,errorMessage.indexOf(':'));
try {
int errorNum = Integer.parseInt(num);
this.errors.add(new LDAPException(this.errorType,errorNum,this.errorMessage.substring(errorMessage.indexOf(':') + 1)));
} catch (NumberFormatException nfe) {
this.errors.add(new LDAPException(this.errorType,LDAPException.UNWILLING_TO_PERFORM,this.errorMessage));
}
} else {
this.errors.add(new LDAPException(this.errorType,LDAPException.UNWILLING_TO_PERFORM,this.errorMessage));
}
state = BATCH_RESPONSE;
case BATCH_REQUEST :
case BATCH_RESPONSE :
state = START;
......@@ -1094,6 +1142,12 @@ class DSMLHandler
referrallist.add(turl);
state = LDAP_RESPONSE;
break;
case MESSAGE:
errorMessage = new String(value.toString().getBytes("UTF-8"));
state = ERROR_RESPONSE;
break;
case ERROR_MESSAGE :
errorMessage = new String(value.toString().getBytes("UTF-8"));
......@@ -1512,6 +1566,9 @@ class DSMLHandler
if (this.isBase64) {
attributeValues.add(Base64.decode(value, 0, value.length()));
} else {
if (value == null) value = new StringBuffer();
attributeValues.add(value.toString().getBytes("UTF-8"));
}
break;
......@@ -1525,8 +1582,7 @@ class DSMLHandler
}
public void warning(SAXParseException e) throws SAXException {
System.out.println("warning: " + e.toString());
throw e;
}
public void error(SAXParseException e) throws SAXException {
......@@ -1535,7 +1591,8 @@ class DSMLHandler
}
public void fatalError(SAXParseException e) throws SAXException {
System.out.println("fatal error: " + e.toString());
System.out.println("line : " + e.getLineNumber() + ", column : " + e.getColumnNumber());
System.out.println("fatal error: " + e.toString());
throw e;
}
......@@ -1711,4 +1768,8 @@ class DSMLHandler
ArrayList getQueue() {
return this.queue;
}
ArrayList getErrors() {
return this.errors;
}
}
......@@ -17,6 +17,9 @@ package com.novell.ldap.util;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
......@@ -78,9 +81,10 @@ public class DSMLReader implements LDAPReader {
* @throws LDAPLocalException Occurs when no batchRequest or batchResponse
* is found, or the document is invalid DSML.
*/
public DSMLReader (java.io.InputStream inputStream) throws LDAPLocalException
public DSMLReader (java.io.InputStream inputStream) throws LDAPLocalException, UnsupportedEncodingException
{
this( new java.io.InputStreamReader(inputStream));
this( new java.io.InputStreamReader(inputStream,"UTF8"));
return;
}
......@@ -239,4 +243,8 @@ public class DSMLReader implements LDAPReader {
public boolean isResumeOnError(){
return this.handler.isResumeOnError();
}
public ArrayList getErrors() {
return this.handler.getErrors();
}
}
......@@ -42,11 +42,18 @@ public class DSMLWriter implements LDAPWriter {
private String tabString = " ";
private String version = "2.0";
private boolean resumeOnError;
private static final String BATCH_REQUEST_START =
"<batchRequest xmlns=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">";
"<batchRequest xmlns=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" onError=\"";
private static final String BATCH_RESPONSE_START =
"<batchResponse xmlns=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">";
public void setResumeOnError(boolean resumeOnError) {
this.resumeOnError = resumeOnError;
}
/**
* Initializes this writer by opening the specified file to write DSML into.
* @param file File to write DSML
......@@ -124,7 +131,13 @@ public class DSMLWriter implements LDAPWriter {
newLine(2);
out.write("<message>");
newLine(3);
out.write(e.toString());
if (e instanceof LDAPException) {
LDAPException le = (LDAPException) e;
String msg = Integer.toString(le.getResultCode()) + ":" + le.getMessage();
out.write(makeXMLSafe(msg));
} else {
out.write(makeXMLSafe(e.getMessage()));
}
newLine(2);
out.write("</message>");
newLine(1);
......@@ -450,7 +463,7 @@ public class DSMLWriter implements LDAPWriter {
checkState(true);
newLine(2);
out.write("<searchResultEntry dn=\"");
out.write(entry.getDN());
out.write(this.makeAttributeSafe(entry.getDN()));
if( requestID != null) {
out.write("\" requestID=\"" + requestID);
}
......@@ -973,13 +986,13 @@ public class DSMLWriter implements LDAPWriter {
if (message instanceof LDAPResponse){
String matchedDN = ((LDAPResponse) message).getMatchedDN();
if (matchedDN != null && !matchedDN.equals("")){
out.write(" matchedDN=\"" + matchedDN + "\"");
out.write(" matchedDN=\"" + this.makeAttributeSafe(matchedDN) + "\"");
}
}
if( message instanceof LDAPCompareRequest) {
String dn = ((LDAPCompareRequest) message).getDN();
out.write(" dn=\"" + dn + "\"");
out.write(" dn=\"" + this.makeAttributeSafe(dn) + "\"");
}
if( message instanceof LDAPExtendedRequest) {
......@@ -988,22 +1001,22 @@ public class DSMLWriter implements LDAPWriter {
if( message instanceof LDAPDeleteRequest) {
String dn = ((LDAPDeleteRequest) message).getDN();
out.write(" dn=\"" + dn + "\"");
out.write(" dn=\"" + this.makeAttributeSafe(dn) + "\"");
}
if( message instanceof LDAPAddRequest) {
String dn = ((LDAPAddRequest) message).getEntry().getDN();
out.write(" dn=\"" + dn + "\"");
out.write(" dn=\"" + this.makeAttributeSafe(dn) + "\"");
}
if( message instanceof LDAPModifyRequest) {
String dn = ((LDAPModifyRequest) message).getDN();
out.write(" dn=\"" + dn + "\"");
out.write(" dn=\"" + this.makeAttributeSafe(dn) + "\"");
}
if( message instanceof LDAPModifyDNRequest) {
String dn = ((LDAPModifyDNRequest) message).getDN();
out.write(" dn=\"" + dn + "\"");
out.write(" dn=\"" + this.makeAttributeSafe(dn) + "\"");
out.write(" newrdn=\"");
out.write(((LDAPModifyDNRequest) message).getNewRDN() + "\" deleteoldrdn=\"");
out.write(((LDAPModifyDNRequest) message).getDeleteOldRDN() + "\" newSuperior=\"");
......@@ -1022,7 +1035,9 @@ public class DSMLWriter implements LDAPWriter {
int tlimit = ((LDAPSearchRequest) message).getServerTimeLimit();
boolean isTypesonly = ((LDAPSearchRequest) message).isTypesOnly();
out.write(" dn=\"" + dn + "\"");
out.write(" dn=\"" + this.makeAttributeSafe(dn) + "\"");
switch(scope) {
case LDAPConnection.SCOPE_BASE:
searchScope = "baseObject";
......@@ -1121,7 +1136,7 @@ public class DSMLWriter implements LDAPWriter {
if (temp != null && temp.length() > 0){
newLine(indent);
out.write("<errorMessage>");
out.write(temp);
out.write(makeXMLSafe(temp));
out.write("</errorMessage>");
}
return;
......@@ -1305,14 +1320,16 @@ public class DSMLWriter implements LDAPWriter {
byte bytevalues[][] = attr.getByteValueArray();
for(int i=0; i<values.length; i++){
newLine(4);
if (Base64.isValidUTF8(bytevalues[i], false)){
if (Base64.isValidUTF8(bytevalues[i],true) && this.isXMLSafe(bytevalues[i])){
out.write("<value>");
String xmlvalue = values[i];
xmlvalue = xmlvalue.replaceAll("&", "&amp;");
xmlvalue = xmlvalue.replaceAll("<", "&lt;");
xmlvalue = xmlvalue.replaceAll(">", "&gt;");
xmlvalue = xmlvalue.replaceAll("'", "&apos");
xmlvalue = xmlvalue.replaceAll("'", "&apos;");
xmlvalue = xmlvalue.replaceAll("\"", "&quot;");
out.write(xmlvalue);
out.write("</value>");
} else {
......@@ -1346,7 +1363,7 @@ public class DSMLWriter implements LDAPWriter {
state = RESPONSE_BATCH;
}
else{
out.write(BATCH_REQUEST_START);
out.write(BATCH_REQUEST_START + (resumeOnError ? "resume" : "exit") + "\">");
state = REQUEST_BATCH;
}
}
......@@ -1416,4 +1433,49 @@ public class DSMLWriter implements LDAPWriter {
this.tabString = temp.toString();
return;
}
private boolean isXMLSafe(byte[] val) {
boolean safe = true;
for (int i=0,m=val.length;i<m;i++) {
if (val[i] >= 0 && val[i] <= 31) {
safe = false;
break;
}
}
return safe;
}
private String makeXMLSafe(String msg) {
if (msg == null) {
return "";
}
byte[] val = msg.getBytes();
boolean safe = true;
for (int i=0,m=val.length;i<m;i++) {
if (val[i] >= 0 && val[i] <= 31) {
val[i] = (byte) ' ';
}
}
return new String(val);
}
private String makeAttributeSafe(String attrib) {
String ret = attrib;
ret = ret.replaceAll("&", "&amp;");
ret = ret.replaceAll(">", "&gt;");
ret = ret.replaceAll("'", "&apos;");
ret = ret.replaceAll("\"", "&quot;");
return ret;
}
}
/* **************************************************************************
*
* Copyright (C) 2004 Octet String, Inc. All Rights Reserved.
*
* THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
* TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
* TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
* AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
* IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
* OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
* PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
* THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
******************************************************************************/
package com.novell.ldap.util;
import org.apache.commons.httpclient.methods.*;
import com.novell.ldap.*;
/**
*
* @author Marc Boorshtein
*
* This interfact exposes methods that allow for the manipulation of the HTTP Request before it
* is sent to the server. This is usefaul for adjusting HTTP headers
*/
public interface HttpRequestCallback {
/**