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