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 davmail.Settings;
22 import davmail.util.StringUtil;
23 import org.apache.log4j.Logger;
24
25 import java.io.*;
26 import java.nio.charset.StandardCharsets;
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29 import java.util.*;
30
31
32
33
34 public class VCalendar extends VObject {
35 protected static final Logger LOGGER = Logger.getLogger(VCalendar.class);
36 protected VObject firstVevent;
37 protected VObject vTimezone;
38 protected String email;
39
40
41
42
43
44
45
46
47
48 public VCalendar(BufferedReader reader, String email, VObject vTimezone) throws IOException {
49 super(reader);
50 if (!"VCALENDAR".equals(type)) {
51 throw new IOException("Invalid type: " + type);
52 }
53 this.email = email;
54
55 if (this.vTimezone == null && vTimezone != null) {
56 setTimezone(vTimezone);
57 }
58 }
59
60
61
62
63
64
65
66
67
68 public VCalendar(String vCalendarBody, String email, VObject vTimezone) throws IOException {
69 this(new ICSBufferedReader(new StringReader(vCalendarBody)), email, vTimezone);
70 }
71
72
73
74
75
76
77
78
79
80 public VCalendar(byte[] vCalendarContent, String email, VObject vTimezone) throws IOException {
81 this(new ICSBufferedReader(new InputStreamReader(new ByteArrayInputStream(vCalendarContent), StandardCharsets.UTF_8)), email, vTimezone);
82 }
83
84
85
86
87 public VCalendar() {
88 type = "VCALENDAR";
89 }
90
91
92
93
94
95
96 public void setTimezone(VObject vTimezone) {
97 if (vObjects == null) {
98 addVObject(vTimezone);
99 } else {
100 vObjects.add(0, vTimezone);
101 }
102 this.vTimezone = vTimezone;
103 }
104
105 @Override
106 public void addVObject(VObject vObject) {
107 if (firstVevent == null && ("VEVENT".equals(vObject.type) || "VTODO".equals(vObject.type))) {
108 firstVevent = vObject;
109 }
110 if ("VTIMEZONE".equals(vObject.type)) {
111 if (vTimezone == null) {
112 vTimezone = vObject;
113 } else if (vTimezone.getPropertyValue("TZID").equals(vObject.getPropertyValue("TZID"))){
114
115 vObject = null;
116 }
117 }
118 if (vObject != null) {
119 super.addVObject(vObject);
120 }
121 }
122
123 protected boolean isAllDay(VObject vObject) {
124 VProperty dtstart = vObject.getProperty("DTSTART");
125 return dtstart != null && dtstart.hasParam("VALUE", "DATE");
126 }
127
128 protected boolean isCdoAllDay(VObject vObject) {
129 return "TRUE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT"));
130 }
131
132
133
134
135
136
137 public boolean isCdoAllDay() {
138 return firstVevent != null && isCdoAllDay(firstVevent);
139 }
140
141
142
143
144
145
146
147 public String getEmailValue(VProperty property) {
148 if (property == null) {
149 return null;
150 }
151 String propertyValue = property.getValue();
152 if (propertyValue != null && (propertyValue.startsWith("MAILTO:") || propertyValue.startsWith("mailto:"))) {
153 return propertyValue.substring(7);
154 } else {
155 return propertyValue;
156 }
157 }
158
159 protected String getMethod() {
160 return getPropertyValue("METHOD");
161 }
162
163 protected void fixVCalendar(boolean fromServer) {
164
165 if (fromServer) {
166 setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess());
167 }
168
169 if (fromServer && "PUBLISH".equals(getPropertyValue("METHOD"))) {
170 removeProperty("METHOD");
171 }
172
173
174 String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS");
175 String now = ExchangeSession.getZuluDateFormat().format(new Date());
176
177
178 if (!fromServer && getPropertyValue("METHOD") == null) {
179 setPropertyValue("METHOD", "PUBLISH");
180 }
181
182
183 if (fromServer) {
184
185 VObject vObject = vTimezone;
186 if (vObject != null) {
187 String currentTzid = vObject.getPropertyValue("TZID");
188 vObject.setPropertyValue("TZID", fixupTZID(currentTzid));
189 }
190 }
191
192 if (!fromServer) {
193 fixTimezoneToServer();
194 }
195
196
197 for (VObject vObject : vObjects) {
198 if (vObject.isVEvent()) {
199 if (calendarServerAccess != null) {
200 vObject.setPropertyValue("CLASS", getEventClass(calendarServerAccess));
201
202 } else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null) {
203 vObject.setPropertyValue("CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS")));
204 }
205 if (fromServer) {
206
207 if (vObject.getProperty("ATTENDEE") == null) {
208 vObject.setPropertyValue("ORGANIZER", null);
209 }
210
211 if (isCdoAllDay(vObject)) {
212 setClientAllday(vObject.getProperty("DTSTART"));
213 setClientAllday(vObject.getProperty("DTEND"));
214 setClientAllday(vObject.getProperty("RECURRENCE-ID"));
215 }
216 String cdoBusyStatus = vObject.getPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS");
217 if (cdoBusyStatus != null) {
218
219 if ("TENTATIVE".equals(cdoBusyStatus)) {
220 vObject.setPropertyValue("STATUS", "TENTATIVE");
221 }
222
223 vObject.setPropertyValue("TRANSP",
224 !"FREE".equals(cdoBusyStatus) ? "OPAQUE" : "TRANSPARENT");
225 }
226
227
228
229 vObject.removeProperty("X-ENTOURAGE_UUID");
230
231 splitExDate(vObject);
232
233
234 if ("".equals(vObject.getPropertyValue("LOCATION"))) {
235 vObject.removeProperty("LOCATION");
236 }
237 if ("".equals(vObject.getPropertyValue("DESCRIPTION"))) {
238 vObject.removeProperty("DESCRIPTION");
239 }
240 if ("".equals(vObject.getPropertyValue("CLASS"))) {
241 vObject.removeProperty("CLASS");
242 }
243
244 VProperty dtStart = vObject.getProperty("DTSTART");
245 if (dtStart != null && dtStart.getParam("TZID") != null) {
246 dtStart.setParam("TZID", fixupTZID(dtStart.getParamValue("TZID")));
247 }
248 VProperty dtEnd = vObject.getProperty("DTEND");
249 if (dtEnd != null && dtEnd.getParam("TZID") != null) {
250 dtEnd.setParam("TZID", fixupTZID(dtEnd.getParamValue("TZID")));
251 }
252 VProperty recurrenceId = vObject.getProperty("RECURRENCE-ID");
253 if (recurrenceId != null && recurrenceId.getParam("TZID") != null) {
254 recurrenceId.setParam("TZID", fixupTZID(recurrenceId.getParamValue("TZID")));
255 }
256 VProperty exDate = vObject.getProperty("EXDATE");
257 if (exDate != null && exDate.getParam("TZID") != null) {
258 exDate.setParam("TZID", fixupTZID(exDate.getParamValue("TZID")));
259 }
260
261 if (vObject.getProperty("ATTACH") != null) {
262 List<String> toRemoveValues = null;
263 List<String> values = vObject.getProperty("ATTACH").getValues();
264 for (String value : values) {
265 if (value.contains("CID:")) {
266 if (toRemoveValues == null) {
267 toRemoveValues = new ArrayList<>();
268 }
269 toRemoveValues.add(value);
270 }
271 }
272 if (toRemoveValues != null) {
273 values.removeAll(toRemoveValues);
274 if (values.isEmpty()) {
275 vObject.removeProperty("ATTACH");
276 }
277 }
278 }
279 } else {
280
281 String organizer = getEmailValue(vObject.getProperty("ORGANIZER"));
282 if (organizer == null) {
283 vObject.setPropertyValue("ORGANIZER", "MAILTO:" + email);
284 } else if (!email.equalsIgnoreCase(organizer) && vObject.getProperty("X-MICROSOFT-CDO-REPLYTIME") == null) {
285 vObject.setPropertyValue("X-MICROSOFT-CDO-REPLYTIME", now);
286 }
287
288 vObject.setPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT", isAllDay(vObject) ? "TRUE" : "FALSE");
289 if (vObject.getPropertyValue("TRANSP") != null) {
290 vObject.setPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS",
291 !"TRANSPARENT".equals(vObject.getPropertyValue("TRANSP")) ? "BUSY" : "FREE");
292 }
293
294 if (isAllDay(vObject)) {
295
296 setServerAllday(vObject.getProperty("DTSTART"));
297 setServerAllday(vObject.getProperty("DTEND"));
298 } else {
299 fixTzid(vObject.getProperty("DTSTART"));
300 fixTzid(vObject.getProperty("DTEND"));
301 }
302 }
303
304 fixAttendees(vObject, fromServer);
305
306 fixAlarm(vObject, fromServer);
307 }
308 }
309
310 }
311
312 private String fixupTZID(String currentTzid) {
313
314 if (currentTzid != null && currentTzid.endsWith("\n")) {
315 currentTzid = currentTzid.substring(0, currentTzid.length() - 1);
316 }
317 if (currentTzid != null && currentTzid.indexOf(' ') >= 0) {
318 try {
319 currentTzid = ResourceBundle.getBundle("timezones").getString(currentTzid);
320 } catch (MissingResourceException e) {
321 LOGGER.debug("Timezone " + currentTzid + " not found in rename table");
322 }
323 }
324 return currentTzid;
325 }
326
327 private void fixTimezoneToServer() {
328 if (vTimezone != null && vTimezone.vObjects != null && vTimezone.vObjects.size() > 2) {
329 VObject standard = null;
330 VObject daylight = null;
331 for (VObject vObject : vTimezone.vObjects) {
332 if ("STANDARD".equals(vObject.type)) {
333 if (standard == null ||
334 (vObject.getPropertyValue("DTSTART").compareTo(standard.getPropertyValue("DTSTART")) > 0)) {
335 standard = vObject;
336 }
337 }
338 if ("DAYLIGHT".equals(vObject.type)) {
339 if (daylight == null ||
340 (vObject.getPropertyValue("DTSTART").compareTo(daylight.getPropertyValue("DTSTART")) > 0)) {
341 daylight = vObject;
342 }
343 }
344 }
345 vTimezone.vObjects.clear();
346 vTimezone.vObjects.add(standard);
347 vTimezone.vObjects.add(daylight);
348 }
349
350 if (vTimezone != null && vTimezone.vObjects != null) {
351 for (VObject vObject : vTimezone.vObjects) {
352 VProperty rrule = vObject.getProperty("RRULE");
353 if (rrule != null && rrule.getValues().size() == 3 && "BYDAY=-2SU".equals(rrule.getValues().get(1))) {
354 rrule.getValues().set(1, "BYDAY=4SU");
355 }
356
357 if (rrule != null && rrule.getValues().size() == 4 && "BYDAY=FR".equals(rrule.getValues().get(1))
358 && "BYMONTHDAY=23,24,25,26,27,28,29".equals(rrule.getValues().get(2))) {
359 rrule.getValues().set(1, "BYDAY=-1FR");
360 rrule.getValues().remove(2);
361 }
362 }
363 }
364
365
366 if (vTimezone != null && vTimezone.vObjects != null) {
367 for (VObject vObject : vTimezone.vObjects) {
368 VProperty rrule = vObject.getProperty("RRULE");
369 if (rrule != null) {
370 Map<String, String> rruleValueMap = rrule.getValuesAsMap();
371 if (rruleValueMap.containsKey("UNTIL") && rruleValueMap.containsKey("COUNT")) {
372 rrule.removeValue("UNTIL="+rruleValueMap.get("UNTIL"));
373 }
374 }
375 }
376 }
377
378
379
380 ResourceBundle tzBundle = ResourceBundle.getBundle("exchtimezones");
381 ResourceBundle tzidsBundle = ResourceBundle.getBundle("stdtimezones");
382 for (VObject vObject : vObjects) {
383 if (vObject.isVTimezone()) {
384 String tzid = vObject.getPropertyValue("TZID");
385
386 if (!tzidsBundle.containsKey(tzid)) {
387 String exchangeTzid = null;
388
389 if (tzBundle.containsKey(tzid)) {
390 exchangeTzid = tzBundle.getString(tzid);
391 } else {
392
393 for (VObject tzDefinition : vObject.vObjects) {
394 if ("STANDARD".equals(tzDefinition.type)) {
395 exchangeTzid = getTzidFromOffset(tzDefinition.getPropertyValue("TZOFFSETTO"));
396 }
397 }
398 }
399 if (exchangeTzid != null) {
400 vObject.setPropertyValue("TZID", exchangeTzid);
401
402 updateTzid(tzid, exchangeTzid);
403 }
404 }
405 }
406 }
407 }
408
409 protected void updateTzid(String tzid, String newTzid) {
410 for (VObject vObject : vObjects) {
411 if (vObject.isVEvent() || vObject.isVTodo()) {
412 for (VProperty vProperty : vObject.properties) {
413 if (tzid.equalsIgnoreCase(vProperty.getParamValue("TZID"))) {
414 vProperty.setParam("TZID", newTzid);
415 }
416 }
417 }
418 }
419 }
420
421 private void fixTzid(VProperty property) {
422 if (property != null && !property.hasParam("TZID")) {
423 property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
424 }
425 }
426
427 protected void splitExDate(VObject vObject) {
428 List<VProperty> exDateProperties = vObject.getProperties("EXDATE");
429 if (exDateProperties != null) {
430 for (VProperty property : exDateProperties) {
431 String value = property.getValue();
432 if (value.indexOf(',') >= 0) {
433
434 vObject.removeProperty(property);
435 for (String singleValue : value.split(",")) {
436 VProperty singleProperty = new VProperty("EXDATE", singleValue);
437 singleProperty.setParams(property.getParams());
438 vObject.addProperty(singleProperty);
439 }
440 }
441 }
442 }
443 }
444
445 protected void setServerAllday(VProperty property) {
446 if (vTimezone != null) {
447
448 if (!property.hasParam("TZID")) {
449 property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
450 }
451
452 property.removeParam("VALUE");
453 String value = property.getValue();
454 if (value.length() != 8) {
455 LOGGER.warn("Invalid date value in allday event: " + value);
456 }
457 property.setValue(property.getValue() + "T000000");
458 }
459 }
460
461 protected void setClientAllday(VProperty property) {
462 if (property != null) {
463
464 if (!property.hasParam("VALUE")) {
465 property.addParam("VALUE", "DATE");
466 }
467
468 property.removeParam("TZID");
469 String value = property.getValue();
470 if (value.length() != 8) {
471
472 try {
473 Calendar calendar = Calendar.getInstance();
474 SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
475 calendar.setTime(dateParser.parse(value));
476 calendar.add(Calendar.HOUR_OF_DAY, 12);
477 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
478 value = dateFormatter.format(calendar.getTime());
479 } catch (ParseException e) {
480 LOGGER.warn("Invalid date value in allday event: " + value);
481 }
482 }
483 property.setValue(value);
484 }
485 }
486
487 protected void fixAlarm(VObject vObject, boolean fromServer) {
488 if (vObject.vObjects != null) {
489 if (Settings.getBooleanProperty("davmail.caldavDisableReminders", false)) {
490 ArrayList<VObject> vAlarms = null;
491 for (VObject vAlarm : vObject.vObjects) {
492 if ("VALARM".equals(vAlarm.type)) {
493 if (vAlarms == null) {
494 vAlarms = new ArrayList<>();
495 }
496 vAlarms.add(vAlarm);
497 }
498 }
499
500 if (vAlarms != null) {
501 for (VObject vAlarm : vAlarms) {
502 vObject.vObjects.remove(vAlarm);
503 }
504 }
505
506 } else {
507 for (VObject vAlarm : vObject.vObjects) {
508 if ("VALARM".equals(vAlarm.type)) {
509 String action = vAlarm.getPropertyValue("ACTION");
510 if (fromServer && "DISPLAY".equals(action)
511
512 && Settings.getProperty("davmail.caldavAlarmSound") != null) {
513
514 vAlarm.setPropertyValue("ACTION", "AUDIO");
515
516 if (vAlarm.getPropertyValue("ATTACH") == null) {
517
518 VProperty vProperty = new VProperty("ATTACH", Settings.getProperty("davmail.caldavAlarmSound"));
519 vProperty.addParam("VALUE", "URI");
520 vAlarm.addProperty(vProperty);
521 }
522
523 } else if (!fromServer && "AUDIO".equals(action)) {
524
525
526 vAlarm.setPropertyValue("ACTION", "DISPLAY");
527 }
528 }
529 }
530 }
531 }
532 }
533
534
535
536
537
538
539
540 protected String replaceIcal4Principal(String value) {
541 final String principalPrefix = "/principals/__uuids__/";
542 final String principalAt = "__AT__";
543 if (value.contains(principalPrefix) && value.contains(principalAt)) {
544 return "mailto:" +
545 value.substring(value.indexOf(principalPrefix) + principalPrefix.length(), value.indexOf(principalAt)) +
546 "@" +
547 value.substring(value.indexOf(principalAt) + principalAt.length(), value.length() - 1);
548 } else {
549 return value;
550 }
551 }
552
553 private void fixAttendees(VObject vObject, boolean fromServer) {
554 if (vObject.properties != null) {
555 for (VProperty property : vObject.properties) {
556 if ("ATTENDEE".equalsIgnoreCase(property.getKey())) {
557 if (fromServer) {
558
559
560
561
562 if (isCurrentUser(property) && property.hasParam("RSVP", "TRUE")) {
563 if (!"NEEDS-ACTION".equals(property.getParamValue("PARTSTAT"))) {
564 property.removeParam("RSVP");
565 }
566 }
567 } else {
568 property.setValue(replaceIcal4Principal(property.getValue()));
569 }
570 }
571
572 }
573 }
574
575 }
576
577 private boolean isCurrentUser(VProperty property) {
578 return property.getValue().equalsIgnoreCase("mailto:" + email);
579 }
580
581
582
583
584
585
586 public VObject getVTimezone() {
587 return vTimezone;
588 }
589
590
591
592
593
594
595
596
597 protected String getEventClass(String calendarServerAccess) {
598 if ("PRIVATE".equalsIgnoreCase(calendarServerAccess)) {
599 return "CONFIDENTIAL";
600 } else if ("CONFIDENTIAL".equalsIgnoreCase(calendarServerAccess) || "RESTRICTED".equalsIgnoreCase(calendarServerAccess)) {
601 return "PRIVATE";
602 } else {
603 return null;
604 }
605 }
606
607
608
609
610
611
612
613 protected String getCalendarServerAccess() {
614 String eventClass = getFirstVeventPropertyValue("CLASS");
615 if ("PRIVATE".equalsIgnoreCase(eventClass)) {
616 return "CONFIDENTIAL";
617 } else if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
618 return "PRIVATE";
619 } else {
620 return null;
621 }
622 }
623
624
625
626
627
628
629
630 public String getFirstVeventPropertyValue(String name) {
631 if (firstVevent == null) {
632 return null;
633 } else {
634 return firstVevent.getPropertyValue(name);
635 }
636 }
637
638 protected VProperty getFirstVeventProperty(String name) {
639 if (firstVevent == null) {
640 return null;
641 } else {
642 return firstVevent.getProperty(name);
643 }
644 }
645
646
647
648
649
650
651
652
653 public List<VProperty> getFirstVeventProperties(String name) {
654 if (firstVevent == null) {
655 return null;
656 } else {
657 return firstVevent.getProperties(name);
658 }
659 }
660
661
662
663
664 public void removeVAlarm() {
665 if (vObjects != null) {
666 for (VObject vObject : vObjects) {
667 if ("VEVENT".equals(vObject.type)) {
668
669 if (vObject.vObjects != null) {
670 vObject.vObjects = null;
671 }
672 }
673 }
674 }
675 }
676
677
678
679
680
681
682 public boolean hasVAlarm() {
683 if (vObjects != null) {
684 for (VObject vObject : vObjects) {
685 if ("VEVENT".equals(vObject.type)) {
686 if (vObject.vObjects != null && !vObject.vObjects.isEmpty()) {
687 return vObject.vObjects.get(0).isVAlarm();
688 }
689 }
690 }
691 }
692 return false;
693 }
694
695 public String getReminderMinutesBeforeStart() {
696 String result = "0";
697 if (vObjects != null) {
698 for (VObject vObject : vObjects) {
699 if (vObject.vObjects != null && !vObject.vObjects.isEmpty() &&
700 vObject.vObjects.get(0).isVAlarm()) {
701 String trigger = vObject.vObjects.get(0).getPropertyValue("TRIGGER");
702 if (trigger != null) {
703 if (trigger.startsWith("-PT") && trigger.endsWith("M")) {
704 result = trigger.substring(3, trigger.length() - 1);
705 } else if (trigger.startsWith("-PT") && trigger.endsWith("H")) {
706 result = trigger.substring(3, trigger.length() - 1);
707
708 result = String.valueOf(Integer.parseInt(result) * 60);
709 } else if (trigger.startsWith("-P") && trigger.endsWith("D")) {
710 result = trigger.substring(2, trigger.length() - 1);
711
712 result = String.valueOf(Integer.parseInt(result) * 60 * 24);
713 } else if (trigger.startsWith("-P") && trigger.endsWith("W")) {
714 result = trigger.substring(2, trigger.length() - 1);
715
716 result = String.valueOf(Integer.parseInt(result) * 60 * 24 * 7);
717 }
718 }
719 }
720 }
721 }
722 return result;
723 }
724
725
726
727
728
729
730
731 public boolean isMeeting() {
732 return getFirstVeventProperty("ATTENDEE") != null;
733 }
734
735
736
737
738
739
740 public boolean isMeetingOrganizer() {
741 return email.equalsIgnoreCase(getEmailValue(getFirstVeventProperty("ORGANIZER")));
742 }
743
744
745
746
747
748
749
750 public void setFirstVeventPropertyValue(String propertyName, String propertyValue) {
751 firstVevent.setPropertyValue(propertyName, propertyValue);
752 }
753
754
755
756
757
758
759 public void addFirstVeventProperty(VProperty vProperty) {
760 firstVevent.addProperty(vProperty);
761 }
762
763
764
765
766
767
768 public boolean isTodo() {
769 return firstVevent != null && "VTODO".equals(firstVevent.type);
770 }
771
772
773
774
775
776 public String getCalendarEmail() {
777 return email;
778 }
779
780 public void setEmail(String email) {
781 this.email = email;
782 }
783
784
785
786
787 public static class Recipients {
788
789
790
791 public String attendees;
792
793
794
795
796 public String optionalAttendees;
797
798
799
800
801 public String organizer;
802 }
803
804
805
806
807
808
809
810 public Recipients getRecipients(boolean isNotification) {
811
812 HashSet<String> attendees = new HashSet<>();
813 HashSet<String> optionalAttendees = new HashSet<>();
814
815
816 List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE");
817 if (attendeeProperties != null) {
818 for (VProperty property : attendeeProperties) {
819
820
821 String attendeeEmail = getEmailValue(property);
822 if (!email.equalsIgnoreCase(attendeeEmail) && attendeeEmail != null && attendeeEmail.indexOf('@') >= 0
823
824 && (!isNotification
825
826 || (property.hasParam("RSVP", "TRUE"))
827 || (
828
829 !(property.hasParam("RSVP", "FALSE")) &&
830 ((property.hasParam("PARTSTAT", "NEEDS-ACTION")
831
832 || property.hasParam("PARTSTAT", "ACCEPTED")
833 || property.hasParam("PARTSTAT", "DECLINED")
834 || property.hasParam("PARTSTAT", "TENTATIVE")))
835 ))) {
836 if (property.hasParam("ROLE", "OPT-PARTICIPANT")) {
837 optionalAttendees.add(attendeeEmail);
838 } else {
839 attendees.add(attendeeEmail);
840 }
841 }
842 }
843 }
844 Recipients recipients = new Recipients();
845 recipients.organizer = getEmailValue(getFirstVeventProperty("ORGANIZER"));
846 recipients.attendees = StringUtil.join(attendees, ", ");
847 recipients.optionalAttendees = StringUtil.join(optionalAttendees, ", ");
848 return recipients;
849 }
850
851 public String getAttendeeStatus() {
852 String attendeeStatus = null;
853
854 for (VObject vObject : vObjects) {
855 if ("VEVENT".equals(vObject.type)) {
856 List<VProperty> attendeeProperties = vObject.getProperties("ATTENDEE");
857 if (attendeeProperties != null) {
858 for (VProperty property : attendeeProperties) {
859 if (email.equalsIgnoreCase(getEmailValue(property))) {
860 String status = property.getParamValue("PARTSTAT");
861 if (!"NEEDS-ACTION".equals(status)) {
862 attendeeStatus = status;
863 }
864 }
865 }
866 }
867 }
868 }
869 return attendeeStatus;
870 }
871
872
873
874
875
876
877 public VObject getFirstVevent() {
878 return firstVevent;
879 }
880
881
882
883
884
885
886 public List<VObject> getModifiedOccurrences() {
887 boolean first = true;
888 ArrayList<VObject> results = new ArrayList<>();
889 for (VObject vObject : vObjects) {
890 if ("VEVENT".equals(vObject.type)) {
891 if (first) {
892 first = false;
893 } else {
894 results.add(vObject);
895 }
896 }
897 }
898 return results;
899 }
900
901 public TimeZone getStandardTimezoneId(String tzid) {
902 String convertedTzid;
903
904 try {
905 convertedTzid = ResourceBundle.getBundle("timezones").getString(tzid);
906 } catch (MissingResourceException e) {
907 convertedTzid = tzid;
908
909 VObject vTimezone = getVTimezone();
910 for (VObject tzDefinition : vTimezone.vObjects) {
911 if ("STANDARD".equals(tzDefinition.type)) {
912 convertedTzid = getTzidFromOffset(tzDefinition.getPropertyValue("TZOFFSETTO"));
913 }
914 }
915 convertedTzid = ResourceBundle.getBundle("timezones").getString(convertedTzid);
916 }
917 return TimeZone.getTimeZone(convertedTzid);
918
919 }
920
921 private String getTzidFromOffset(String tzOffset) {
922 if (tzOffset == null) {
923 return null;
924 } else if (tzOffset.length() == 7) {
925 tzOffset = tzOffset.substring(0, 5);
926 }
927 return ResourceBundle.getBundle("tzoffsettimezones").getString(tzOffset);
928 }
929
930 public String convertCalendarDateToExchangeZulu(String vcalendarDateValue, String tzid) throws IOException {
931 String zuluDateValue = null;
932 TimeZone timeZone;
933 if (tzid == null) {
934 timeZone = ExchangeSession.GMT_TIMEZONE;
935 } else {
936 timeZone = getStandardTimezoneId(tzid);
937 }
938 if (vcalendarDateValue != null) {
939 try {
940 SimpleDateFormat dateParser;
941 if (vcalendarDateValue.length() == 8) {
942 dateParser = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
943 } else {
944 dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
945 }
946 if (vcalendarDateValue.endsWith("Z")) {
947
948 dateParser.setTimeZone(TimeZone.getTimeZone("UTC"));
949 } else {
950 dateParser.setTimeZone(timeZone);
951 }
952 dateParser.setTimeZone(timeZone);
953 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
954 dateFormatter.setTimeZone(ExchangeSession.GMT_TIMEZONE);
955 zuluDateValue = dateFormatter.format(dateParser.parse(vcalendarDateValue));
956 } catch (ParseException e) {
957 throw new IOException("Invalid date " + vcalendarDateValue + " with tzid " + tzid);
958 }
959 }
960 return zuluDateValue;
961 }
962
963
964
965
966
967
968
969
970 public String convertCalendarDateToGraph(String vcalendarDateValue, String tzid) throws IOException {
971 String graphDateValue = null;
972 TimeZone timeZone;
973 if (tzid == null) {
974 timeZone = ExchangeSession.GMT_TIMEZONE;
975 } else {
976 timeZone = getStandardTimezoneId(tzid);
977 }
978 if (vcalendarDateValue != null) {
979 try {
980 SimpleDateFormat dateParser;
981 if (vcalendarDateValue.length() == 8) {
982 dateParser = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
983 } else {
984 dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
985 }
986 dateParser.setTimeZone(timeZone);
987 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
988 dateFormatter.setTimeZone(timeZone);
989 graphDateValue = dateFormatter.format(dateParser.parse(vcalendarDateValue));
990 } catch (ParseException e) {
991 throw new IOException("Invalid date " + vcalendarDateValue + " with tzid " + tzid);
992 }
993 }
994 return graphDateValue;
995 }
996
997 }