View Javadoc
1   package org.argeo.naming;
2   
3   import java.io.BufferedReader;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.InputStreamReader;
7   import java.io.Reader;
8   import java.nio.charset.Charset;
9   import java.nio.charset.StandardCharsets;
10  import java.util.ArrayList;
11  import java.util.Base64;
12  import java.util.List;
13  import java.util.SortedMap;
14  import java.util.TreeMap;
15  
16  import javax.naming.InvalidNameException;
17  import javax.naming.NamingException;
18  import javax.naming.directory.Attribute;
19  import javax.naming.directory.Attributes;
20  import javax.naming.directory.BasicAttribute;
21  import javax.naming.directory.BasicAttributes;
22  import javax.naming.ldap.LdapName;
23  import javax.naming.ldap.Rdn;
24  
25  import org.argeo.osgi.useradmin.UserDirectoryException;
26  
27  /** Basic LDIF parser. */
28  public class LdifParser {
29  	private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
30  
31  	protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
32  			Attributes currentAttributes) {
33  		try {
34  			Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
35  			Attribute nameAttr = currentAttributes.get(nameRdn.getType());
36  			if (nameAttr == null)
37  				currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
38  			else if (!nameAttr.get().equals(nameRdn.getValue()))
39  				throw new UserDirectoryException(
40  						"Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
41  								+ " (shortly before line " + lineNumber + " in LDIF file)");
42  			Attributes previous = res.put(currentDn, currentAttributes);
43  			return previous;
44  		} catch (NamingException e) {
45  			throw new UserDirectoryException("Cannot add " + currentDn, e);
46  		}
47  	}
48  
49  	/** With UTF-8 charset */
50  	public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
51  		try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
52  			return read(reader);
53  		} finally {
54  			try {
55  				in.close();
56  			} catch (IOException e) {
57  				// silent
58  			}
59  		}
60  	}
61  
62  	/** Will close the reader. */
63  	public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
64  		SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
65  		try {
66  			List<String> lines = new ArrayList<>();
67  			try (BufferedReader br = new BufferedReader(reader)) {
68  				String line;
69  				while ((line = br.readLine()) != null) {
70  					lines.add(line);
71  				}
72  			}
73  			if (lines.size() == 0)
74  				return res;
75  			// add an empty new line since the last line is not checked
76  			if (!lines.get(lines.size() - 1).equals(""))
77  				lines.add("");
78  
79  			LdapName currentDn = null;
80  			Attributes currentAttributes = null;
81  			StringBuilder currentEntry = new StringBuilder();
82  
83  			readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
84  				String line = lines.get(lineNumber);
85  				boolean isLastLine = false;
86  				if (lineNumber == lines.size() - 1)
87  					isLastLine = true;
88  				if (line.startsWith(" ")) {
89  					currentEntry.append(line.substring(1));
90  					if (!isLastLine)
91  						continue readLines;
92  				}
93  
94  				if (currentEntry.length() != 0 || isLastLine) {
95  					// read previous attribute
96  					StringBuilder attrId = new StringBuilder(8);
97  					boolean isBase64 = false;
98  					readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
99  						char c = currentEntry.charAt(i);
100 						if (c == ':') {
101 							if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
102 								isBase64 = true;
103 							currentEntry.delete(0, i + (isBase64 ? 2 : 1));
104 							break readAttrId;
105 						} else {
106 							attrId.append(c);
107 						}
108 					}
109 
110 					String attributeId = attrId.toString();
111 					// TODO should we really trim the end of the string as well?
112 					String cleanValueStr = currentEntry.toString().trim();
113 					Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
114 
115 					// manage DN attributes
116 					if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
117 						if (currentDn != null) {
118 							//
119 							// ADD
120 							//
121 							Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
122 							if (previous != null) {
123 //								log.warn("There was already an entry with DN " + currentDn
124 //										+ ", which has been discarded by a subsequent one.");
125 							}
126 						}
127 
128 						if (attributeId.equals(LdapAttrs.DN))
129 							try {
130 								currentDn = new LdapName(attributeValue.toString());
131 								currentAttributes = new BasicAttributes(true);
132 							} catch (InvalidNameException e) {
133 //								log.error(attributeValue + " not a valid DN, skipping the entry.");
134 								currentDn = null;
135 								currentAttributes = null;
136 							}
137 					}
138 
139 					// store attribute
140 					if (currentAttributes != null) {
141 						Attribute attribute = currentAttributes.get(attributeId);
142 						if (attribute == null) {
143 							attribute = new BasicAttribute(attributeId);
144 							currentAttributes.put(attribute);
145 						}
146 						attribute.add(attributeValue);
147 					}
148 					currentEntry = new StringBuilder();
149 				}
150 				currentEntry.append(line);
151 			}
152 		} finally {
153 			try {
154 				reader.close();
155 			} catch (IOException e) {
156 				// silent
157 			}
158 		}
159 		return res;
160 	}
161 }