1/*
2 * Copyright (c) 1999, 2011, 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
26package javax.naming;
27
28import java.util.Locale;
29import java.util.Vector;
30import java.util.Enumeration;
31import java.util.Properties;
32import java.util.NoSuchElementException;
33
34/**
35  * The implementation class for CompoundName and CompositeName.
36  * This class is package private.
37  *
38  * @author Rosanna Lee
39  * @author Scott Seligman
40  * @author Aravindan Ranganathan
41  * @since 1.3
42  */
43
44class NameImpl {
45    private static final byte LEFT_TO_RIGHT = 1;
46    private static final byte RIGHT_TO_LEFT = 2;
47    private static final byte FLAT = 0;
48
49    private Vector<String> components;
50
51    private byte syntaxDirection = LEFT_TO_RIGHT;
52    private String syntaxSeparator = "/";
53    private String syntaxSeparator2 = null;
54    private boolean syntaxCaseInsensitive = false;
55    private boolean syntaxTrimBlanks = false;
56    private String syntaxEscape = "\\";
57    private String syntaxBeginQuote1 = "\"";
58    private String syntaxEndQuote1 = "\"";
59    private String syntaxBeginQuote2 = "'";
60    private String syntaxEndQuote2 = "'";
61    private String syntaxAvaSeparator = null;
62    private String syntaxTypevalSeparator = null;
63
64    // escapingStyle gives the method used at creation time for
65    // quoting or escaping characters in the name.  It is set to the
66    // first style of quote or escape encountered if and when the name
67    // is parsed.
68    private static final int STYLE_NONE = 0;
69    private static final int STYLE_QUOTE1 = 1;
70    private static final int STYLE_QUOTE2 = 2;
71    private static final int STYLE_ESCAPE = 3;
72    private int escapingStyle = STYLE_NONE;
73
74    // Returns true if "match" is not null, and n contains "match" at
75    // position i.
76    private final boolean isA(String n, int i, String match) {
77        return (match != null && n.startsWith(match, i));
78    }
79
80    private final boolean isMeta(String n, int i) {
81        return (isA(n, i, syntaxEscape) ||
82                isA(n, i, syntaxBeginQuote1) ||
83                isA(n, i, syntaxBeginQuote2) ||
84                isSeparator(n, i));
85    }
86
87    private final boolean isSeparator(String n, int i) {
88        return (isA(n, i, syntaxSeparator) ||
89                isA(n, i, syntaxSeparator2));
90    }
91
92    private final int skipSeparator(String name, int i) {
93        if (isA(name, i, syntaxSeparator)) {
94            i += syntaxSeparator.length();
95        } else if (isA(name, i, syntaxSeparator2)) {
96            i += syntaxSeparator2.length();
97        }
98        return (i);
99    }
100
101    private final int extractComp(String name, int i, int len, Vector<String> comps)
102    throws InvalidNameException {
103        String beginQuote;
104        String endQuote;
105        boolean start = true;
106        boolean one = false;
107        StringBuilder answer = new StringBuilder(len);
108
109        while (i < len) {
110            // handle quoted strings
111            if (start && ((one = isA(name, i, syntaxBeginQuote1)) ||
112                          isA(name, i, syntaxBeginQuote2))) {
113
114                // record choice of quote chars being used
115                beginQuote = one ? syntaxBeginQuote1 : syntaxBeginQuote2;
116                endQuote = one ? syntaxEndQuote1 : syntaxEndQuote2;
117                if (escapingStyle == STYLE_NONE) {
118                    escapingStyle = one ? STYLE_QUOTE1 : STYLE_QUOTE2;
119                }
120
121                // consume string until matching quote
122                for (i += beginQuote.length();
123                     ((i < len) && !name.startsWith(endQuote, i));
124                     i++) {
125                    // skip escape character if it is escaping ending quote
126                    // otherwise leave as is.
127                    if (isA(name, i, syntaxEscape) &&
128                        isA(name, i + syntaxEscape.length(), endQuote)) {
129                        i += syntaxEscape.length();
130                    }
131                    answer.append(name.charAt(i));  // copy char
132                }
133
134                // no ending quote found
135                if (i >= len)
136                    throw
137                        new InvalidNameException(name + ": no close quote");
138//                      new Exception("no close quote");
139
140                i += endQuote.length();
141
142                // verify that end-quote occurs at separator or end of string
143                if (i == len || isSeparator(name, i)) {
144                    break;
145                }
146//              throw (new Exception(
147                throw (new InvalidNameException(name +
148                    ": close quote appears before end of component"));
149
150            } else if (isSeparator(name, i)) {
151                break;
152
153            } else if (isA(name, i, syntaxEscape)) {
154                if (isMeta(name, i + syntaxEscape.length())) {
155                    // if escape precedes meta, consume escape and let
156                    // meta through
157                    i += syntaxEscape.length();
158                    if (escapingStyle == STYLE_NONE) {
159                        escapingStyle = STYLE_ESCAPE;
160                    }
161                } else if (i + syntaxEscape.length() >= len) {
162                    throw (new InvalidNameException(name +
163                        ": unescaped " + syntaxEscape + " at end of component"));
164                }
165            } else if (isA(name, i, syntaxTypevalSeparator) &&
166        ((one = isA(name, i+syntaxTypevalSeparator.length(), syntaxBeginQuote1)) ||
167            isA(name, i+syntaxTypevalSeparator.length(), syntaxBeginQuote2))) {
168                // Handle quote occurring after typeval separator
169                beginQuote = one ? syntaxBeginQuote1 : syntaxBeginQuote2;
170                endQuote = one ? syntaxEndQuote1 : syntaxEndQuote2;
171
172                i += syntaxTypevalSeparator.length();
173                answer.append(syntaxTypevalSeparator).append(beginQuote); // add back
174
175                // consume string until matching quote
176                for (i += beginQuote.length();
177                     ((i < len) && !name.startsWith(endQuote, i));
178                     i++) {
179                    // skip escape character if it is escaping ending quote
180                    // otherwise leave as is.
181                    if (isA(name, i, syntaxEscape) &&
182                        isA(name, i + syntaxEscape.length(), endQuote)) {
183                        i += syntaxEscape.length();
184                    }
185                    answer.append(name.charAt(i));  // copy char
186                }
187
188                // no ending quote found
189                if (i >= len)
190                    throw
191                        new InvalidNameException(name + ": typeval no close quote");
192
193                i += endQuote.length();
194                answer.append(endQuote); // add back
195
196                // verify that end-quote occurs at separator or end of string
197                if (i == len || isSeparator(name, i)) {
198                    break;
199                }
200                throw (new InvalidNameException(name.substring(i) +
201                    ": typeval close quote appears before end of component"));
202            }
203
204            answer.append(name.charAt(i++));
205            start = false;
206        }
207
208        if (syntaxDirection == RIGHT_TO_LEFT)
209            comps.insertElementAt(answer.toString(), 0);
210        else
211            comps.addElement(answer.toString());
212        return i;
213    }
214
215    private static boolean getBoolean(Properties p, String name) {
216        return toBoolean(p.getProperty(name));
217    }
218
219    private static boolean toBoolean(String name) {
220        return ((name != null) &&
221            name.toLowerCase(Locale.ENGLISH).equals("true"));
222    }
223
224    private final void recordNamingConvention(Properties p) {
225        String syntaxDirectionStr =
226            p.getProperty("jndi.syntax.direction", "flat");
227        if (syntaxDirectionStr.equals("left_to_right")) {
228            syntaxDirection = LEFT_TO_RIGHT;
229        } else if (syntaxDirectionStr.equals("right_to_left")) {
230            syntaxDirection = RIGHT_TO_LEFT;
231        } else if (syntaxDirectionStr.equals("flat")) {
232            syntaxDirection = FLAT;
233        } else {
234            throw new IllegalArgumentException(syntaxDirectionStr +
235                " is not a valid value for the jndi.syntax.direction property");
236        }
237
238        if (syntaxDirection != FLAT) {
239            syntaxSeparator = p.getProperty("jndi.syntax.separator");
240            syntaxSeparator2 = p.getProperty("jndi.syntax.separator2");
241            if (syntaxSeparator == null) {
242                throw new IllegalArgumentException(
243                    "jndi.syntax.separator property required for non-flat syntax");
244            }
245        } else {
246            syntaxSeparator = null;
247        }
248        syntaxEscape = p.getProperty("jndi.syntax.escape");
249
250        syntaxCaseInsensitive = getBoolean(p, "jndi.syntax.ignorecase");
251        syntaxTrimBlanks = getBoolean(p, "jndi.syntax.trimblanks");
252
253        syntaxBeginQuote1 = p.getProperty("jndi.syntax.beginquote");
254        syntaxEndQuote1 = p.getProperty("jndi.syntax.endquote");
255        if (syntaxEndQuote1 == null && syntaxBeginQuote1 != null)
256            syntaxEndQuote1 = syntaxBeginQuote1;
257        else if (syntaxBeginQuote1 == null && syntaxEndQuote1 != null)
258            syntaxBeginQuote1 = syntaxEndQuote1;
259        syntaxBeginQuote2 = p.getProperty("jndi.syntax.beginquote2");
260        syntaxEndQuote2 = p.getProperty("jndi.syntax.endquote2");
261        if (syntaxEndQuote2 == null && syntaxBeginQuote2 != null)
262            syntaxEndQuote2 = syntaxBeginQuote2;
263        else if (syntaxBeginQuote2 == null && syntaxEndQuote2 != null)
264            syntaxBeginQuote2 = syntaxEndQuote2;
265
266        syntaxAvaSeparator = p.getProperty("jndi.syntax.separator.ava");
267        syntaxTypevalSeparator =
268            p.getProperty("jndi.syntax.separator.typeval");
269    }
270
271    NameImpl(Properties syntax) {
272        if (syntax != null) {
273            recordNamingConvention(syntax);
274        }
275        components = new Vector<>();
276    }
277
278    NameImpl(Properties syntax, String n) throws InvalidNameException {
279        this(syntax);
280
281        boolean rToL = (syntaxDirection == RIGHT_TO_LEFT);
282        boolean compsAllEmpty = true;
283        int len = n.length();
284
285        for (int i = 0; i < len; ) {
286            i = extractComp(n, i, len, components);
287
288            String comp = rToL
289                ? components.firstElement()
290                : components.lastElement();
291            if (comp.length() >= 1) {
292                compsAllEmpty = false;
293            }
294
295            if (i < len) {
296                i = skipSeparator(n, i);
297                if ((i == len) && !compsAllEmpty) {
298                    // Trailing separator found.  Add an empty component.
299                    if (rToL) {
300                        components.insertElementAt("", 0);
301                    } else {
302                        components.addElement("");
303                    }
304                }
305            }
306        }
307    }
308
309    NameImpl(Properties syntax, Enumeration<String> comps) {
310        this(syntax);
311
312        // %% comps could shrink in the middle.
313        while (comps.hasMoreElements())
314            components.addElement(comps.nextElement());
315    }
316/*
317    // Determines whether this component needs any escaping.
318    private final boolean escapingNeeded(String comp) {
319        int len = comp.length();
320        for (int i = 0; i < len; i++) {
321            if (i == 0) {
322                if (isA(comp, 0, syntaxBeginQuote1) ||
323                    isA(comp, 0, syntaxBeginQuote2)) {
324                    return (true);
325                }
326            }
327            if (isSeparator(comp, i)) {
328                return (true);
329            }
330            if (isA(comp, i, syntaxEscape)) {
331                i += syntaxEscape.length();
332                if (i >= len || isMeta(comp, i)) {
333                    return (true);
334                }
335            }
336        }
337        return (false);
338    }
339*/
340    private final String stringifyComp(String comp) {
341        int len = comp.length();
342        boolean escapeSeparator = false, escapeSeparator2 = false;
343        String beginQuote = null, endQuote = null;
344        StringBuffer strbuf = new StringBuffer(len);
345
346        // determine whether there are any separators; if so escape
347        // or quote them
348        if (syntaxSeparator != null &&
349            comp.indexOf(syntaxSeparator) >= 0) {
350            if (syntaxBeginQuote1 != null) {
351                beginQuote = syntaxBeginQuote1;
352                endQuote = syntaxEndQuote1;
353            } else if (syntaxBeginQuote2 != null) {
354                beginQuote = syntaxBeginQuote2;
355                endQuote = syntaxEndQuote2;
356            } else if (syntaxEscape != null)
357                escapeSeparator = true;
358        }
359        if (syntaxSeparator2 != null &&
360            comp.indexOf(syntaxSeparator2) >= 0) {
361            if (syntaxBeginQuote1 != null) {
362                if (beginQuote == null) {
363                    beginQuote = syntaxBeginQuote1;
364                    endQuote = syntaxEndQuote1;
365                }
366            } else if (syntaxBeginQuote2 != null) {
367                if (beginQuote == null) {
368                    beginQuote = syntaxBeginQuote2;
369                    endQuote = syntaxEndQuote2;
370                }
371            } else if (syntaxEscape != null)
372                escapeSeparator2 = true;
373        }
374
375        // if quoting component,
376        if (beginQuote != null) {
377
378            // start string off with opening quote
379            strbuf = strbuf.append(beginQuote);
380
381            // component is being quoted, so we only need to worry about
382            // escaping end quotes that occur in component
383            for (int i = 0; i < len; ) {
384                if (comp.startsWith(endQuote, i)) {
385                    // end-quotes must be escaped when inside a quoted string
386                    strbuf.append(syntaxEscape).append(endQuote);
387                    i += endQuote.length();
388                } else {
389                    // no special treatment required
390                    strbuf.append(comp.charAt(i++));
391                }
392            }
393
394            // end with closing quote
395            strbuf.append(endQuote);
396
397        } else {
398
399            // When component is not quoted, add escape for:
400            // 1. leading quote
401            // 2. an escape preceding any meta char
402            // 3. an escape at the end of a component
403            // 4. separator
404
405            // go through characters in component and escape where necessary
406            boolean start = true;
407            for (int i = 0; i < len; ) {
408                // leading quote must be escaped
409                if (start && isA(comp, i, syntaxBeginQuote1)) {
410                    strbuf.append(syntaxEscape).append(syntaxBeginQuote1);
411                    i += syntaxBeginQuote1.length();
412                } else if (start && isA(comp, i, syntaxBeginQuote2)) {
413                    strbuf.append(syntaxEscape).append(syntaxBeginQuote2);
414                    i += syntaxBeginQuote2.length();
415                } else
416
417                // Escape an escape preceding meta characters, or at end.
418                // Other escapes pass through.
419                if (isA(comp, i, syntaxEscape)) {
420                    if (i + syntaxEscape.length() >= len) {
421                        // escape an ending escape
422                        strbuf.append(syntaxEscape);
423                    } else if (isMeta(comp, i + syntaxEscape.length())) {
424                        // escape meta strings
425                        strbuf.append(syntaxEscape);
426                    }
427                    strbuf.append(syntaxEscape);
428                    i += syntaxEscape.length();
429                } else
430
431                // escape unescaped separator
432                if (escapeSeparator && comp.startsWith(syntaxSeparator, i)) {
433                    // escape separator
434                    strbuf.append(syntaxEscape).append(syntaxSeparator);
435                    i += syntaxSeparator.length();
436                } else if (escapeSeparator2 &&
437                           comp.startsWith(syntaxSeparator2, i)) {
438                    // escape separator2
439                    strbuf.append(syntaxEscape).append(syntaxSeparator2);
440                    i += syntaxSeparator2.length();
441                } else {
442                    // no special treatment required
443                    strbuf.append(comp.charAt(i++));
444                }
445                start = false;
446            }
447        }
448        return (strbuf.toString());
449    }
450
451    public String toString() {
452        StringBuffer answer = new StringBuffer();
453        String comp;
454        boolean compsAllEmpty = true;
455        int size = components.size();
456
457        for (int i = 0; i < size; i++) {
458            if (syntaxDirection == RIGHT_TO_LEFT) {
459                comp =
460                    stringifyComp(components.elementAt(size - 1 - i));
461            } else {
462                comp = stringifyComp(components.elementAt(i));
463            }
464            if ((i != 0) && (syntaxSeparator != null))
465                answer.append(syntaxSeparator);
466            if (comp.length() >= 1)
467                compsAllEmpty = false;
468            answer = answer.append(comp);
469        }
470        if (compsAllEmpty && (size >= 1) && (syntaxSeparator != null))
471            answer = answer.append(syntaxSeparator);
472        return (answer.toString());
473    }
474
475    public boolean equals(Object obj) {
476        if ((obj != null) && (obj instanceof NameImpl)) {
477            NameImpl target = (NameImpl)obj;
478            if (target.size() ==  this.size()) {
479                Enumeration<String> mycomps = getAll();
480                Enumeration<String> comps = target.getAll();
481                while (mycomps.hasMoreElements()) {
482                    // %% comps could shrink in the middle.
483                    String my = mycomps.nextElement();
484                    String his = comps.nextElement();
485                    if (syntaxTrimBlanks) {
486                        my = my.trim();
487                        his = his.trim();
488                    }
489                    if (syntaxCaseInsensitive) {
490                        if (!(my.equalsIgnoreCase(his)))
491                            return false;
492                    } else {
493                        if (!(my.equals(his)))
494                            return false;
495                    }
496                }
497                return true;
498            }
499        }
500        return false;
501    }
502
503    /**
504      * Compares obj to this NameImpl to determine ordering.
505      * Takes into account syntactic properties such as
506      * elimination of blanks, case-ignore, etc, if relevant.
507      *
508      * Note: using syntax of this NameImpl and ignoring
509      * that of comparison target.
510      */
511    public int compareTo(NameImpl obj) {
512        if (this == obj) {
513            return 0;
514        }
515
516        int len1 = size();
517        int len2 = obj.size();
518        int n = Math.min(len1, len2);
519
520        int index1 = 0, index2 = 0;
521
522        while (n-- != 0) {
523            String comp1 = get(index1++);
524            String comp2 = obj.get(index2++);
525
526            // normalize according to syntax
527            if (syntaxTrimBlanks) {
528                comp1 = comp1.trim();
529                comp2 = comp2.trim();
530            }
531
532            int local;
533            if (syntaxCaseInsensitive) {
534                local = comp1.compareToIgnoreCase(comp2);
535            } else {
536                local = comp1.compareTo(comp2);
537            }
538
539            if (local != 0) {
540                return local;
541            }
542        }
543
544        return len1 - len2;
545    }
546
547    public int size() {
548        return (components.size());
549    }
550
551    public Enumeration<String> getAll() {
552        return components.elements();
553    }
554
555    public String get(int posn) {
556        return components.elementAt(posn);
557    }
558
559    public Enumeration<String> getPrefix(int posn) {
560        if (posn < 0 || posn > size()) {
561            throw new ArrayIndexOutOfBoundsException(posn);
562        }
563        return new NameImplEnumerator(components, 0, posn);
564    }
565
566    public Enumeration<String> getSuffix(int posn) {
567        int cnt = size();
568        if (posn < 0 || posn > cnt) {
569            throw new ArrayIndexOutOfBoundsException(posn);
570        }
571        return new NameImplEnumerator(components, posn, cnt);
572    }
573
574    public boolean isEmpty() {
575        return (components.isEmpty());
576    }
577
578    public boolean startsWith(int posn, Enumeration<String> prefix) {
579        if (posn < 0 || posn > size()) {
580            return false;
581        }
582        try {
583            Enumeration<String> mycomps = getPrefix(posn);
584            while (mycomps.hasMoreElements()) {
585                String my = mycomps.nextElement();
586                String his = prefix.nextElement();
587                if (syntaxTrimBlanks) {
588                    my = my.trim();
589                    his = his.trim();
590                }
591                if (syntaxCaseInsensitive) {
592                    if (!(my.equalsIgnoreCase(his)))
593                        return false;
594                } else {
595                    if (!(my.equals(his)))
596                        return false;
597                }
598            }
599        } catch (NoSuchElementException e) {
600            return false;
601        }
602        return true;
603    }
604
605    public boolean endsWith(int posn, Enumeration<String> suffix) {
606        // posn is number of elements in suffix
607        // startIndex is the starting position in this name
608        // at which to start the comparison. It is calculated by
609        // subtracting 'posn' from size()
610        int startIndex = size() - posn;
611        if (startIndex < 0 || startIndex > size()) {
612            return false;
613        }
614        try {
615            Enumeration<String> mycomps = getSuffix(startIndex);
616            while (mycomps.hasMoreElements()) {
617                String my = mycomps.nextElement();
618                String his = suffix.nextElement();
619                if (syntaxTrimBlanks) {
620                    my = my.trim();
621                    his = his.trim();
622                }
623                if (syntaxCaseInsensitive) {
624                    if (!(my.equalsIgnoreCase(his)))
625                        return false;
626                } else {
627                    if (!(my.equals(his)))
628                        return false;
629                }
630            }
631        } catch (NoSuchElementException e) {
632            return false;
633        }
634        return true;
635    }
636
637    public boolean addAll(Enumeration<String> comps) throws InvalidNameException {
638        boolean added = false;
639        while (comps.hasMoreElements()) {
640            try {
641                String comp = comps.nextElement();
642                if (size() > 0 && syntaxDirection == FLAT) {
643                    throw new InvalidNameException(
644                        "A flat name can only have a single component");
645                }
646                components.addElement(comp);
647                added = true;
648            } catch (NoSuchElementException e) {
649                break;  // "comps" has shrunk.
650            }
651        }
652        return added;
653    }
654
655    public boolean addAll(int posn, Enumeration<String> comps)
656    throws InvalidNameException {
657        boolean added = false;
658        for (int i = posn; comps.hasMoreElements(); i++) {
659            try {
660                String comp = comps.nextElement();
661                if (size() > 0 && syntaxDirection == FLAT) {
662                    throw new InvalidNameException(
663                        "A flat name can only have a single component");
664                }
665                components.insertElementAt(comp, i);
666                added = true;
667            } catch (NoSuchElementException e) {
668                break;  // "comps" has shrunk.
669            }
670        }
671        return added;
672    }
673
674    public void add(String comp) throws InvalidNameException {
675        if (size() > 0 && syntaxDirection == FLAT) {
676            throw new InvalidNameException(
677                "A flat name can only have a single component");
678        }
679        components.addElement(comp);
680    }
681
682    public void add(int posn, String comp) throws InvalidNameException {
683        if (size() > 0 && syntaxDirection == FLAT) {
684            throw new InvalidNameException(
685                "A flat name can only zero or one component");
686        }
687        components.insertElementAt(comp, posn);
688    }
689
690    public Object remove(int posn) {
691        Object r = components.elementAt(posn);
692        components.removeElementAt(posn);
693        return r;
694    }
695
696    public int hashCode() {
697        int hash = 0;
698        for (Enumeration<String> e = getAll(); e.hasMoreElements();) {
699            String comp = e.nextElement();
700            if (syntaxTrimBlanks) {
701                comp = comp.trim();
702            }
703            if (syntaxCaseInsensitive) {
704                comp = comp.toLowerCase(Locale.ENGLISH);
705            }
706
707            hash += comp.hashCode();
708        }
709        return hash;
710    }
711}
712
713final
714class NameImplEnumerator implements Enumeration<String> {
715    Vector<String> vector;
716    int count;
717    int limit;
718
719    NameImplEnumerator(Vector<String> v, int start, int lim) {
720        vector = v;
721        count = start;
722        limit = lim;
723    }
724
725    public boolean hasMoreElements() {
726        return count < limit;
727    }
728
729    public String nextElement() {
730        if (count < limit) {
731            return vector.elementAt(count++);
732        }
733        throw new NoSuchElementException("NameImplEnumerator");
734    }
735}
736