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