1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.directory.api.ldap.model.url;
21
22
23 import java.io.ByteArrayOutputStream;
24 import java.text.ParseException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32
33 import org.apache.directory.api.i18n.I18n;
34 import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
35 import org.apache.directory.api.ldap.model.exception.LdapURLEncodingException;
36 import org.apache.directory.api.ldap.model.exception.LdapUriException;
37 import org.apache.directory.api.ldap.model.exception.UrlDecoderException;
38 import org.apache.directory.api.ldap.model.filter.FilterParser;
39 import org.apache.directory.api.ldap.model.message.SearchScope;
40 import org.apache.directory.api.ldap.model.name.Dn;
41 import org.apache.directory.api.util.Chars;
42 import org.apache.directory.api.util.StringConstants;
43 import org.apache.directory.api.util.Strings;
44 import org.apache.directory.api.util.Unicode;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public class LdapUrl
99 {
100
101 public static final String LDAPS_SCHEME = "ldaps://";
102
103
104 public static final String LDAP_SCHEME = "ldap://";
105
106
107 public static final LdapUrl EMPTY_URL = new LdapUrl();
108
109
110 private String scheme;
111
112
113 private String host;
114
115
116 private int port;
117
118
119 private Dn dn;
120
121
122 private List<String> attributes;
123
124
125 private SearchScope scope;
126
127
128 private String filter;
129
130
131 private List<Extension> extensionList;
132
133
134 private String string;
135
136
137 private byte[] bytes;
138
139
140 private boolean forceScopeRendering;
141
142
143 private HostTypeEnum hostType = HostTypeEnum.REGULAR_NAME;
144
145
146 private static final Pattern ATTRIBUTE = Pattern
147 .compile( "(?:(?:\\d|[1-9]\\d*)(?:\\.(?:\\d|[1-9]\\d*))+)|(?:[a-zA-Z][a-zA-Z0-9-]*)" );
148
149
150
151
152
153 public LdapUrl()
154 {
155 scheme = LDAP_SCHEME;
156 host = null;
157 port = -1;
158 dn = null;
159 attributes = new ArrayList<>();
160 scope = SearchScope.OBJECT;
161 filter = null;
162 extensionList = new ArrayList<>( 2 );
163 }
164
165
166
167
168
169
170
171
172 public LdapUrl( String string ) throws LdapURLEncodingException
173 {
174 if ( string == null )
175 {
176 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04408 ) );
177 }
178
179 bytes = Strings.getBytesUtf8( string );
180 this.string = string;
181 parse( string.toCharArray() );
182 }
183
184
185
186
187
188
189
190
191 private void parse( char[] chars ) throws LdapURLEncodingException
192 {
193 scheme = LDAP_SCHEME;
194 host = null;
195 port = -1;
196 dn = null;
197 attributes = new ArrayList<>();
198 scope = SearchScope.OBJECT;
199 filter = null;
200 extensionList = new ArrayList<>( 2 );
201
202 if ( ( chars == null ) || ( chars.length == 0 ) )
203 {
204 host = "";
205 return;
206 }
207
208
209
210
211
212 int pos = 0;
213
214
215 pos = Strings.areEquals( chars, 0, LDAP_SCHEME );
216
217 if ( pos == StringConstants.NOT_EQUAL )
218 {
219 pos = Strings.areEquals( chars, 0, LDAPS_SCHEME );
220 if ( pos == StringConstants.NOT_EQUAL )
221 {
222 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04398 ) );
223 }
224 }
225 scheme = new String( chars, 0, pos );
226
227
228 pos = parseHostPort( chars, pos );
229 if ( pos == -1 )
230 {
231 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04399 ) );
232 }
233
234 if ( pos == chars.length )
235 {
236 return;
237 }
238
239
240 if ( !Chars.isCharASCII( chars, pos, '/' ) )
241 {
242 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04400, pos, chars[pos] ) );
243 }
244
245 pos++;
246
247 if ( pos == chars.length )
248 {
249 return;
250 }
251
252
253 pos = parseDN( chars, pos );
254 if ( pos == -1 )
255 {
256 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04401 ) );
257 }
258
259 if ( pos == chars.length )
260 {
261 return;
262 }
263
264
265 if ( !Chars.isCharASCII( chars, pos, '?' ) )
266 {
267 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
268 }
269
270 pos++;
271
272 pos = parseAttributes( chars, pos );
273 if ( pos == -1 )
274 {
275 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04403 ) );
276 }
277
278 if ( pos == chars.length )
279 {
280 return;
281 }
282
283
284 if ( !Chars.isCharASCII( chars, pos, '?' ) )
285 {
286 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
287 }
288
289 pos++;
290
291 pos = parseScope( chars, pos );
292 if ( pos == -1 )
293 {
294 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04404 ) );
295 }
296
297 if ( pos == chars.length )
298 {
299 return;
300 }
301
302
303 if ( !Chars.isCharASCII( chars, pos, '?' ) )
304 {
305 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
306 }
307
308 pos++;
309
310 if ( pos == chars.length )
311 {
312 return;
313 }
314
315 pos = parseFilter( chars, pos );
316 if ( pos == -1 )
317 {
318 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04405 ) );
319 }
320
321 if ( pos == chars.length )
322 {
323 return;
324 }
325
326
327 if ( !Chars.isCharASCII( chars, pos, '?' ) )
328 {
329 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
330 }
331
332 pos++;
333
334 pos = parseExtensions( chars, pos );
335 if ( pos == -1 )
336 {
337 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04406 ) );
338 }
339
340 if ( pos == chars.length )
341 {
342 return;
343 }
344 else
345 {
346 throw new LdapURLEncodingException( I18n.err( I18n.ERR_04407 ) );
347 }
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372 private int parseHost( char[] chars, int pos )
373 {
374 int start = pos;
375
376
377
378
379
380 switch ( chars[pos] )
381 {
382 case '[':
383
384 return parseIpLiteral( chars, pos + 1 );
385
386 case '0':
387 case '1':
388 case '2':
389 case '3':
390 case '4':
391 case '5':
392 case '6':
393 case '7':
394 case '8':
395 case '9':
396
397
398 int currentPos = parseIPV4( chars, pos );
399
400 if ( currentPos != -1 )
401 {
402 host = new String( chars, start, currentPos - start );
403
404 return currentPos;
405 }
406
407
408 case 'a' : case 'b' : case 'c' : case 'd' : case 'e' :
409 case 'A' : case 'B' : case 'C' : case 'D' : case 'E' :
410 case 'f' : case 'g' : case 'h' : case 'i' : case 'j' :
411 case 'F' : case 'G' : case 'H' : case 'I' : case 'J' :
412 case 'k' : case 'l' : case 'm' : case 'n' : case 'o' :
413 case 'K' : case 'L' : case 'M' : case 'N' : case 'O' :
414 case 'p' : case 'q' : case 'r' : case 's' : case 't' :
415 case 'P' : case 'Q' : case 'R' : case 'S' : case 'T' :
416 case 'u' : case 'v' : case 'w' : case 'x' : case 'y' :
417 case 'U' : case 'V' : case 'W' : case 'X' : case 'Y' :
418 case 'z' : case 'Z' : case '-' : case '.' : case '_' :
419 case '~' : case '%' : case '!' : case '$' : case '&' :
420 case '\'' : case '(' : case ')' : case '*' : case '+' :
421 case ',' : case ';' : case '=' :
422
423 return parseRegName( chars, pos );
424
425 default:
426 break;
427 }
428
429 host = new String( chars, start, pos - start );
430
431 return pos;
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 private int parseIpLiteral( char[] chars, int pos )
453 {
454 int start = pos;
455
456 if ( Chars.isCharASCII( chars, pos, 'v' ) )
457 {
458
459 pos++;
460 hostType = HostTypeEnum.IPV_FUTURE;
461
462 pos = parseIPvFuture( chars, pos );
463
464 if ( pos != -1 )
465 {
466
467 host = new String( chars, start, pos - start - 1 );
468 }
469
470 return pos;
471 }
472 else
473 {
474
475 hostType = HostTypeEnum.IPV6;
476
477 return parseIPV6( chars, pos );
478 }
479 }
480
481
482
483
484
485
486
487 public boolean isValidInet4Address( String inet4Address )
488 {
489 return parseIPV4( inet4Address.toCharArray(), 0 ) != -1;
490 }
491
492
493
494
495
496
497
498
499
500
501
502 public boolean isValidInet6Address( String inet6Address )
503 {
504 boolean containsCompressedZeroes = inet6Address.contains( "::" );
505
506 if ( containsCompressedZeroes && ( inet6Address.indexOf( "::" ) != inet6Address.lastIndexOf( "::" ) ) )
507 {
508 return false;
509 }
510
511 if ( ( inet6Address.startsWith( ":" ) && !inet6Address.startsWith( "::" ) )
512 || ( inet6Address.endsWith( ":" ) && !inet6Address.endsWith( "::" ) ) )
513 {
514 return false;
515 }
516
517 String[] octets = inet6Address.split( ":" );
518
519 if ( containsCompressedZeroes )
520 {
521 List<String> octetList = new ArrayList<>( Arrays.asList( octets ) );
522
523 if ( inet6Address.endsWith( "::" ) )
524 {
525
526 octetList.add( "" );
527 }
528 else if ( inet6Address.startsWith( "::" ) && !octetList.isEmpty() )
529 {
530 octetList.remove( 0 );
531 }
532
533 octets = octetList.toArray( new String[octetList.size()] );
534 }
535
536 if ( octets.length > 8 )
537 {
538 return false;
539 }
540
541 int validOctets = 0;
542 int emptyOctets = 0;
543
544 for ( int index = 0; index < octets.length; index++ )
545 {
546 String octet = octets[index];
547
548 if ( octet.length() == 0 )
549 {
550 emptyOctets++;
551
552 if ( emptyOctets > 1 )
553 {
554 return false;
555 }
556 }
557 else
558 {
559 emptyOctets = 0;
560
561 if ( octet.contains( "." ) )
562 {
563 if ( !inet6Address.endsWith( octet ) )
564 {
565 return false;
566 }
567
568 if ( index > octets.length - 1 || index > 6 )
569 {
570
571 return false;
572 }
573
574 if ( !isValidInet4Address( octet ) )
575 {
576 return false;
577 }
578
579 validOctets += 2;
580
581 continue;
582 }
583
584 if ( octet.length() > 4 )
585 {
586 return false;
587 }
588
589 int octetInt = 0;
590
591 try
592 {
593 octetInt = Integer.valueOf( octet, 16 ).intValue();
594 }
595 catch ( NumberFormatException e )
596 {
597 return false;
598 }
599
600 if ( octetInt < 0 || octetInt > 0xffff )
601 {
602 return false;
603 }
604 }
605
606 validOctets++;
607 }
608
609 if ( validOctets < 8 && !containsCompressedZeroes )
610 {
611 return false;
612 }
613
614 return true;
615 }
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634 private int parseIPV6( char[] chars, int pos )
635 {
636
637 int start = pos;
638
639 while ( !Chars.isCharASCII( chars, pos, ']' ) )
640 {
641 pos++;
642 }
643
644 if ( Chars.isCharASCII( chars, pos, ']' ) )
645 {
646 String hostString = new String( chars, start, pos - start );
647
648 if ( isValidInet6Address( hostString ) )
649 {
650 host = hostString;
651
652 return pos + 1;
653 }
654 else
655 {
656 return -1;
657 }
658 }
659
660 return -1;
661 }
662
663
664
665
666
667
668
669
670
671 private int parseIPvFuture( char[] chars, int pos )
672 {
673
674 boolean hexFound = false;
675
676 while ( Chars.isHex( chars, pos ) )
677 {
678 hexFound = true;
679 pos++;
680 }
681
682 if ( !hexFound )
683 {
684 return -1;
685 }
686
687
688 if ( !Chars.isCharASCII( chars, pos, '.' ) )
689 {
690 return -1;
691 }
692
693
694 boolean valueFound = false;
695
696 while ( !Chars.isCharASCII( chars, pos, ']' ) )
697 {
698 switch ( chars[pos] )
699 {
700
701
702 case 'a' : case 'b' : case 'c' : case 'd' : case 'e' :
703 case 'A' : case 'B' : case 'C' : case 'D' : case 'E' :
704 case 'f' : case 'g' : case 'h' : case 'i' : case 'j' :
705 case 'F' : case 'G' : case 'H' : case 'I' : case 'J' :
706 case 'k' : case 'l' : case 'm' : case 'n' : case 'o' :
707 case 'K' : case 'L' : case 'M' : case 'N' : case 'O' :
708 case 'p' : case 'q' : case 'r' : case 's' : case 't' :
709 case 'P' : case 'Q' : case 'R' : case 'S' : case 'T' :
710 case 'u' : case 'v' : case 'w' : case 'x' : case 'y' :
711 case 'U' : case 'V' : case 'W' : case 'X' : case 'Y' :
712 case 'z' : case 'Z' :
713
714
715 case '0' : case '1' : case '2' : case '3' : case '4' :
716 case '5' : case '6' : case '7' : case '8' : case '9' :
717
718
719 case '-' : case '.' : case '_' : case '~' :
720
721
722 case '!' : case '$' : case '&' : case '\'' :
723 case '(' : case ')' : case '*' : case '+' : case ',' :
724 case ';' : case '=' :
725
726
727 case ':':
728 pos++;
729 valueFound = true;
730 break;
731
732 default:
733
734 return -1;
735 }
736 }
737
738 if ( !valueFound )
739 {
740 return -1;
741 }
742
743 return pos;
744 }
745
746
747
748
749
750
751
752
753
754
755
756
757 private int parseRegName( char[] chars, int pos )
758 {
759 int start = pos;
760
761 while ( !Chars.isCharASCII( chars, pos, ':' ) && !Chars.isCharASCII( chars, pos, '/' ) && ( pos < chars.length ) )
762 {
763 switch ( chars[pos] )
764 {
765
766
767 case 'a' : case 'b' : case 'c' : case 'd' : case 'e' :
768 case 'A' : case 'B' : case 'C' : case 'D' : case 'E' :
769 case 'f' : case 'g' : case 'h' : case 'i' : case 'j' :
770 case 'F' : case 'G' : case 'H' : case 'I' : case 'J' :
771 case 'k' : case 'l' : case 'm' : case 'n' : case 'o' :
772 case 'K' : case 'L' : case 'M' : case 'N' : case 'O' :
773 case 'p' : case 'q' : case 'r' : case 's' : case 't' :
774 case 'P' : case 'Q' : case 'R' : case 'S' : case 'T' :
775 case 'u' : case 'v' : case 'w' : case 'x' : case 'y' :
776 case 'U' : case 'V' : case 'W' : case 'X' : case 'Y' :
777 case 'z' : case 'Z' :
778
779
780 case '0' : case '1' : case '2' : case '3' : case '4' :
781 case '5' : case '6' : case '7' : case '8' : case '9' :
782
783
784 case '-' : case '.' : case '_' : case '~' :
785
786
787 case '!' : case '$' : case '&' : case '\'' :
788 case '(' : case ')' : case '*' : case '+' : case ',' :
789 case ';' : case '=' :
790 pos++;
791 break;
792
793
794 case '%':
795 if ( Chars.isHex( chars, pos + 1 ) && Chars.isHex( chars, pos + 2 ) )
796 {
797 pos += 3;
798 }
799 else
800 {
801 return -1;
802 }
803
804 default:
805
806 return -1;
807 }
808 }
809
810 host = new String( chars, start, pos - start );
811 hostType = HostTypeEnum.REGULAR_NAME;
812
813 return pos;
814 }
815
816
817
818
819
820
821
822
823
824
825
826
827
828 private int parseIPV4( char[] chars, int pos )
829 {
830 int[] ipElem = new int[4];
831 int ipPos = pos;
832 int start = pos;
833
834 for ( int i = 0; i < 3; i++ )
835 {
836 ipPos = parseDecOctet( chars, ipPos, ipElem, i );
837
838 if ( ipPos == -1 )
839 {
840
841 return -1;
842 }
843
844 if ( chars[ipPos] != '.' )
845 {
846
847 return -1;
848 }
849 else
850 {
851 ipPos++;
852 }
853 }
854
855 ipPos = parseDecOctet( chars, ipPos, ipElem, 3 );
856
857 if ( ipPos == -1 )
858 {
859
860 return -1;
861 }
862 else
863 {
864 pos = ipPos;
865 host = new String( chars, start, pos - start );
866 hostType = HostTypeEnum.IPV4;
867
868 return pos;
869 }
870 }
871
872
873
874
875
876
877
878
879 private int parseDecOctet( char[] chars, int pos, int[] ipElem, int octetNb )
880 {
881 int ipElemValue = 0;
882 boolean ipElemSeen = false;
883 boolean hasHeadingZeroes = false;
884
885 while ( Chars.isDigit( chars, pos ) )
886 {
887 ipElemSeen = true;
888
889 if ( chars[pos] == '0' )
890 {
891 if ( hasHeadingZeroes )
892 {
893
894 return -1;
895 }
896
897 if ( ipElemValue > 0 )
898 {
899 ipElemValue = ipElemValue * 10;
900 }
901 else
902 {
903 hasHeadingZeroes = true;
904 }
905 }
906 else
907 {
908 hasHeadingZeroes = false;
909 ipElemValue = ( ipElemValue * 10 ) + ( chars[pos] - '0' );
910 }
911
912 if ( ipElemValue > 255 )
913 {
914 return -1;
915 }
916
917 pos++;
918 }
919
920 if ( ipElemSeen )
921 {
922 ipElem[octetNb] = ipElemValue;
923
924 return pos;
925 }
926 else
927 {
928 return -1;
929 }
930 }
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946 private int parsePort( char[] chars, int pos )
947 {
948
949 if ( !Chars.isDigit( chars, pos ) )
950 {
951 return -1;
952 }
953
954 port = chars[pos] - '0';
955
956 pos++;
957
958 while ( Chars.isDigit( chars, pos ) )
959 {
960 port = ( port * 10 ) + ( chars[pos] - '0' );
961
962 if ( port > 65535 )
963 {
964 return -1;
965 }
966
967 pos++;
968 }
969
970 return pos;
971 }
972
973
974
975
976
977
978
979
980
981
982
983
984
985 private int parseHostPort( char[] chars, int pos )
986 {
987 int hostPos = pos;
988
989 pos = parseHost( chars, pos );
990 if ( pos == -1 )
991 {
992 return -1;
993 }
994
995
996 if ( Chars.isCharASCII( chars, pos, ':' ) )
997 {
998 if ( pos == hostPos )
999 {
1000
1001 return -1;
1002 }
1003
1004 pos++;
1005 }
1006 else
1007 {
1008 return pos;
1009 }
1010
1011
1012 pos = parsePort( chars, pos );
1013 if ( pos == -1 )
1014 {
1015 return -1;
1016 }
1017
1018 return pos;
1019 }
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029 private static byte[] getAsciiBytes( final String data ) throws UrlDecoderException
1030 {
1031 if ( data == null )
1032 {
1033 throw new IllegalArgumentException( I18n.err( I18n.ERR_04411 ) );
1034 }
1035
1036 return Strings.getBytesUtf8( data );
1037 }
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 private static byte[] decodeUrl( byte[] bytes ) throws UrlDecoderException
1050 {
1051 if ( bytes == null )
1052 {
1053 return Strings.EMPTY_BYTES;
1054 }
1055
1056 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1057
1058 for ( int i = 0; i < bytes.length; i++ )
1059 {
1060 int b = bytes[i];
1061
1062 if ( b == '%' )
1063 {
1064 try
1065 {
1066 int u = Character.digit( ( char ) bytes[++i], 16 );
1067 int l = Character.digit( ( char ) bytes[++i], 16 );
1068
1069 if ( ( u == -1 ) || ( l == -1 ) )
1070 {
1071 throw new UrlDecoderException( I18n.err( I18n.ERR_04414 ) );
1072 }
1073
1074 buffer.write( ( char ) ( ( u << 4 ) + l ) );
1075 }
1076 catch ( ArrayIndexOutOfBoundsException aioobe )
1077 {
1078 throw new UrlDecoderException( I18n.err( I18n.ERR_04414 ), aioobe );
1079 }
1080 }
1081 else
1082 {
1083 buffer.write( b );
1084 }
1085 }
1086
1087 return buffer.toByteArray();
1088 }
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099 private static String decode( String escaped ) throws LdapUriException
1100 {
1101 try
1102 {
1103 byte[] rawdata = decodeUrl( getAsciiBytes( escaped ) );
1104 return Strings.getString( rawdata, "UTF-8" );
1105 }
1106 catch ( UrlDecoderException e )
1107 {
1108 throw new LdapUriException( e.getMessage(), e );
1109 }
1110 }
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121 private int parseDN( char[] chars, int pos )
1122 {
1123
1124 int end = pos;
1125
1126 for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
1127 {
1128 end++;
1129 }
1130
1131 try
1132 {
1133 String dnStr = new String( chars, pos, end - pos );
1134 dn = new Dn( decode( dnStr ) );
1135 }
1136 catch ( LdapUriException | LdapInvalidDnException e )
1137 {
1138 return -1;
1139 }
1140
1141 return end;
1142 }
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159 private void validateAttribute( String attribute ) throws LdapURLEncodingException
1160 {
1161 Matcher matcher = ATTRIBUTE.matcher( attribute );
1162
1163 if ( !matcher.matches() )
1164 {
1165 throw new LdapURLEncodingException( "Attribute " + attribute + " is invalid" );
1166 }
1167 }
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177 private int parseAttributes( char[] chars, int pos )
1178 {
1179 int start = pos;
1180 int end = pos;
1181 Set<String> hAttributes = new HashSet<>();
1182 boolean hadComma = false;
1183
1184 try
1185 {
1186
1187 for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
1188 {
1189
1190 if ( Chars.isCharASCII( chars, i, ',' ) )
1191 {
1192 hadComma = true;
1193
1194 if ( ( end - start ) == 0 )
1195 {
1196
1197
1198 return -1;
1199 }
1200 else
1201 {
1202 String attribute = null;
1203
1204
1205 attribute = new String( chars, start, end - start ).trim();
1206
1207 if ( attribute.length() == 0 )
1208 {
1209 return -1;
1210 }
1211
1212
1213 try
1214 {
1215 validateAttribute( attribute );
1216 }
1217 catch ( LdapURLEncodingException luee )
1218 {
1219 return -1;
1220 }
1221
1222 String decodedAttr = decode( attribute );
1223
1224 if ( !hAttributes.contains( decodedAttr ) )
1225 {
1226 attributes.add( decodedAttr );
1227 hAttributes.add( decodedAttr );
1228 }
1229 }
1230
1231 start = i + 1;
1232 }
1233 else
1234 {
1235 hadComma = false;
1236 }
1237
1238 end++;
1239 }
1240
1241 if ( hadComma )
1242 {
1243
1244
1245
1246 return -1;
1247 }
1248 else
1249 {
1250
1251 if ( end == start )
1252 {
1253
1254
1255 return end;
1256 }
1257
1258
1259
1260 String attribute = null;
1261
1262 attribute = new String( chars, start, end - start ).trim();
1263
1264 if ( attribute.length() == 0 )
1265 {
1266 return -1;
1267 }
1268
1269 String decodedAttr = decode( attribute );
1270
1271 if ( !hAttributes.contains( decodedAttr ) )
1272 {
1273 attributes.add( decodedAttr );
1274 hAttributes.add( decodedAttr );
1275 }
1276 }
1277
1278 return end;
1279 }
1280 catch ( LdapUriException ue )
1281 {
1282 return -1;
1283 }
1284 }
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294 private int parseFilter( char[] chars, int pos )
1295 {
1296
1297 int end = pos;
1298
1299 for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
1300 {
1301 end++;
1302 }
1303
1304 if ( end == pos )
1305 {
1306
1307 return end;
1308 }
1309
1310 try
1311 {
1312 filter = decode( new String( chars, pos, end - pos ) );
1313 FilterParser.parse( null, filter );
1314 }
1315 catch ( LdapUriException | ParseException e )
1316 {
1317 return -1;
1318 }
1319
1320 return end;
1321 }
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331 private int parseScope( char[] chars, int pos )
1332 {
1333
1334 if ( Chars.isCharASCII( chars, pos, 'b' ) || Chars.isCharASCII( chars, pos, 'B' ) )
1335 {
1336 pos++;
1337
1338 if ( Chars.isCharASCII( chars, pos, 'a' ) || Chars.isCharASCII( chars, pos, 'A' ) )
1339 {
1340 pos++;
1341
1342 if ( Chars.isCharASCII( chars, pos, 's' ) || Chars.isCharASCII( chars, pos, 'S' ) )
1343 {
1344 pos++;
1345
1346 if ( Chars.isCharASCII( chars, pos, 'e' ) || Chars.isCharASCII( chars, pos, 'E' ) )
1347 {
1348 pos++;
1349 scope = SearchScope.OBJECT;
1350 return pos;
1351 }
1352 }
1353 }
1354 }
1355 else if ( Chars.isCharASCII( chars, pos, 'o' ) || Chars.isCharASCII( chars, pos, 'O' ) )
1356 {
1357 pos++;
1358
1359 if ( Chars.isCharASCII( chars, pos, 'n' ) || Chars.isCharASCII( chars, pos, 'N' ) )
1360 {
1361 pos++;
1362
1363 if ( Chars.isCharASCII( chars, pos, 'e' ) || Chars.isCharASCII( chars, pos, 'E' ) )
1364 {
1365 pos++;
1366
1367 scope = SearchScope.ONELEVEL;
1368 return pos;
1369 }
1370 }
1371 }
1372 else if ( Chars.isCharASCII( chars, pos, 's' ) || Chars.isCharASCII( chars, pos, 'S' ) )
1373 {
1374 pos++;
1375
1376 if ( Chars.isCharASCII( chars, pos, 'u' ) || Chars.isCharASCII( chars, pos, 'U' ) )
1377 {
1378 pos++;
1379
1380 if ( Chars.isCharASCII( chars, pos, 'b' ) || Chars.isCharASCII( chars, pos, 'B' ) )
1381 {
1382 pos++;
1383
1384 scope = SearchScope.SUBTREE;
1385 return pos;
1386 }
1387 }
1388 }
1389 else if ( Chars.isCharASCII( chars, pos, '?' ) )
1390 {
1391
1392 return pos;
1393 }
1394 else if ( pos == chars.length )
1395 {
1396
1397 return pos;
1398 }
1399
1400
1401 return -1;
1402 }
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417 private int parseExtensions( char[] chars, int pos )
1418 {
1419 int start = pos;
1420 boolean isCritical = false;
1421 boolean isNewExtension = true;
1422 boolean hasValue = false;
1423 String extension = null;
1424 String value = null;
1425
1426 if ( pos == chars.length )
1427 {
1428 return pos;
1429 }
1430
1431 try
1432 {
1433 for ( int i = pos; i < chars.length; i++ )
1434 {
1435 if ( Chars.isCharASCII( chars, i, ',' ) )
1436 {
1437 if ( isNewExtension )
1438 {
1439
1440
1441 return -1;
1442 }
1443 else
1444 {
1445 if ( extension == null )
1446 {
1447 extension = decode( new String( chars, start, i - start ) ).trim();
1448 }
1449 else
1450 {
1451 value = decode( new String( chars, start, i - start ) ).trim();
1452 }
1453
1454 Extension ext = new Extension( isCritical, extension, value );
1455 extensionList.add( ext );
1456
1457 isNewExtension = true;
1458 hasValue = false;
1459 isCritical = false;
1460 start = i + 1;
1461 extension = null;
1462 value = null;
1463 }
1464 }
1465 else if ( Chars.isCharASCII( chars, i, '=' ) )
1466 {
1467 if ( hasValue )
1468 {
1469
1470 continue;
1471 }
1472
1473
1474 extension = decode( new String( chars, start, i - start ) ).trim();
1475
1476 if ( extension.length() == 0 )
1477 {
1478
1479 return -1;
1480 }
1481
1482 hasValue = true;
1483 start = i + 1;
1484 }
1485 else if ( Chars.isCharASCII( chars, i, '!' ) )
1486 {
1487 if ( hasValue )
1488 {
1489
1490 continue;
1491 }
1492
1493 if ( !isNewExtension )
1494 {
1495
1496 return -1;
1497 }
1498
1499 isCritical = true;
1500 start++;
1501 }
1502 else
1503 {
1504 isNewExtension = false;
1505 }
1506 }
1507
1508 if ( extension == null )
1509 {
1510 extension = decode( new String( chars, start, chars.length - start ) ).trim();
1511 }
1512 else
1513 {
1514 value = decode( new String( chars, start, chars.length - start ) ).trim();
1515 }
1516
1517 Extension ext = new Extension( isCritical, extension, value );
1518 extensionList.add( ext );
1519
1520 return chars.length;
1521 }
1522 catch ( LdapUriException ue )
1523 {
1524 return -1;
1525 }
1526 }
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572 public static String urlEncode( String url, boolean doubleEncode )
1573 {
1574 StringBuffer sb = new StringBuffer();
1575
1576 for ( int i = 0; i < url.length(); i++ )
1577 {
1578 char c = url.charAt( i );
1579
1580 switch ( c )
1581
1582 {
1583
1584
1585
1586
1587
1588 case ':':
1589 case '/':
1590 case '#':
1591 case '[':
1592 case ']':
1593 case '@':
1594
1595
1596
1597
1598 case '!':
1599 case '$':
1600 case '&':
1601 case '\'':
1602 case '(':
1603 case ')':
1604 case '*':
1605 case '+':
1606 case ';':
1607 case '=':
1608
1609
1610
1611 case 'a':
1612 case 'b':
1613 case 'c':
1614 case 'd':
1615 case 'e':
1616 case 'f':
1617 case 'g':
1618 case 'h':
1619 case 'i':
1620 case 'j':
1621 case 'k':
1622 case 'l':
1623 case 'm':
1624 case 'n':
1625 case 'o':
1626 case 'p':
1627 case 'q':
1628 case 'r':
1629 case 's':
1630 case 't':
1631 case 'u':
1632 case 'v':
1633 case 'w':
1634 case 'x':
1635 case 'y':
1636 case 'z':
1637
1638 case 'A':
1639 case 'B':
1640 case 'C':
1641 case 'D':
1642 case 'E':
1643 case 'F':
1644 case 'G':
1645 case 'H':
1646 case 'I':
1647 case 'J':
1648 case 'K':
1649 case 'L':
1650 case 'M':
1651 case 'N':
1652 case 'O':
1653 case 'P':
1654 case 'Q':
1655 case 'R':
1656 case 'S':
1657 case 'T':
1658 case 'U':
1659 case 'V':
1660 case 'W':
1661 case 'X':
1662 case 'Y':
1663 case 'Z':
1664
1665 case '0':
1666 case '1':
1667 case '2':
1668 case '3':
1669 case '4':
1670 case '5':
1671 case '6':
1672 case '7':
1673 case '8':
1674 case '9':
1675
1676 case '-':
1677 case '.':
1678 case '_':
1679 case '~':
1680
1681 sb.append( c );
1682 break;
1683
1684 case ',':
1685
1686
1687 if ( doubleEncode )
1688 {
1689 sb.append( "%2c" );
1690 }
1691 else
1692 {
1693 sb.append( c );
1694 }
1695 break;
1696
1697 default:
1698
1699
1700 byte[] bytes = Unicode.charToBytes( c );
1701 char[] hex = Strings.toHexString( bytes ).toCharArray();
1702 for ( int j = 0; j < hex.length; j++ )
1703 {
1704 if ( j % 2 == 0 )
1705 {
1706 sb.append( '%' );
1707 }
1708 sb.append( hex[j] );
1709
1710 }
1711
1712 break;
1713 }
1714 }
1715
1716 return sb.toString();
1717 }
1718
1719
1720
1721
1722
1723
1724
1725 @Override
1726 public String toString()
1727 {
1728 StringBuffer sb = new StringBuffer();
1729
1730 sb.append( scheme );
1731
1732 if ( host != null )
1733 {
1734 switch ( hostType )
1735 {
1736 case IPV4:
1737 case REGULAR_NAME:
1738 sb.append( host );
1739 break;
1740
1741 case IPV6:
1742 case IPV_FUTURE:
1743 sb.append( '[' ).append( host ).append( ']' );
1744 break;
1745
1746 default:
1747 throw new IllegalArgumentException( "Unexpected HostTypeEnum " + hostType );
1748 }
1749 }
1750
1751 if ( port != -1 )
1752 {
1753 sb.append( ':' ).append( port );
1754 }
1755
1756 if ( dn != null )
1757 {
1758 sb.append( '/' ).append( urlEncode( dn.getName(), false ) );
1759
1760 if ( !attributes.isEmpty() || forceScopeRendering
1761 || ( ( scope != SearchScope.OBJECT ) || ( filter != null ) || !extensionList.isEmpty() ) )
1762 {
1763 sb.append( '?' );
1764
1765 boolean isFirst = true;
1766
1767 for ( String attribute : attributes )
1768 {
1769 if ( isFirst )
1770 {
1771 isFirst = false;
1772 }
1773 else
1774 {
1775 sb.append( ',' );
1776 }
1777
1778 sb.append( urlEncode( attribute, false ) );
1779 }
1780 }
1781
1782 if ( forceScopeRendering )
1783 {
1784 sb.append( '?' );
1785
1786 sb.append( scope.getLdapUrlValue() );
1787 }
1788 else
1789 {
1790 if ( ( scope != SearchScope.OBJECT ) || ( filter != null ) || !extensionList.isEmpty() )
1791 {
1792 sb.append( '?' );
1793
1794 switch ( scope )
1795 {
1796 case ONELEVEL:
1797 case SUBTREE:
1798 sb.append( scope.getLdapUrlValue() );
1799 break;
1800
1801 default:
1802 break;
1803 }
1804
1805 if ( ( filter != null ) || !extensionList.isEmpty() )
1806 {
1807 sb.append( "?" );
1808
1809 if ( filter != null )
1810 {
1811 sb.append( urlEncode( filter, false ) );
1812 }
1813
1814 if ( !extensionList.isEmpty() )
1815 {
1816 sb.append( '?' );
1817
1818 boolean isFirst = true;
1819
1820 if ( !extensionList.isEmpty() )
1821 {
1822 for ( Extension extension : extensionList )
1823 {
1824 if ( !isFirst )
1825 {
1826 sb.append( ',' );
1827 }
1828 else
1829 {
1830 isFirst = false;
1831 }
1832
1833 if ( extension.isCritical )
1834 {
1835 sb.append( '!' );
1836 }
1837 sb.append( urlEncode( extension.type, false ) );
1838
1839 if ( extension.value != null )
1840 {
1841 sb.append( '=' );
1842 sb.append( urlEncode( extension.value, true ) );
1843 }
1844 }
1845 }
1846 }
1847 }
1848 }
1849 }
1850 }
1851 else
1852 {
1853 sb.append( '/' );
1854 }
1855
1856 return sb.toString();
1857 }
1858
1859
1860
1861
1862
1863 public List<String> getAttributes()
1864 {
1865 return attributes;
1866 }
1867
1868
1869
1870
1871
1872 public Dn getDn()
1873 {
1874 return dn;
1875 }
1876
1877
1878
1879
1880
1881 public List<Extension> getExtensions()
1882 {
1883 return extensionList;
1884 }
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895 public Extension getExtension( String type )
1896 {
1897 for ( Extension extension : getExtensions() )
1898 {
1899 if ( extension.getType().equalsIgnoreCase( type ) )
1900 {
1901 return extension;
1902 }
1903 }
1904 return null;
1905 }
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916 public String getExtensionValue( String type )
1917 {
1918 for ( Extension extension : getExtensions() )
1919 {
1920 if ( extension.getType().equalsIgnoreCase( type ) )
1921 {
1922 return extension.getValue();
1923 }
1924 }
1925 return null;
1926 }
1927
1928
1929
1930
1931
1932 public String getFilter()
1933 {
1934 return filter;
1935 }
1936
1937
1938
1939
1940
1941 public String getHost()
1942 {
1943 return host;
1944 }
1945
1946
1947
1948
1949
1950 public int getPort()
1951 {
1952 return port;
1953 }
1954
1955
1956
1957
1958
1959
1960
1961
1962 public SearchScope getScope()
1963 {
1964 return scope;
1965 }
1966
1967
1968
1969
1970
1971 public String getScheme()
1972 {
1973 return scheme;
1974 }
1975
1976
1977
1978
1979
1980 public int getNbBytes()
1981 {
1982 return bytes != null ? bytes.length : 0;
1983 }
1984
1985
1986
1987
1988
1989 public byte[] getBytesReference()
1990 {
1991 return bytes;
1992 }
1993
1994
1995
1996
1997
1998 public byte[] getBytesCopy()
1999 {
2000 if ( bytes != null )
2001 {
2002 byte[] copy = new byte[bytes.length];
2003 System.arraycopy( bytes, 0, copy, 0, bytes.length );
2004 return copy;
2005 }
2006 else
2007 {
2008 return null;
2009 }
2010 }
2011
2012
2013
2014
2015
2016 public String getString()
2017 {
2018 return string;
2019 }
2020
2021
2022
2023
2024
2025 @Override
2026 public int hashCode()
2027 {
2028 return this.toString().hashCode();
2029 }
2030
2031
2032
2033
2034
2035 @Override
2036 public boolean equals( Object obj )
2037 {
2038 if ( this == obj )
2039 {
2040 return true;
2041 }
2042 if ( obj == null )
2043 {
2044 return false;
2045 }
2046 if ( getClass() != obj.getClass() )
2047 {
2048 return false;
2049 }
2050
2051 final LdapUrl other = ( LdapUrl ) obj;
2052 return this.toString().equals( other.toString() );
2053 }
2054
2055
2056
2057
2058
2059
2060
2061 public void setScheme( String scheme )
2062 {
2063 if ( ( ( scheme != null ) && LDAP_SCHEME.equals( scheme ) ) || LDAPS_SCHEME.equals( scheme ) )
2064 {
2065 this.scheme = scheme;
2066 }
2067 else
2068 {
2069 this.scheme = LDAP_SCHEME;
2070 }
2071
2072 }
2073
2074
2075
2076
2077
2078
2079
2080 public void setHost( String host )
2081 {
2082 this.host = host;
2083 }
2084
2085
2086
2087
2088
2089
2090
2091 public void setPort( int port )
2092 {
2093 if ( ( port < 1 ) || ( port > 65535 ) )
2094 {
2095 this.port = -1;
2096 }
2097 else
2098 {
2099 this.port = port;
2100 }
2101 }
2102
2103
2104
2105
2106
2107
2108
2109 public void setDn( Dn dn )
2110 {
2111 this.dn = dn;
2112 }
2113
2114
2115
2116
2117
2118
2119
2120 public void setAttributes( List<String> attributes )
2121 {
2122 if ( attributes == null )
2123 {
2124 this.attributes.clear();
2125 }
2126 else
2127 {
2128 this.attributes = attributes;
2129 }
2130 }
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140 public void setScope( int scope )
2141 {
2142 try
2143 {
2144 this.scope = SearchScope.getSearchScope( scope );
2145 }
2146 catch ( IllegalArgumentException iae )
2147 {
2148 this.scope = SearchScope.OBJECT;
2149 }
2150 }
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160 public void setScope( SearchScope scope )
2161 {
2162 if ( scope == null )
2163 {
2164 this.scope = SearchScope.OBJECT;
2165 }
2166 else
2167 {
2168 this.scope = scope;
2169 }
2170 }
2171
2172
2173
2174
2175
2176
2177
2178 public void setFilter( String filter )
2179 {
2180 this.filter = filter;
2181 }
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191 public void setForceScopeRendering( boolean forceScopeRendering )
2192 {
2193 this.forceScopeRendering = forceScopeRendering;
2194 }
2195
2196
2197
2198
2199
2200
2201 public static class Extension
2202 {
2203 private boolean isCritical;
2204 private String type;
2205 private String value;
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215 public Extension( boolean isCritical, String type, String value )
2216 {
2217 super();
2218 this.isCritical = isCritical;
2219 this.type = type;
2220 this.value = value;
2221 }
2222
2223
2224
2225
2226
2227
2228
2229 public boolean isCritical()
2230 {
2231 return isCritical;
2232 }
2233
2234
2235
2236
2237
2238
2239
2240 public void setCritical( boolean critical )
2241 {
2242 this.isCritical = critical;
2243 }
2244
2245
2246
2247
2248
2249
2250
2251 public String getType()
2252 {
2253 return type;
2254 }
2255
2256
2257
2258
2259
2260
2261
2262 public void setType( String type )
2263 {
2264 this.type = type;
2265 }
2266
2267
2268
2269
2270
2271
2272
2273 public String getValue()
2274 {
2275 return value;
2276 }
2277
2278
2279
2280
2281
2282
2283
2284 public void setValue( String value )
2285 {
2286 this.value = value;
2287 }
2288 }
2289 }