1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.exchange;
20
21 import java.util.*;
22
23
24
25
26 public class VProperty {
27
28 protected enum State {
29 KEY, PARAM_NAME, PARAM_VALUE, QUOTED_PARAM_VALUE, QUOTED_PARAM_VALUE_BACKSLASH, VALUE, BACKSLASH
30 }
31
32 protected static final HashSet<String> MULTIVALUED_PROPERTIES = new HashSet<>();
33
34 static {
35 MULTIVALUED_PROPERTIES.add("RESOURCES");
36 MULTIVALUED_PROPERTIES.add("LOCATION");
37 }
38
39 protected static class Param {
40 String name;
41 List<String> values;
42
43 public void addAll(List<String> paramValues) {
44 if (values == null) {
45 values = new ArrayList<>();
46 }
47 values.addAll(paramValues);
48 }
49
50 protected String getValue() {
51 if (values != null && !values.isEmpty()) {
52 return values.get(0);
53 } else {
54 return null;
55 }
56 }
57 }
58
59 protected String key;
60 protected List<Param> params;
61 protected List<String> values;
62
63
64
65
66
67
68
69 public VProperty(String name, String value) {
70 setKey(name);
71 setValue(value);
72 }
73
74
75
76
77
78
79 public VProperty(String line) {
80 if (line != null && !"END:VCARD".equals(line)) {
81 State state = State.KEY;
82 String paramName = null;
83 List<String> paramValues = null;
84 int startIndex = 0;
85 for (int i = 0; i < line.length(); i++) {
86 char currentChar = line.charAt(i);
87 if (state == State.KEY) {
88 if (currentChar == ':') {
89 setKey(line.substring(startIndex, i));
90 state = State.VALUE;
91 startIndex = i + 1;
92 } else if (currentChar == ';') {
93 setKey(line.substring(startIndex, i));
94 state = State.PARAM_NAME;
95 startIndex = i + 1;
96 }
97 } else if (state == State.PARAM_NAME) {
98 if (currentChar == '=') {
99 paramName = line.substring(startIndex, i).toUpperCase();
100 state = State.PARAM_VALUE;
101 paramValues = new ArrayList<>();
102 startIndex = i + 1;
103 } else if (currentChar == ';') {
104
105 paramName = line.substring(startIndex, i).toUpperCase();
106 addParam(paramName);
107 state = State.PARAM_NAME;
108 startIndex = i + 1;
109 } else if (currentChar == ':') {
110
111 paramName = line.substring(startIndex, i).toUpperCase();
112 addParam(paramName);
113 state = State.VALUE;
114 startIndex = i + 1;
115 }
116 } else if (state == State.PARAM_VALUE) {
117 if (currentChar == '"') {
118 state = State.QUOTED_PARAM_VALUE;
119 startIndex = i + 1;
120 } else if (currentChar == ':') {
121 if (startIndex < i) {
122 paramValues = addParamValue(paramValues, line.substring(startIndex, i));
123 }
124 addParam(paramName, paramValues);
125 state = State.VALUE;
126 startIndex = i + 1;
127 } else if (currentChar == ';') {
128 if (startIndex < i) {
129 paramValues = addParamValue(paramValues, line.substring(startIndex, i));
130 }
131 addParam(paramName, paramValues);
132 state = State.PARAM_NAME;
133 startIndex = i + 1;
134 } else if (currentChar == ',') {
135 if (startIndex < i) {
136 paramValues = addParamValue(paramValues, line.substring(startIndex, i));
137 }
138 startIndex = i + 1;
139 }
140 } else if (state == State.QUOTED_PARAM_VALUE) {
141 if (currentChar == '\\') {
142 state = State.QUOTED_PARAM_VALUE_BACKSLASH;
143 } else if (currentChar == '"') {
144 state = State.PARAM_VALUE;
145 paramValues = addParamValue(paramValues, line.substring(startIndex, i));
146 startIndex = i + 1;
147 }
148 } else if (state == State.QUOTED_PARAM_VALUE_BACKSLASH){
149 state = State.QUOTED_PARAM_VALUE;
150 } else if (state == State.VALUE) {
151 if (currentChar == '\\') {
152 state = State.BACKSLASH;
153 } else if (currentChar == ';' || (MULTIVALUED_PROPERTIES.contains(key) && currentChar == ',')) {
154 addValue(line.substring(startIndex, i));
155 startIndex = i + 1;
156 }
157 } else if (state == State.BACKSLASH) {
158 state = State.VALUE;
159 }
160 }
161 if (state == State.VALUE) {
162 addValue(line.substring(startIndex));
163 } else {
164 throw new IllegalArgumentException("Invalid property line: " + line);
165 }
166 }
167 }
168
169
170
171
172
173
174 public String getKey() {
175 return key;
176 }
177
178
179
180
181
182
183 public String getValue() {
184 if (values == null || values.isEmpty()) {
185 return null;
186 } else {
187 return values.get(0);
188 }
189 }
190
191
192
193
194
195
196 public List<String> getValues() {
197 return values;
198 }
199
200
201
202
203
204
205 public Map<String, String> getValuesAsMap() {
206 HashMap<String, String> valuesMap = new HashMap<>();
207
208 if (values != null) {
209 for (String value:values) {
210 if (value.contains("=")) {
211 int index = value.indexOf("=");
212 valuesMap.put(value.substring(0, index), value.substring(index+1));
213 }
214 }
215 }
216 return valuesMap;
217 }
218
219
220
221
222
223
224
225
226
227 public boolean hasParam(String paramName, String paramValue) {
228 return params != null && getParam(paramName) != null && containsIgnoreCase(getParam(paramName).values, paramValue);
229 }
230
231
232
233
234
235
236
237 public boolean hasParam(String paramName) {
238 return params != null && getParam(paramName) != null;
239 }
240
241
242
243
244
245
246 public void removeParam(String paramName) {
247 if (params != null) {
248 Param param = getParam(paramName);
249 if (param != null) {
250 params.remove(param);
251 }
252 }
253 }
254
255 protected boolean containsIgnoreCase(List<String> stringCollection, String value) {
256 for (String collectionValue : stringCollection) {
257 if (value.equalsIgnoreCase(collectionValue)) {
258 return true;
259 }
260 }
261 return false;
262 }
263
264
265
266
267
268
269
270
271 protected List<String> addParamValue(List<String> paramValues, String value) {
272 List<String> result = paramValues;
273 if (result == null) {
274 result = new ArrayList<>();
275 }
276 result.add(value);
277 return result;
278 }
279
280 protected void addParam(String paramName) {
281 addParam(paramName, (String) null);
282 }
283
284
285
286
287
288
289
290 public void setParam(String paramName, String paramValue) {
291 Param currentParam = getParam(paramName);
292 if (currentParam != null) {
293 params.remove(currentParam);
294 }
295 addParam(paramName, paramValue);
296 }
297
298
299
300
301
302
303
304 public void addParam(String paramName, String paramValue) {
305 if (paramValue != null) {
306 List<String> paramValues = new ArrayList<>();
307 paramValues.add(paramValue);
308 addParam(paramName, paramValues);
309 }
310 }
311
312 protected void addParam(String paramName, List<String> paramValues) {
313 if (params == null) {
314 params = new ArrayList<>();
315 }
316 Param currentParam = getParam(paramName);
317 if (currentParam == null) {
318 currentParam = new Param();
319 currentParam.name = paramName;
320 params.add(currentParam);
321 }
322 currentParam.addAll(paramValues);
323 }
324
325 protected Param getParam(String paramName) {
326 if (params != null && paramName != null) {
327 for (Param param : params) {
328 if (paramName.equals(param.name)) {
329 return param;
330 }
331 }
332 }
333 return null;
334 }
335
336
337
338
339
340
341 public String getParamValue(String paramName) {
342 Param param = getParam(paramName);
343 if (param != null) {
344 return param.getValue();
345 } else {
346 return null;
347 }
348 }
349
350 protected List<Param> getParams() {
351 return params;
352 }
353
354 protected void setParams(List<Param> params) {
355 this.params = params;
356 }
357
358 protected void setValue(String value) {
359 if (value == null) {
360 values = null;
361 } else {
362 if (values == null) {
363 values = new ArrayList<>();
364 } else {
365 values.clear();
366 }
367 values.add(decodeValue(value));
368 }
369 }
370
371 protected void addValue(String value) {
372 if (values == null) {
373 values = new ArrayList<>();
374 }
375 values.add(decodeValue(value));
376 }
377
378 public void removeValue(String value) {
379 if (values != null) {
380 int index = -1;
381 for (int i=0;i<values.size();i++) {
382 if (value.equals(values.get(i))) {
383 index = i;
384 }
385 }
386 if (index >= 0) {
387 values.remove(index);
388 }
389 }
390 }
391
392
393 protected String decodeValue(String value) {
394 if (value == null || (value.indexOf('\\') < 0 && value.indexOf(',') < 0)) {
395 return value;
396 } else {
397
398 StringBuilder decodedValue = new StringBuilder();
399 for (int i = 0; i < value.length(); i++) {
400 char c = value.charAt(i);
401 if (c == '\\') {
402
403 i++;
404 if (i == value.length()) {
405 break;
406 }
407 c = value.charAt(i);
408 if (c == 'n' || c == 'N') {
409 c = '\n';
410 } else if (c == 'r') {
411 c = '\r';
412 }
413 }
414
415 if (c == ',' &&
416
417 ("N".equals(key) ||
418
419 "NICKNAME".equals(key)
420 )) {
421
422 c = '\n';
423 }
424 decodedValue.append(c);
425 }
426 return decodedValue.toString();
427 }
428 }
429
430
431
432
433
434
435 public void setKey(String key) {
436 int dotIndex = key.indexOf('.');
437 if (dotIndex < 0) {
438 this.key = key;
439 } else {
440 this.key = key.substring(dotIndex + 1);
441 }
442 }
443
444 public String toString() {
445 StringBuilder buffer = new StringBuilder();
446 buffer.append(key);
447 if (params != null) {
448 for (Param param : params) {
449 buffer.append(';').append(param.name);
450 appendParamValues(buffer, param);
451 }
452 }
453 buffer.append(':');
454 if (values != null) {
455 boolean firstValue = true;
456 for (String value : values) {
457 if (firstValue) {
458 firstValue = false;
459 } else if (MULTIVALUED_PROPERTIES.contains(key)) {
460 buffer.append(',');
461 } else {
462 buffer.append(';');
463 }
464 appendMultilineEncodedValue(buffer, value);
465 }
466 }
467 return buffer.toString();
468 }
469
470 protected void appendParamValues(StringBuilder buffer, Param param) {
471 if (param.values != null) {
472 buffer.append('=');
473 boolean first = true;
474 for (String value : param.values) {
475 if (first) {
476 first = false;
477 } else {
478 buffer.append(',');
479 }
480
481 if ("CN".equalsIgnoreCase(param.name)
482
483 || value.indexOf(';') >= 0 || value.indexOf(',') >= 0
484 || value.indexOf('(') >= 0 || value.indexOf('/') >= 0
485 || value.indexOf(':') >= 0) {
486 buffer.append('"').append(value).append('"');
487 } else {
488 buffer.append(value);
489 }
490 }
491 }
492 }
493
494
495
496
497
498
499
500 protected void appendMultilineEncodedValue(StringBuilder buffer, String value) {
501 for (int i = 0; i < value.length(); i++) {
502 char c = value.charAt(i);
503 if (c == '\n') {
504 buffer.append("\\n");
505 } else if (MULTIVALUED_PROPERTIES.contains(key) && c == ',') {
506 buffer.append('\\').append(',');
507
508 } else if (c != '\r') {
509 buffer.append(value.charAt(i));
510 }
511 }
512 }
513
514 }
515