1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.util;
20
21 import org.apache.commons.codec.DecoderException;
22 import org.apache.commons.codec.binary.Base64;
23 import org.apache.commons.codec.binary.Hex;
24
25 import java.nio.charset.StandardCharsets;
26 import java.text.ParseException;
27 import java.text.SimpleDateFormat;
28 import java.util.ArrayList;
29 import java.util.Calendar;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.regex.Pattern;
33
34
35
36
37 public final class StringUtil {
38 private StringUtil() {
39 }
40
41
42
43
44
45
46
47
48
49 public static String getToken(String value, String startDelimiter, String endDelimiter) {
50 String token = null;
51 if (value != null) {
52 int startIndex = value.indexOf(startDelimiter);
53 if (startIndex >= 0) {
54 startIndex += startDelimiter.length();
55 int endIndex = value.indexOf(endDelimiter, startIndex);
56 if (endIndex >= 0) {
57 token = value.substring(startIndex, endIndex);
58 }
59 }
60 }
61 return token;
62 }
63
64
65
66
67
68
69
70
71
72
73 public static String getLastToken(String value, String startDelimiter, String endDelimiter) {
74 String token = null;
75 if (value != null) {
76 int startIndex = value.lastIndexOf(startDelimiter);
77 if (startIndex >= 0) {
78 startIndex += startDelimiter.length();
79 int endIndex = value.indexOf(endDelimiter, startIndex);
80 if (endIndex >= 0) {
81 token = value.substring(startIndex, endIndex);
82 }
83 }
84 }
85 return token;
86 }
87
88
89
90
91
92
93
94
95
96
97 public static String replaceToken(String value, String startDelimiter, String endDelimiter, String newToken) {
98 String result = null;
99 if (value != null) {
100 int startIndex = value.indexOf(startDelimiter);
101 if (startIndex >= 0) {
102 startIndex += startDelimiter.length();
103 int endIndex = value.indexOf(endDelimiter, startIndex);
104 if (endIndex >= 0) {
105 result = value.substring(0, startIndex) + newToken + value.substring(endIndex);
106 }
107 }
108 }
109 return result;
110 }
111
112
113
114
115
116
117
118
119 public static String join(Set<String> values, String separator) {
120 if (values != null && !values.isEmpty()) {
121 StringBuilder result = new StringBuilder();
122 for (String value : values) {
123 if (result.length() > 0) {
124 result.append(separator);
125 }
126 result.append(value);
127 }
128 return result.toString();
129 } else {
130 return null;
131 }
132 }
133
134 static class PatternMap {
135 protected String match;
136 protected String value;
137 protected Pattern pattern;
138
139 protected PatternMap(String match, String value) {
140 this.match = match;
141 this.value = value;
142 pattern = Pattern.compile(match);
143 }
144
145 protected PatternMap(String match, String escapedMatch, String value) {
146 this.match = match;
147 this.value = value;
148 pattern = Pattern.compile(escapedMatch);
149 }
150
151 protected PatternMap(String match, Pattern pattern, String value) {
152 this.match = match;
153 this.value = value;
154 this.pattern = pattern;
155 }
156
157 protected String replaceAll(String string) {
158 if (string != null && string.contains(match)) {
159 return pattern.matcher(string).replaceAll(value);
160 } else {
161 return string;
162 }
163 }
164 }
165
166 private static final Pattern AMP_PATTERN = Pattern.compile("&");
167 private static final Pattern PLUS_PATTERN = Pattern.compile("\\+");
168
169 private static final Pattern QUOTE_PATTERN = Pattern.compile("\"");
170 private static final Pattern CR_PATTERN = Pattern.compile("\r");
171 private static final Pattern LF_PATTERN = Pattern.compile("\n");
172
173 private static final List<PatternMap> URLENCODED_PATTERNS = new ArrayList<>();
174 static {
175 URLENCODED_PATTERNS.add(new PatternMap(String.valueOf((char) 0xF8FF), "_xF8FF_"));
176 URLENCODED_PATTERNS.add(new PatternMap("%26", "&"));
177 URLENCODED_PATTERNS.add(new PatternMap("%2B", "+"));
178 URLENCODED_PATTERNS.add(new PatternMap("%3A", ":"));
179 URLENCODED_PATTERNS.add(new PatternMap("%3B", ";"));
180 URLENCODED_PATTERNS.add(new PatternMap("%3C", "<"));
181 URLENCODED_PATTERNS.add(new PatternMap("%3E", ">"));
182 URLENCODED_PATTERNS.add(new PatternMap("%22", "\""));
183 URLENCODED_PATTERNS.add(new PatternMap("%23", "#"));
184 URLENCODED_PATTERNS.add(new PatternMap("%2A", "*"));
185 URLENCODED_PATTERNS.add(new PatternMap("%7C", "|"));
186 URLENCODED_PATTERNS.add(new PatternMap("%3F", "?"));
187 URLENCODED_PATTERNS.add(new PatternMap("%7E", "~"));
188
189
190 URLENCODED_PATTERNS.add(new PatternMap("\n", "_x000D__x000A_"));
191
192
193 URLENCODED_PATTERNS.add(new PatternMap("%25", "%"));
194 }
195
196 private static final List<PatternMap> URLENCODE_PATTERNS = new ArrayList<>();
197 static {
198
199 URLENCODE_PATTERNS.add(new PatternMap("%", "%25"));
200
201 URLENCODE_PATTERNS.add(new PatternMap("_xF8FF_", String.valueOf((char) 0xF8FF)));
202 URLENCODE_PATTERNS.add(new PatternMap("&", AMP_PATTERN, "%26"));
203 URLENCODE_PATTERNS.add(new PatternMap("+", PLUS_PATTERN, "%2B"));
204 URLENCODE_PATTERNS.add(new PatternMap(":", "%3A"));
205 URLENCODE_PATTERNS.add(new PatternMap(";", "%3B"));
206 URLENCODE_PATTERNS.add(new PatternMap("<", "%3C"));
207 URLENCODE_PATTERNS.add(new PatternMap(">", "%3E"));
208 URLENCODE_PATTERNS.add(new PatternMap("\"", "%22"));
209 URLENCODE_PATTERNS.add(new PatternMap("#", "%23"));
210 URLENCODE_PATTERNS.add(new PatternMap("~", "%7E"));
211 URLENCODE_PATTERNS.add(new PatternMap("*", "\\*", "%2A"));
212 URLENCODE_PATTERNS.add(new PatternMap("|", "\\|", "%7C"));
213 URLENCODE_PATTERNS.add(new PatternMap("?", "\\?", "%3F"));
214
215 URLENCODE_PATTERNS.add(new PatternMap("_x000D__x000A_", "\r\n"));
216
217 }
218
219 private static final List<PatternMap> XML_DECODE_PATTERNS = new ArrayList<>();
220 static {
221 XML_DECODE_PATTERNS.add(new PatternMap("&", "&"));
222 XML_DECODE_PATTERNS.add(new PatternMap("<", "<"));
223 XML_DECODE_PATTERNS.add(new PatternMap(">", ">"));
224 }
225
226 private static final List<PatternMap> XML_ENCODE_PATTERNS = new ArrayList<>();
227 static {
228 XML_ENCODE_PATTERNS.add(new PatternMap("&", AMP_PATTERN, "&"));
229 XML_ENCODE_PATTERNS.add(new PatternMap("<", "<"));
230 XML_ENCODE_PATTERNS.add(new PatternMap(">", ">"));
231 }
232
233 private static final Pattern SLASH_PATTERN = Pattern.compile("/");
234 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
235 private static final Pattern DASH_PATTERN = Pattern.compile("-");
236
237
238 private static final Pattern APOS_PATTERN = Pattern.compile("'");
239
240
241
242
243
244
245
246 public static String xmlEncode(String name) {
247 String result = name;
248 if (result != null) {
249 for (PatternMap patternMap : XML_ENCODE_PATTERNS) {
250 result = patternMap.replaceAll(result);
251 }
252 }
253 return result;
254 }
255
256
257
258
259
260
261
262 public static String xmlEncodeAttribute(String name) {
263 String result = xmlEncode(name);
264 if (result != null) {
265 if (result.indexOf('"') >= 0) {
266 result = QUOTE_PATTERN.matcher(result).replaceAll(""");
267 }
268 if (result.indexOf('\r') >= 0) {
269 result = CR_PATTERN.matcher(result).replaceAll("
");
270 }
271 if (result.indexOf('\n') >= 0) {
272 result = LF_PATTERN.matcher(result).replaceAll("
");
273 }
274 }
275 return result;
276 }
277
278
279
280
281
282
283
284 public static String xmlDecode(String name) {
285 String result = name;
286 if (result != null) {
287 for (PatternMap patternMap : XML_DECODE_PATTERNS) {
288 result = patternMap.replaceAll(result);
289 }
290 }
291 return result;
292 }
293
294
295
296
297
298
299
300 @SuppressWarnings("unused")
301 public static String base64ToHex(String value) {
302 String hexValue = null;
303 if (value != null) {
304 hexValue = new String(Hex.encodeHex(Base64.decodeBase64(value.getBytes(StandardCharsets.UTF_8))));
305 }
306 return hexValue;
307 }
308
309
310
311
312
313
314
315
316 @SuppressWarnings("unused")
317 public static String hexToBase64(String value) throws DecoderException {
318 String base64Value = null;
319 if (value != null) {
320 base64Value = new String(Base64.encodeBase64(Hex.decodeHex(value.toCharArray())), StandardCharsets.UTF_8);
321 }
322 return base64Value;
323 }
324
325
326
327
328
329
330
331 public static String encodeUrlcompname(String value) {
332 String result = value;
333 if (result != null) {
334 for (PatternMap patternMap : URLENCODE_PATTERNS) {
335 result = patternMap.replaceAll(result);
336 }
337 }
338 return result;
339 }
340
341
342
343
344
345
346
347 public static String decodeUrlcompname(String urlcompname) {
348 String result = urlcompname;
349 if (result != null) {
350 for (PatternMap patternMap : URLENCODED_PATTERNS) {
351 result = patternMap.replaceAll(result);
352 }
353 }
354 return result;
355 }
356
357
358
359
360
361
362
363
364 public static String encodePlusSign(String value) {
365 String result = value;
366 if (result.indexOf('+') >= 0) {
367 result = PLUS_PATTERN.matcher(result).replaceAll("%2B");
368 }
369 return result;
370 }
371
372
373
374
375
376
377
378 public static String base64ToUrl(String value) {
379 String result = value;
380 if (result != null) {
381 if (result.indexOf('+') >= 0) {
382 result = PLUS_PATTERN.matcher(result).replaceAll("-");
383 }
384 if (result.indexOf('/') >= 0) {
385 result = SLASH_PATTERN.matcher(result).replaceAll("_");
386 }
387 }
388 return result;
389 }
390
391
392
393
394
395
396
397 public static String urlToBase64(String value) {
398 String result = value;
399 if (result.indexOf('-') >= 0) {
400 result = DASH_PATTERN.matcher(result).replaceAll("+");
401 }
402 if (result.indexOf('_') >= 0) {
403 result = UNDERSCORE_PATTERN.matcher(result).replaceAll("/");
404 }
405 return result;
406 }
407
408
409
410
411
412
413
414 public static String davSearchEncode(String value) {
415 return escapeQuotes(value);
416 }
417
418
419
420
421
422
423 public static String escapeQuotes(String value) {
424 String result = value;
425 if (result != null && result.indexOf('\'') >= 0) {
426 result = APOS_PATTERN.matcher(result).replaceAll("''");
427 }
428 return result;
429 }
430
431
432
433
434
435
436
437 public static String escapeDoubleQuotes(String value) {
438 String result = value;
439 if (result != null && result.indexOf('"') >= 0) {
440 result = QUOTE_PATTERN.matcher(result).replaceAll("\\\\\"");
441 }
442 return result;
443 }
444
445
446
447
448
449
450
451 public static String convertZuluDateTimeToAllDay(String value) {
452 String result = value;
453 if (value != null && value.length() != 8) {
454
455 try {
456 Calendar calendar = Calendar.getInstance();
457 SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
458 calendar.setTime(dateParser.parse(value));
459 calendar.add(Calendar.HOUR_OF_DAY, 12);
460 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
461 result = dateFormatter.format(calendar.getTime());
462 } catch (ParseException e) {
463
464 }
465 }
466 return result;
467 }
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487 public static String parseQuotedImapString(String quoted) throws ParseException {
488 if (null == quoted) {
489 return null;
490 }
491 char[] quotedChars = quoted.toCharArray();
492 if (quotedChars.length < 2) {
493
494
495 throw new ParseException("Not a valid imap quoted string (too short): " + quoted, 0);
496 }
497 if ('"' != quotedChars[0]) {
498 throw new ParseException("Not a valid imap quoted string (does not start with double quote): " + quoted, 0);
499 }
500
501
502 char[] resultChars = new char[quotedChars.length - 2];
503
504
505
506
507 int resultPos = 0;
508
509 boolean backslashMode = false;
510
511 for (int i = 1; i < quotedChars.length - 1; i++) {
512 if (backslashMode) {
513 if ('\\' == quotedChars[i] || '"' == quotedChars[i]) {
514 resultChars[resultPos++] = quotedChars[i];
515 backslashMode = false;
516 } else {
517 throw new ParseException("Not a valid imap quoted string "
518 + "(only '\"' and '\\' allowed after '\\') at index " + i + ": " + quoted, i);
519 }
520 } else {
521 if ('\\' == quotedChars[i]) {
522 backslashMode = true;
523 } else {
524 resultChars[resultPos++] = quotedChars[i];
525 }
526 }
527 }
528 if (backslashMode) {
529 throw new ParseException("Not a valid imap quoted string "
530 + "(outer ending quote is backslashed): " + quoted, quotedChars.length - 1);
531 }
532 if ('"' != quotedChars[quotedChars.length - 1]) {
533 throw new ParseException("Not a valid imap quoted string "
534 + "(does not end with double quotes): " + quoted, quotedChars.length - 1);
535 }
536 return new String(resultChars, 0, resultPos);
537 }
538
539
540
541
542
543
544
545 public static String removeQuotes(String value) {
546 String result = value;
547 if (result != null) {
548 if (result.startsWith("\"") || result.startsWith("{") || result.startsWith("(")) {
549 result = result.substring(1);
550 }
551 if (result.endsWith("\"") || result.endsWith("}") || result.endsWith(")")) {
552 result = result.substring(0, result.length() - 1);
553 }
554 }
555 return result;
556 }
557
558
559
560
561
562
563 public static String decodeFolderName(String folderName) {
564 if (folderName.contains("_xF8FF_")) {
565 return folderName.replace("_xF8FF_", "/");
566 }
567 if (folderName.contains("_x003E_")) {
568 return folderName.replace("_x003E_", ">");
569 }
570 return folderName;
571 }
572
573
574
575
576
577
578 public static String encodeFolderName(String folderName) {
579 if (folderName.contains("/")) {
580 folderName = folderName.replace("/", "_xF8FF_");
581 }
582 if (folderName.contains(">")) {
583 folderName = folderName.replace(">", "_x003E_");
584 }
585 return folderName;
586 }
587
588 }