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.ldif;
21
22
23 import java.io.IOException;
24 import java.nio.charset.StandardCharsets;
25 import java.util.Base64;
26
27 import javax.naming.directory.Attributes;
28
29 import org.apache.directory.api.i18n.I18n;
30 import org.apache.directory.api.ldap.model.entry.Attribute;
31 import org.apache.directory.api.ldap.model.entry.AttributeUtils;
32 import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
33 import org.apache.directory.api.ldap.model.entry.Entry;
34 import org.apache.directory.api.ldap.model.entry.Modification;
35 import org.apache.directory.api.ldap.model.entry.Value;
36 import org.apache.directory.api.ldap.model.exception.LdapException;
37 import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
38 import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
39 import org.apache.directory.api.ldap.model.name.Dn;
40 import org.apache.directory.api.util.Strings;
41
42
43
44
45
46
47
48 public final class LdifUtils
49 {
50
51 private static final boolean[] LDIF_SAFE_STARTING_CHAR_ALPHABET = new boolean[128];
52
53
54 private static final boolean[] LDIF_SAFE_OTHER_CHARS_ALPHABET = new boolean[128];
55
56
57 private static final int DEFAULT_LINE_LENGTH = 80;
58
59
60 private static final String LINE_SEPARATOR = System.getProperty( "line.separator" );
61
62 static
63 {
64
65 for ( int i = 0; i < 128; i++ )
66 {
67 LDIF_SAFE_STARTING_CHAR_ALPHABET[i] = true;
68 }
69
70
71 LDIF_SAFE_STARTING_CHAR_ALPHABET[0] = false;
72
73 LDIF_SAFE_STARTING_CHAR_ALPHABET[10] = false;
74
75 LDIF_SAFE_STARTING_CHAR_ALPHABET[13] = false;
76
77 LDIF_SAFE_STARTING_CHAR_ALPHABET[32] = false;
78
79 LDIF_SAFE_STARTING_CHAR_ALPHABET[58] = false;
80
81 LDIF_SAFE_STARTING_CHAR_ALPHABET[60] = false;
82
83
84 for ( int i = 0; i < 128; i++ )
85 {
86 LDIF_SAFE_OTHER_CHARS_ALPHABET[i] = true;
87 }
88
89
90 LDIF_SAFE_OTHER_CHARS_ALPHABET[0] = false;
91
92 LDIF_SAFE_OTHER_CHARS_ALPHABET[10] = false;
93
94 LDIF_SAFE_OTHER_CHARS_ALPHABET[13] = false;
95 }
96
97
98
99
100
101 private LdifUtils()
102 {
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public static boolean isLDIFSafe( String str )
137 {
138 if ( Strings.isEmpty( str ) )
139 {
140
141 return true;
142 }
143
144
145 char currentChar = str.charAt( 0 );
146
147 if ( ( currentChar > 127 ) || !LDIF_SAFE_STARTING_CHAR_ALPHABET[currentChar] )
148 {
149 return false;
150 }
151
152
153 for ( int i = 1; i < str.length(); i++ )
154 {
155 currentChar = str.charAt( i );
156
157 if ( ( currentChar > 127 ) || !LDIF_SAFE_OTHER_CHARS_ALPHABET[currentChar] )
158 {
159 return false;
160 }
161 }
162
163
164 return currentChar != ' ';
165 }
166
167
168
169
170
171
172
173
174
175 public static String convertToLdif( Attributes attrs ) throws LdapException
176 {
177 return convertAttributesToLdif( AttributeUtils.toEntry( attrs, null ), DEFAULT_LINE_LENGTH );
178 }
179
180
181
182
183
184
185
186
187
188
189 public static String convertToLdif( Attributes attrs, int length ) throws LdapException
190 {
191 return convertAttributesToLdif( AttributeUtils.toEntry( attrs, null ), length );
192 }
193
194
195
196
197
198
199
200
201
202
203
204 public static String convertToLdif( Attributes attrs, Dn dn, int length ) throws LdapException
205 {
206 return convertToLdif( AttributeUtils.toEntry( attrs, dn ), length );
207 }
208
209
210
211
212
213
214
215
216
217
218 public static String convertToLdif( Attributes attrs, Dn dn ) throws LdapException
219 {
220 return convertToLdif( AttributeUtils.toEntry( attrs, dn ), DEFAULT_LINE_LENGTH );
221 }
222
223
224
225
226
227
228
229
230 public static String convertToLdif( Entry entry )
231 {
232 return convertToLdif( entry, DEFAULT_LINE_LENGTH );
233 }
234
235
236
237
238
239
240
241
242
243 public static String convertToLdif( Entry entry, boolean includeVersionInfo )
244 {
245 String ldif = convertToLdif( entry, DEFAULT_LINE_LENGTH );
246
247 if ( includeVersionInfo )
248 {
249 ldif = "version: 1" + LINE_SEPARATOR + ldif;
250 }
251
252 return ldif;
253 }
254
255
256
257
258
259
260
261
262 public static String convertAttributesToLdif( Entry entry )
263 {
264 return convertAttributesToLdif( entry, DEFAULT_LINE_LENGTH );
265 }
266
267
268
269
270
271
272
273
274
275 public static Attributes getJndiAttributesFromLdif( String ldif ) throws LdapLdifException
276 {
277 try ( LdifAttributesReader reader = new LdifAttributesReader() )
278 {
279 return AttributeUtils.toAttributes( reader.parseEntry( ldif ) );
280 }
281 catch ( IOException ioe )
282 {
283 throw new LdapLdifException( ioe.getMessage(), ioe );
284 }
285 }
286
287
288
289
290
291
292
293
294
295 public static String convertToLdif( Entry entry, int length )
296 {
297 StringBuilder sb = new StringBuilder();
298
299 if ( entry.getDn() != null )
300 {
301
302 if ( isLDIFSafe( entry.getDn().getName() ) )
303 {
304 sb.append( stripLineToNChars( "dn: " + entry.getDn().getName(), length ) );
305 }
306 else
307 {
308 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) );
309 }
310
311 sb.append( '\n' );
312 }
313
314
315 for ( Attribute attribute : entry )
316 {
317 sb.append( convertToLdif( attribute, length ) );
318 }
319
320 return sb.toString();
321 }
322
323
324
325
326
327
328
329
330
331 public static String convertAttributesToLdif( Entry entry, int length )
332 {
333 StringBuilder sb = new StringBuilder();
334
335
336 for ( Attribute attribute : entry )
337 {
338 sb.append( convertToLdif( attribute, length ) );
339 }
340
341 return sb.toString();
342 }
343
344
345
346
347
348
349
350
351
352 public static String convertToLdif( LdifEntry entry ) throws LdapException
353 {
354 return convertToLdif( entry, DEFAULT_LINE_LENGTH );
355 }
356
357
358
359
360
361
362
363
364
365
366 public static String convertToLdif( LdifEntry entry, int length ) throws LdapException
367 {
368 StringBuilder sb = new StringBuilder();
369
370
371 if ( isLDIFSafe( entry.getDn().getName() ) )
372 {
373 sb.append( stripLineToNChars( "dn: " + entry.getDn(), length ) );
374 }
375 else
376 {
377 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) );
378 }
379
380 sb.append( '\n' );
381
382
383 String changeType = Strings.toLowerCaseAscii( entry.getChangeType().toString() );
384
385 if ( entry.getChangeType() != ChangeType.None )
386 {
387
388 if ( entry.hasControls() )
389 {
390 for ( LdifControl control : entry.getControls().values() )
391 {
392 StringBuilder controlStr = new StringBuilder();
393
394 controlStr.append( "control: " ).append( control.getOid() );
395 controlStr.append( " " ).append( control.isCritical() );
396
397 if ( control.hasValue() )
398 {
399 controlStr.append( "::" ).append( Base64.getEncoder().encode( control.getValue() ) );
400 }
401
402 sb.append( stripLineToNChars( controlStr.toString(), length ) );
403 sb.append( '\n' );
404 }
405 }
406
407 sb.append( stripLineToNChars( "changetype: " + changeType, length ) );
408 sb.append( '\n' );
409 }
410
411 switch ( entry.getChangeType() )
412 {
413 case None:
414 if ( entry.hasControls() )
415 {
416 sb.append( stripLineToNChars( "changetype: " + ChangeType.Add, length ) );
417 }
418
419
420
421 case Add:
422 if ( entry.getEntry() == null )
423 {
424 throw new LdapException( I18n.err( I18n.ERR_13472_ENTRY_WITH_NO_ATTRIBUTE ) );
425 }
426
427
428 for ( Attribute attribute : entry.getEntry() )
429 {
430 sb.append( convertToLdif( attribute, length ) );
431 }
432
433 break;
434
435 case Delete:
436 if ( entry.getEntry() != null )
437 {
438 throw new LdapException( I18n.err( I18n.ERR_13471_DELETED_ENTRY_WITH_ATTRIBUTES ) );
439 }
440
441 break;
442
443 case ModDn:
444 case ModRdn:
445 if ( entry.getEntry() != null )
446 {
447 throw new LdapException( I18n.err( I18n.ERR_13473_MODDN_WITH_ATTRIBUTES ) );
448 }
449
450
451 Attribute newRdn = new DefaultAttribute( "newrdn", entry.getNewRdn() );
452 sb.append( convertToLdif( newRdn, length ) );
453
454
455 sb.append( "deleteoldrdn: " );
456
457 if ( entry.isDeleteOldRdn() )
458 {
459 sb.append( "1" );
460 }
461 else
462 {
463 sb.append( "0" );
464 }
465
466 sb.append( '\n' );
467
468
469 if ( !Strings.isEmpty( entry.getNewSuperior() ) )
470 {
471 Attribute newSuperior = new DefaultAttribute( "newsuperior", entry.getNewSuperior() );
472 sb.append( convertToLdif( newSuperior, length ) );
473 }
474
475 break;
476
477 case Modify:
478 boolean isFirst = true;
479
480 for ( Modification modification : entry.getModifications() )
481 {
482
483 if ( isFirst )
484 {
485 isFirst = false;
486 }
487 else
488 {
489 sb.append( "-\n" );
490 }
491
492 switch ( modification.getOperation() )
493 {
494 case ADD_ATTRIBUTE:
495 sb.append( "add: " );
496 break;
497
498 case REMOVE_ATTRIBUTE:
499 sb.append( "delete: " );
500 break;
501
502 case REPLACE_ATTRIBUTE:
503 sb.append( "replace: " );
504 break;
505
506 case INCREMENT_ATTRIBUTE:
507 sb.append( "increment: " );
508 break;
509
510 default:
511 throw new IllegalArgumentException( I18n.err( I18n.ERR_13434_UNEXPECTED_MOD_OPERATION, modification.getOperation() ) );
512 }
513
514 sb.append( modification.getAttribute().getUpId() );
515 sb.append( '\n' );
516
517 sb.append( convertToLdif( modification.getAttribute(), length ) );
518 }
519
520 sb.append( '-' );
521 break;
522
523 default:
524 throw new IllegalArgumentException( I18n.err( I18n.ERR_13431_UNEXPECTED_CHANGETYPE, entry.getChangeType() ) );
525 }
526
527 sb.append( '\n' );
528
529 return sb.toString();
530 }
531
532
533
534
535
536
537
538
539 private static String encodeBase64( String str )
540 {
541
542 return new String( Base64.getEncoder().encode( Strings.getBytesUtf8( str ) ), StandardCharsets.UTF_8 );
543 }
544
545
546
547
548
549
550
551
552 public static String convertToLdif( Attribute attr )
553 {
554 return convertToLdif( attr, DEFAULT_LINE_LENGTH );
555 }
556
557
558
559
560
561
562
563
564
565 public static String convertToLdif( Attribute attr, int length )
566 {
567 StringBuilder sb = new StringBuilder();
568
569 if ( attr.size() == 0 )
570 {
571
572 return "";
573 }
574
575 for ( Value value : attr )
576 {
577 StringBuilder lineBuffer = new StringBuilder();
578
579 lineBuffer.append( attr.getUpId() );
580
581
582 if ( value.isNull() )
583 {
584 lineBuffer.append( ':' );
585 }
586 else if ( value.isHumanReadable() )
587 {
588
589 String str = value.getString();
590
591 if ( !LdifUtils.isLDIFSafe( str ) )
592 {
593 lineBuffer.append( ":: " ).append( encodeBase64( str ) );
594 }
595 else
596 {
597 lineBuffer.append( ':' );
598
599 if ( str != null )
600 {
601 lineBuffer.append( ' ' ).append( str );
602 }
603 }
604 }
605 else
606 {
607
608 String encoded = new String( Base64.getEncoder().encode( value.getBytes() ), StandardCharsets.UTF_8 );
609
610 lineBuffer.append( ":: " + encoded );
611 }
612
613 lineBuffer.append( '\n' );
614 sb.append( stripLineToNChars( lineBuffer.toString(), length ) );
615 }
616
617 return sb.toString();
618 }
619
620
621
622
623
624
625
626
627
628 public static String stripLineToNChars( String str, int nbChars )
629 {
630 int strLength = str.length();
631
632 if ( strLength <= nbChars )
633 {
634 return str;
635 }
636
637 if ( nbChars < 2 )
638 {
639 throw new IllegalArgumentException( I18n.err( I18n.ERR_13474_LINE_LENGTH_TOO_SHORT ) );
640 }
641
642
643
644 int charsPerLine = nbChars - 1;
645
646 int remaining = ( strLength - nbChars ) % charsPerLine;
647
648 int nbLines = 1 + ( ( strLength - nbChars ) / charsPerLine ) + ( remaining == 0 ? 0 : 1 );
649
650 int nbCharsTotal = strLength + nbLines + nbLines - 2;
651
652 char[] buffer = new char[nbCharsTotal];
653 char[] orig = str.toCharArray();
654
655 int posSrc = 0;
656 int posDst = 0;
657
658 System.arraycopy( orig, posSrc, buffer, posDst, nbChars );
659 posSrc += nbChars;
660 posDst += nbChars;
661
662 for ( int i = 0; i < nbLines - 2; i++ )
663 {
664 buffer[posDst++] = '\n';
665 buffer[posDst++] = ' ';
666
667 System.arraycopy( orig, posSrc, buffer, posDst, charsPerLine );
668 posSrc += charsPerLine;
669 posDst += charsPerLine;
670 }
671
672 buffer[posDst++] = '\n';
673 buffer[posDst++] = ' ';
674 System.arraycopy( orig, posSrc, buffer, posDst, remaining == 0 ? charsPerLine : remaining );
675
676 return new String( buffer );
677 }
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697 public static Attributes createJndiAttributes( Object... avas ) throws LdapException
698 {
699 StringBuilder sb = new StringBuilder();
700 int pos = 0;
701 boolean valueExpected = false;
702
703 for ( Object ava : avas )
704 {
705 if ( !valueExpected )
706 {
707 if ( !( ava instanceof String ) )
708 {
709 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
710 I18n.ERR_13233_ATTRIBUTE_ID_MUST_BE_A_STRING, pos + 1 ) );
711 }
712
713 String attribute = ( String ) ava;
714 sb.append( attribute );
715
716 if ( attribute.indexOf( ':' ) != -1 )
717 {
718 sb.append( '\n' );
719 }
720 else
721 {
722 valueExpected = true;
723 }
724 }
725 else
726 {
727 if ( ava instanceof String )
728 {
729 sb.append( ": " ).append( ( String ) ava ).append( '\n' );
730 }
731 else if ( ava instanceof byte[] )
732 {
733 sb.append( ":: " );
734 sb.append( new String( Base64.getEncoder().encode( ( byte[] ) ava ), StandardCharsets.UTF_8 ) );
735 sb.append( '\n' );
736 }
737 else
738 {
739 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
740 I18n.ERR_13234_ATTRIBUTE_VAL_STRING_OR_BYTE, pos + 1 ) );
741 }
742
743 valueExpected = false;
744 }
745 }
746
747 if ( valueExpected )
748 {
749 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n
750 .err( I18n.ERR_13234_ATTRIBUTE_VAL_STRING_OR_BYTE ) );
751 }
752
753 try ( LdifAttributesReader reader = new LdifAttributesReader() )
754 {
755 return AttributeUtils.toAttributes( reader.parseEntry( sb.toString() ) );
756 }
757 catch ( IOException ioe )
758 {
759 throw new LdapLdifException( ioe.getMessage(), ioe );
760 }
761 }
762 }