Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.naming/share/classes/com/sun/jndi/ldap/LdapName.java
41161 views
1
/*
2
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package com.sun.jndi.ldap;
27
28
29
import java.util.Enumeration;
30
import java.util.Vector;
31
import java.util.Locale;
32
33
import javax.naming.*;
34
import javax.naming.directory.Attributes;
35
import javax.naming.directory.Attribute;
36
import javax.naming.directory.BasicAttributes;
37
38
39
/**
40
* <code>LdapName</code> implements compound names for LDAP v3 as
41
* specified by RFC 2253.
42
*<p>
43
* RFC 2253 has a few ambiguities and outright inconsistencies. These
44
* are resolved as follows:
45
* <ul>
46
* <li> RFC 2253 leaves the term "whitespace" undefined. The
47
* definition of "optional-space" given in RFC 1779 is used in
48
* its place: either a space character or a carriage return ("\r").
49
* <li> Whitespace is allowed on either side of ',', ';', '=', and '+'.
50
* Such whitespace is accepted but not generated by this code,
51
* and is ignored when comparing names.
52
* <li> AttributeValue strings containing '=' or non-leading '#'
53
* characters (unescaped) are accepted.
54
* </ul>
55
*<p>
56
* String names passed to <code>LdapName</code> or returned by it
57
* use the full 16-bit Unicode character set. They may also contain
58
* characters encoded into UTF-8 with each octet represented by a
59
* three-character substring such as "\\B4".
60
* They may not, however, contain characters encoded into UTF-8 with
61
* each octet represented by a single character in the string: the
62
* meaning would be ambiguous.
63
*<p>
64
* <code>LdapName</code> will properly parse all valid names, but
65
* does not attempt to detect all possible violations when parsing
66
* invalid names. It's "generous".
67
*<p>
68
* When names are tested for equality, attribute types and binary
69
* values are case-insensitive, and string values are by default
70
* case-insensitive.
71
* String values with different but equivalent usage of quoting,
72
* escaping, or UTF8-hex-encoding are considered equal. The order of
73
* components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not
74
* significant.
75
*
76
* @author Scott Seligman
77
*/
78
79
public final class LdapName implements Name {
80
81
private transient String unparsed; // if non-null, the DN in unparsed form
82
private transient Vector<Rdn> rdns; // parsed name components
83
private transient boolean valuesCaseSensitive = false;
84
85
/**
86
* Constructs an LDAP name from the given DN.
87
*
88
* @param name An LDAP DN. To JNDI, a compound name.
89
*
90
* @throws InvalidNameException if a syntax violation is detected.
91
*/
92
public LdapName(String name) throws InvalidNameException {
93
unparsed = name;
94
parse();
95
}
96
97
/*
98
* Constructs an LDAP name given its parsed components and, optionally
99
* (if "name" is not null), the unparsed DN.
100
*/
101
@SuppressWarnings("unchecked") // clone()
102
private LdapName(String name, Vector<Rdn> rdns) {
103
unparsed = name;
104
this.rdns = (Vector<Rdn>)rdns.clone();
105
}
106
107
/*
108
* Constructs an LDAP name given its parsed components (the elements
109
* of "rdns" in the range [beg,end)) and, optionally
110
* (if "name" is not null), the unparsed DN.
111
*/
112
private LdapName(String name, Vector<Rdn> rdns, int beg, int end) {
113
unparsed = name;
114
this.rdns = new Vector<>();
115
for (int i = beg; i < end; i++) {
116
this.rdns.addElement(rdns.elementAt(i));
117
}
118
}
119
120
121
public Object clone() {
122
return new LdapName(unparsed, rdns);
123
}
124
125
public String toString() {
126
if (unparsed != null) {
127
return unparsed;
128
}
129
130
StringBuffer buf = new StringBuffer();
131
for (int i = rdns.size() - 1; i >= 0; i--) {
132
if (i < rdns.size() - 1) {
133
buf.append(',');
134
}
135
Rdn rdn = rdns.elementAt(i);
136
buf.append(rdn);
137
}
138
139
unparsed = new String(buf);
140
return unparsed;
141
}
142
143
public boolean equals(Object obj) {
144
return ((obj instanceof LdapName) &&
145
(compareTo(obj) == 0));
146
}
147
148
public int compareTo(Object obj) {
149
LdapName that = (LdapName)obj;
150
151
if ((obj == this) || // check possible shortcuts
152
(unparsed != null && unparsed.equals(that.unparsed))) {
153
return 0;
154
}
155
156
// Compare RDNs one by one, lexicographically.
157
int minSize = Math.min(rdns.size(), that.rdns.size());
158
for (int i = 0 ; i < minSize; i++) {
159
// Compare a single pair of RDNs.
160
Rdn rdn1 = rdns.elementAt(i);
161
Rdn rdn2 = that.rdns.elementAt(i);
162
163
int diff = rdn1.compareTo(rdn2);
164
if (diff != 0) {
165
return diff;
166
}
167
}
168
return (rdns.size() - that.rdns.size()); // longer DN wins
169
}
170
171
public int hashCode() {
172
// Sum up the hash codes of the components.
173
int hash = 0;
174
175
// For each RDN...
176
for (int i = 0; i < rdns.size(); i++) {
177
Rdn rdn = rdns.elementAt(i);
178
hash += rdn.hashCode();
179
}
180
return hash;
181
}
182
183
public int size() {
184
return rdns.size();
185
}
186
187
public boolean isEmpty() {
188
return rdns.isEmpty();
189
}
190
191
public Enumeration<String> getAll() {
192
final Enumeration<Rdn> enum_ = rdns.elements();
193
194
return new Enumeration<String>() {
195
public boolean hasMoreElements() {
196
return enum_.hasMoreElements();
197
}
198
public String nextElement() {
199
return enum_.nextElement().toString();
200
}
201
};
202
}
203
204
public String get(int pos) {
205
return rdns.elementAt(pos).toString();
206
}
207
208
public Name getPrefix(int pos) {
209
return new LdapName(null, rdns, 0, pos);
210
}
211
212
public Name getSuffix(int pos) {
213
return new LdapName(null, rdns, pos, rdns.size());
214
}
215
216
public boolean startsWith(Name n) {
217
int len1 = rdns.size();
218
int len2 = n.size();
219
return (len1 >= len2 &&
220
matches(0, len2, n));
221
}
222
223
public boolean endsWith(Name n) {
224
int len1 = rdns.size();
225
int len2 = n.size();
226
return (len1 >= len2 &&
227
matches(len1 - len2, len1, n));
228
}
229
230
/**
231
* Controls whether string-values are treated as case-sensitive
232
* when the string values within names are compared. The default
233
* behavior is case-insensitive comparison.
234
*/
235
public void setValuesCaseSensitive(boolean caseSensitive) {
236
toString();
237
rdns = null; // clear any cached information
238
try {
239
parse();
240
} catch (InvalidNameException e) {
241
// shouldn't happen
242
throw new IllegalStateException("Cannot parse name: " + unparsed);
243
}
244
valuesCaseSensitive = caseSensitive;
245
}
246
247
/*
248
* Helper method for startsWith() and endsWith().
249
* Returns true if components [beg,end) match the components of "n".
250
* If "n" is not an LdapName, each of its components is parsed as
251
* the string form of an RDN.
252
* The following must hold: end - beg == n.size().
253
*/
254
private boolean matches(int beg, int end, Name n) {
255
for (int i = beg; i < end; i++) {
256
Rdn rdn;
257
if (n instanceof LdapName) {
258
LdapName ln = (LdapName)n;
259
rdn = ln.rdns.elementAt(i - beg);
260
} else {
261
String rdnString = n.get(i - beg);
262
try {
263
rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn();
264
} catch (InvalidNameException e) {
265
return false;
266
}
267
}
268
269
if (!rdn.equals(rdns.elementAt(i))) {
270
return false;
271
}
272
}
273
return true;
274
}
275
276
public Name addAll(Name suffix) throws InvalidNameException {
277
return addAll(size(), suffix);
278
}
279
280
/*
281
* If "suffix" is not an LdapName, each of its components is parsed as
282
* the string form of an RDN.
283
*/
284
public Name addAll(int pos, Name suffix) throws InvalidNameException {
285
if (suffix instanceof LdapName) {
286
LdapName s = (LdapName)suffix;
287
for (int i = 0; i < s.rdns.size(); i++) {
288
rdns.insertElementAt(s.rdns.elementAt(i), pos++);
289
}
290
} else {
291
Enumeration<String> comps = suffix.getAll();
292
while (comps.hasMoreElements()) {
293
DnParser p = new DnParser(comps.nextElement(),
294
valuesCaseSensitive);
295
rdns.insertElementAt(p.getRdn(), pos++);
296
}
297
}
298
unparsed = null; // no longer valid
299
return this;
300
}
301
302
public Name add(String comp) throws InvalidNameException {
303
return add(size(), comp);
304
}
305
306
public Name add(int pos, String comp) throws InvalidNameException {
307
Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn();
308
rdns.insertElementAt(rdn, pos);
309
unparsed = null; // no longer valid
310
return this;
311
}
312
313
public Object remove(int pos) throws InvalidNameException {
314
String comp = get(pos);
315
rdns.removeElementAt(pos);
316
unparsed = null; // no longer valid
317
return comp;
318
}
319
320
321
private void parse() throws InvalidNameException {
322
rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn();
323
}
324
325
/*
326
* Best guess as to what RFC 2253 means by "whitespace".
327
*/
328
private static boolean isWhitespace(char c) {
329
return (c == ' ' || c == '\r');
330
}
331
332
/**
333
* Given the value of an attribute, returns a string suitable
334
* for inclusion in a DN. If the value is a string, this is
335
* accomplished by using backslash (\) to escape the following
336
* characters:
337
*<ul>
338
*<li>leading and trailing whitespace
339
*<li><pre>{@literal , = + < > # ; " \}</pre>
340
*</ul>
341
* If the value is a byte array, it is converted to hex
342
* notation (such as "#CEB1DF80").
343
*/
344
public static String escapeAttributeValue(Object val) {
345
return TypeAndValue.escapeValue(val);
346
}
347
348
/**
349
* Given an attribute value formatted according to RFC 2253,
350
* returns the unformatted value. Returns a string value as
351
* a string, and a binary value as a byte array.
352
*/
353
public static Object unescapeAttributeValue(String val) {
354
return TypeAndValue.unescapeValue(val);
355
}
356
357
/**
358
* Serializes only the unparsed DN, for compactness and to avoid
359
* any implementation dependency.
360
*
361
* @serialData The DN string and a boolean indicating whether
362
* the values are case sensitive.
363
*/
364
private void writeObject(java.io.ObjectOutputStream s)
365
throws java.io.IOException {
366
s.writeObject(toString());
367
s.writeBoolean(valuesCaseSensitive);
368
}
369
370
private void readObject(java.io.ObjectInputStream s)
371
throws java.io.IOException, ClassNotFoundException {
372
unparsed = (String)s.readObject();
373
valuesCaseSensitive = s.readBoolean();
374
try {
375
parse();
376
} catch (InvalidNameException e) {
377
// shouldn't happen
378
throw new java.io.StreamCorruptedException(
379
"Invalid name: " + unparsed);
380
}
381
}
382
383
static final long serialVersionUID = -1595520034788997356L;
384
385
386
/*
387
* DnParser implements a recursive descent parser for a single DN.
388
*/
389
static class DnParser {
390
391
private final String name; // DN being parsed
392
private final char[] chars; // characters in LDAP name being parsed
393
private final int len; // length of "chars"
394
private int cur = 0; // index of first unconsumed char in "chars"
395
private boolean valuesCaseSensitive;
396
397
/*
398
* Given an LDAP DN in string form, returns a parser for it.
399
*/
400
DnParser(String name, boolean valuesCaseSensitive)
401
throws InvalidNameException {
402
this.name = name;
403
len = name.length();
404
chars = name.toCharArray();
405
this.valuesCaseSensitive = valuesCaseSensitive;
406
}
407
408
/*
409
* Parses the DN, returning a Vector of its RDNs.
410
*/
411
Vector<Rdn> getDn() throws InvalidNameException {
412
cur = 0;
413
Vector<Rdn> rdns = new Vector<>(len / 3 + 10); // leave room for growth
414
415
if (len == 0) {
416
return rdns;
417
}
418
419
rdns.addElement(parseRdn());
420
while (cur < len) {
421
if (chars[cur] == ',' || chars[cur] == ';') {
422
++cur;
423
rdns.insertElementAt(parseRdn(), 0);
424
} else {
425
throw new InvalidNameException("Invalid name: " + name);
426
}
427
}
428
return rdns;
429
}
430
431
/*
432
* Parses the DN, if it is known to contain a single RDN.
433
*/
434
Rdn getRdn() throws InvalidNameException {
435
Rdn rdn = parseRdn();
436
if (cur < len) {
437
throw new InvalidNameException("Invalid RDN: " + name);
438
}
439
return rdn;
440
}
441
442
/*
443
* Parses the next RDN and returns it. Throws an exception if
444
* none is found. Leading and trailing whitespace is consumed.
445
*/
446
private Rdn parseRdn() throws InvalidNameException {
447
448
Rdn rdn = new Rdn();
449
while (cur < len) {
450
consumeWhitespace();
451
String attrType = parseAttrType();
452
consumeWhitespace();
453
if (cur >= len || chars[cur] != '=') {
454
throw new InvalidNameException("Invalid name: " + name);
455
}
456
++cur; // consume '='
457
consumeWhitespace();
458
String value = parseAttrValue();
459
consumeWhitespace();
460
461
rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive));
462
if (cur >= len || chars[cur] != '+') {
463
break;
464
}
465
++cur; // consume '+'
466
}
467
return rdn;
468
}
469
470
/*
471
* Returns the attribute type that begins at the next unconsumed
472
* char. No leading whitespace is expected.
473
* This routine is more generous than RFC 2253. It accepts
474
* attribute types composed of any nonempty combination of Unicode
475
* letters, Unicode digits, '.', '-', and internal space characters.
476
*/
477
private String parseAttrType() throws InvalidNameException {
478
479
final int beg = cur;
480
while (cur < len) {
481
char c = chars[cur];
482
if (Character.isLetterOrDigit(c) ||
483
c == '.' ||
484
c == '-' ||
485
c == ' ') {
486
++cur;
487
} else {
488
break;
489
}
490
}
491
// Back out any trailing spaces.
492
while ((cur > beg) && (chars[cur - 1] == ' ')) {
493
--cur;
494
}
495
496
if (beg == cur) {
497
throw new InvalidNameException("Invalid name: " + name);
498
}
499
return new String(chars, beg, cur - beg);
500
}
501
502
/*
503
* Returns the attribute value that begins at the next unconsumed
504
* char. No leading whitespace is expected.
505
*/
506
private String parseAttrValue() throws InvalidNameException {
507
508
if (cur < len && chars[cur] == '#') {
509
return parseBinaryAttrValue();
510
} else if (cur < len && chars[cur] == '"') {
511
return parseQuotedAttrValue();
512
} else {
513
return parseStringAttrValue();
514
}
515
}
516
517
private String parseBinaryAttrValue() throws InvalidNameException {
518
final int beg = cur;
519
++cur; // consume '#'
520
while (cur < len &&
521
Character.isLetterOrDigit(chars[cur])) {
522
++cur;
523
}
524
return new String(chars, beg, cur - beg);
525
}
526
527
private String parseQuotedAttrValue() throws InvalidNameException {
528
529
final int beg = cur;
530
++cur; // consume '"'
531
532
while ((cur < len) && chars[cur] != '"') {
533
if (chars[cur] == '\\') {
534
++cur; // consume backslash, then what follows
535
}
536
++cur;
537
}
538
if (cur >= len) { // no closing quote
539
throw new InvalidNameException("Invalid name: " + name);
540
}
541
++cur ; // consume closing quote
542
543
return new String(chars, beg, cur - beg);
544
}
545
546
private String parseStringAttrValue() throws InvalidNameException {
547
548
final int beg = cur;
549
int esc = -1; // index of the most recently escaped character
550
551
while ((cur < len) && !atTerminator()) {
552
if (chars[cur] == '\\') {
553
++cur; // consume backslash, then what follows
554
esc = cur;
555
}
556
++cur;
557
}
558
if (cur > len) { // 'twas backslash followed by nothing
559
throw new InvalidNameException("Invalid name: " + name);
560
}
561
562
// Trim off (unescaped) trailing whitespace.
563
int end;
564
for (end = cur; end > beg; end--) {
565
if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) {
566
break;
567
}
568
}
569
return new String(chars, beg, end - beg);
570
}
571
572
private void consumeWhitespace() {
573
while ((cur < len) && isWhitespace(chars[cur])) {
574
++cur;
575
}
576
}
577
578
/*
579
* Returns true if next unconsumed character is one that terminates
580
* a string attribute value.
581
*/
582
private boolean atTerminator() {
583
return (cur < len &&
584
(chars[cur] == ',' ||
585
chars[cur] == ';' ||
586
chars[cur] == '+'));
587
}
588
}
589
590
591
/*
592
* Class Rdn represents a set of TypeAndValue.
593
*/
594
static class Rdn {
595
596
/*
597
* A vector of the TypeAndValue elements of this Rdn.
598
* It is sorted to facilitate set operations.
599
*/
600
private final Vector<TypeAndValue> tvs = new Vector<>();
601
602
void add(TypeAndValue tv) {
603
604
// Set i to index of first element greater than tv, or to
605
// tvs.size() if there is none.
606
int i;
607
for (i = 0; i < tvs.size(); i++) {
608
int diff = tv.compareTo(tvs.elementAt(i));
609
if (diff == 0) {
610
return; // tv is a duplicate: ignore it
611
} else if (diff < 0) {
612
break;
613
}
614
}
615
616
tvs.insertElementAt(tv, i);
617
}
618
619
public String toString() {
620
StringBuffer buf = new StringBuffer();
621
for (int i = 0; i < tvs.size(); i++) {
622
if (i > 0) {
623
buf.append('+');
624
}
625
buf.append(tvs.elementAt(i));
626
}
627
return new String(buf);
628
}
629
630
public boolean equals(Object obj) {
631
return ((obj instanceof Rdn) &&
632
(compareTo(obj) == 0));
633
}
634
635
// Compare TypeAndValue components one by one, lexicographically.
636
public int compareTo(Object obj) {
637
Rdn that = (Rdn)obj;
638
int minSize = Math.min(tvs.size(), that.tvs.size());
639
for (int i = 0; i < minSize; i++) {
640
// Compare a single pair of type/value pairs.
641
TypeAndValue tv = tvs.elementAt(i);
642
int diff = tv.compareTo(that.tvs.elementAt(i));
643
if (diff != 0) {
644
return diff;
645
}
646
}
647
return (tvs.size() - that.tvs.size()); // longer RDN wins
648
}
649
650
public int hashCode() {
651
// Sum up the hash codes of the components.
652
int hash = 0;
653
654
// For each type/value pair...
655
for (int i = 0; i < tvs.size(); i++) {
656
hash += tvs.elementAt(i).hashCode();
657
}
658
return hash;
659
}
660
661
Attributes toAttributes() {
662
Attributes attrs = new BasicAttributes(true);
663
TypeAndValue tv;
664
Attribute attr;
665
666
for (int i = 0; i < tvs.size(); i++) {
667
tv = tvs.elementAt(i);
668
if ((attr = attrs.get(tv.getType())) == null) {
669
attrs.put(tv.getType(), tv.getUnescapedValue());
670
} else {
671
attr.add(tv.getUnescapedValue());
672
}
673
}
674
return attrs;
675
}
676
}
677
678
679
/*
680
* Class TypeAndValue represents an attribute type and its
681
* corresponding value.
682
*/
683
static class TypeAndValue {
684
685
private final String type;
686
private final String value; // value, escaped or quoted
687
private final boolean binary;
688
private final boolean valueCaseSensitive;
689
690
// If non-null, a canonical representation of the value suitable
691
// for comparison using String.compareTo().
692
private String comparable = null;
693
694
TypeAndValue(String type, String value, boolean valueCaseSensitive) {
695
this.type = type;
696
this.value = value;
697
binary = value.startsWith("#");
698
this.valueCaseSensitive = valueCaseSensitive;
699
}
700
701
public String toString() {
702
return (type + "=" + value);
703
}
704
705
public int compareTo(Object obj) {
706
// NB: Any change here affecting equality must be
707
// reflected in hashCode().
708
709
TypeAndValue that = (TypeAndValue)obj;
710
711
int diff = type.compareToIgnoreCase(that.type);
712
if (diff != 0) {
713
return diff;
714
}
715
if (value.equals(that.value)) { // try shortcut
716
return 0;
717
}
718
return getValueComparable().compareTo(that.getValueComparable());
719
}
720
721
public boolean equals(Object obj) {
722
// NB: Any change here must be reflected in hashCode().
723
if (!(obj instanceof TypeAndValue)) {
724
return false;
725
}
726
TypeAndValue that = (TypeAndValue)obj;
727
return (type.equalsIgnoreCase(that.type) &&
728
(value.equals(that.value) ||
729
getValueComparable().equals(that.getValueComparable())));
730
}
731
732
public int hashCode() {
733
// If two objects are equal, their hash codes must match.
734
return (type.toUpperCase(Locale.ENGLISH).hashCode() +
735
getValueComparable().hashCode());
736
}
737
738
/*
739
* Returns the type.
740
*/
741
String getType() {
742
return type;
743
}
744
745
/*
746
* Returns the unescaped value.
747
*/
748
Object getUnescapedValue() {
749
return unescapeValue(value);
750
}
751
752
/*
753
* Returns a canonical representation of "value" suitable for
754
* comparison using String.compareTo(). If "value" is a string,
755
* it is returned with escapes and quotes stripped away, and
756
* hex-encoded UTF-8 converted to 16-bit Unicode chars.
757
* If value's case is to be ignored, it is returned in uppercase.
758
* If "value" is binary, it is returned in uppercase but
759
* otherwise unmodified.
760
*/
761
private String getValueComparable() {
762
if (comparable != null) {
763
return comparable; // return cached result
764
}
765
766
// cache result
767
if (binary) {
768
comparable = value.toUpperCase(Locale.ENGLISH);
769
} else {
770
comparable = (String)unescapeValue(value);
771
if (!valueCaseSensitive) {
772
// ignore case
773
comparable = comparable.toUpperCase(Locale.ENGLISH);
774
}
775
}
776
return comparable;
777
}
778
779
/*
780
* Given the value of an attribute, returns a string suitable
781
* for inclusion in a DN.
782
*/
783
static String escapeValue(Object val) {
784
return (val instanceof byte[])
785
? escapeBinaryValue((byte[])val)
786
: escapeStringValue((String)val);
787
}
788
789
/*
790
* Given the value of a string-valued attribute, returns a
791
* string suitable for inclusion in a DN. This is accomplished by
792
* using backslash (\) to escape the following characters:
793
* leading and trailing whitespace
794
* , = + < > # ; " \
795
*/
796
private static String escapeStringValue(String val) {
797
798
final String escapees = ",=+<>#;\"\\";
799
char[] chars = val.toCharArray();
800
StringBuffer buf = new StringBuffer(2 * val.length());
801
802
// Find leading and trailing whitespace.
803
int lead; // index of first char that is not leading whitespace
804
for (lead = 0; lead < chars.length; lead++) {
805
if (!isWhitespace(chars[lead])) {
806
break;
807
}
808
}
809
int trail; // index of last char that is not trailing whitespace
810
for (trail = chars.length - 1; trail >= 0; trail--) {
811
if (!isWhitespace(chars[trail])) {
812
break;
813
}
814
}
815
816
for (int i = 0; i < chars.length; i++) {
817
char c = chars[i];
818
if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {
819
buf.append('\\');
820
}
821
buf.append(c);
822
}
823
return new String(buf);
824
}
825
826
/*
827
* Given the value of a binary attribute, returns a string
828
* suitable for inclusion in a DN (such as "#CEB1DF80").
829
*/
830
private static String escapeBinaryValue(byte[] val) {
831
832
StringBuffer buf = new StringBuffer(1 + 2 * val.length);
833
buf.append("#");
834
835
for (int i = 0; i < val.length; i++) {
836
byte b = val[i];
837
buf.append(Character.forDigit(0xF & (b >>> 4), 16));
838
buf.append(Character.forDigit(0xF & b, 16));
839
}
840
841
return (new String(buf)).toUpperCase(Locale.ENGLISH);
842
}
843
844
/*
845
* Given an attribute value formatted according to RFC 2253,
846
* returns the unformatted value. Escapes and quotes are
847
* stripped away, and hex-encoded UTF-8 is converted to 16-bit
848
* Unicode chars. Returns a string value as a String, and a
849
* binary value as a byte array.
850
*/
851
static Object unescapeValue(String val) {
852
853
char[] chars = val.toCharArray();
854
int beg = 0;
855
int end = chars.length;
856
857
// Trim off leading and trailing whitespace.
858
while ((beg < end) && isWhitespace(chars[beg])) {
859
++beg;
860
}
861
while ((beg < end) && isWhitespace(chars[end - 1])) {
862
--end;
863
}
864
865
// Add back the trailing whitespace with a preceding '\'
866
// (escaped or unescaped) that was taken off in the above
867
// loop. Whether or not to retain this whitespace is
868
// decided below.
869
if (end != chars.length &&
870
(beg < end) &&
871
chars[end - 1] == '\\') {
872
end++;
873
}
874
if (beg >= end) {
875
return "";
876
}
877
878
if (chars[beg] == '#') {
879
// Value is binary (eg: "#CEB1DF80").
880
return decodeHexPairs(chars, ++beg, end);
881
}
882
883
// Trim off quotes.
884
if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {
885
++beg;
886
--end;
887
}
888
889
StringBuffer buf = new StringBuffer(end - beg);
890
int esc = -1; // index of the last escaped character
891
892
for (int i = beg; i < end; i++) {
893
if ((chars[i] == '\\') && (i + 1 < end)) {
894
if (!Character.isLetterOrDigit(chars[i + 1])) {
895
++i; // skip backslash
896
buf.append(chars[i]); // snarf escaped char
897
esc = i;
898
} else {
899
900
// Convert hex-encoded UTF-8 to 16-bit chars.
901
byte[] utf8 = getUtf8Octets(chars, i, end);
902
if (utf8.length > 0) {
903
try {
904
buf.append(new String(utf8, "UTF8"));
905
} catch (java.io.UnsupportedEncodingException e) {
906
// shouldn't happen
907
}
908
i += utf8.length * 3 - 1;
909
} else {
910
throw new IllegalArgumentException(
911
"Not a valid attribute string value:" +
912
val +", improper usage of backslash");
913
}
914
}
915
} else {
916
buf.append(chars[i]); // snarf unescaped char
917
}
918
}
919
920
// Get rid of the unescaped trailing whitespace with the
921
// preceding '\' character that was previously added back.
922
int len = buf.length();
923
if (isWhitespace(buf.charAt(len - 1)) && esc != (end - 1)) {
924
buf.setLength(len - 1);
925
}
926
927
return new String(buf);
928
}
929
930
931
/*
932
* Given an array of chars (with starting and ending indexes into it)
933
* representing bytes encoded as hex-pairs (such as "CEB1DF80"),
934
* returns a byte array containing the decoded bytes.
935
*/
936
private static byte[] decodeHexPairs(char[] chars, int beg, int end) {
937
byte[] bytes = new byte[(end - beg) / 2];
938
for (int i = 0; beg + 1 < end; i++) {
939
int hi = Character.digit(chars[beg], 16);
940
int lo = Character.digit(chars[beg + 1], 16);
941
if (hi < 0 || lo < 0) {
942
break;
943
}
944
bytes[i] = (byte)((hi<<4) + lo);
945
beg += 2;
946
}
947
if (beg != end) {
948
throw new IllegalArgumentException(
949
"Illegal attribute value: #" + new String(chars));
950
}
951
return bytes;
952
}
953
954
/*
955
* Given an array of chars (with starting and ending indexes into it),
956
* finds the largest prefix consisting of hex-encoded UTF-8 octets,
957
* and returns a byte array containing the corresponding UTF-8 octets.
958
*
959
* Hex-encoded UTF-8 octets look like this:
960
* \03\B1\DF\80
961
*/
962
private static byte[] getUtf8Octets(char[] chars, int beg, int end) {
963
byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room
964
int len = 0; // index of first unused byte in utf8
965
966
while ((beg + 2 < end) &&
967
(chars[beg++] == '\\')) {
968
int hi = Character.digit(chars[beg++], 16);
969
int lo = Character.digit(chars[beg++], 16);
970
if (hi < 0 || lo < 0) {
971
break;
972
}
973
utf8[len++] = (byte)((hi<<4) + lo);
974
}
975
976
if (len == utf8.length) {
977
return utf8;
978
} else {
979
byte[] res = new byte[len];
980
System.arraycopy(utf8, 0, res, 0, len);
981
return res;
982
}
983
}
984
}
985
986
987
/*
988
* For testing.
989
*/
990
/*
991
public static void main(String[] args) {
992
993
try {
994
if (args.length == 1) { // parse and print components
995
LdapName n = new LdapName(args[0]);
996
997
Enumeration rdns = n.rdns.elements();
998
while (rdns.hasMoreElements()) {
999
Rdn rdn = (Rdn)rdns.nextElement();
1000
for (int i = 0; i < rdn.tvs.size(); i++) {
1001
System.out.print("[" + rdn.tvs.elementAt(i) + "]");
1002
}
1003
System.out.println();
1004
}
1005
1006
} else { // compare two names
1007
LdapName n1 = new LdapName(args[0]);
1008
LdapName n2 = new LdapName(args[1]);
1009
n1.unparsed = null;
1010
n2.unparsed = null;
1011
boolean eq = n1.equals(n2);
1012
System.out.println("[" + n1 + (eq ? "] == [" : "] != [")
1013
+ n2 + "]");
1014
}
1015
} catch (Exception e) {
1016
e.printStackTrace();
1017
}
1018
}
1019
*/
1020
}
1021
1022