1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.exchange.ews;
20
21 import davmail.BundleMessage;
22 import davmail.Settings;
23 import davmail.exchange.XMLStreamUtil;
24 import davmail.http.HttpClientAdapter;
25 import davmail.ui.tray.DavGatewayTray;
26 import davmail.util.StringUtil;
27 import org.apache.commons.codec.binary.Base64;
28 import org.apache.http.HttpResponse;
29 import org.apache.http.HttpStatus;
30 import org.apache.http.client.ResponseHandler;
31 import org.apache.http.client.methods.HttpPost;
32 import org.apache.http.entity.AbstractHttpEntity;
33 import org.apache.http.entity.ContentType;
34 import org.apache.log4j.Level;
35 import org.apache.log4j.Logger;
36 import org.codehaus.stax2.typed.TypedXMLStreamReader;
37
38 import javax.xml.stream.XMLStreamConstants;
39 import javax.xml.stream.XMLStreamException;
40 import javax.xml.stream.XMLStreamReader;
41 import java.io.ByteArrayInputStream;
42 import java.io.ByteArrayOutputStream;
43 import java.io.FilterInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 import java.io.OutputStreamWriter;
48 import java.io.Writer;
49 import java.net.URI;
50 import java.nio.charset.StandardCharsets;
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Set;
56 import java.util.zip.GZIPInputStream;
57
58
59
60
61 public abstract class EWSMethod extends HttpPost implements ResponseHandler<EWSMethod> {
62 protected static final String CONTENT_TYPE = ContentType.create("text/xml", StandardCharsets.UTF_8).toString();
63 protected static final Logger LOGGER = Logger.getLogger(EWSMethod.class);
64 protected static final int CHUNK_LENGTH = 131072;
65
66 protected FolderQueryTraversal traversal;
67 protected BaseShape baseShape;
68 protected boolean includeMimeContent;
69 protected FolderId folderId;
70 protected FolderId savedItemFolderId;
71 protected FolderId toFolderId;
72 protected FolderId parentFolderId;
73 protected ItemId itemId;
74 protected List<ItemId> itemIds;
75 protected ItemId parentItemId;
76 protected Set<FieldURI> additionalProperties;
77 protected Disposal deleteType;
78 protected Set<AttributeOption> methodOptions;
79 protected ElementOption unresolvedEntry;
80
81
82 protected int maxCount;
83 protected int offset;
84
85 protected boolean includesLastItemInRange;
86
87 protected List<FieldUpdate> updates;
88
89 protected FileAttachment attachment;
90
91 protected String attachmentId;
92
93 protected final String itemType;
94 protected final String methodName;
95 protected final String responseCollectionName;
96
97 protected List<Item> responseItems;
98 protected String errorDetail;
99 protected String errorDescription;
100 protected String errorValue;
101 protected long backOffMilliseconds;
102 protected Item item;
103
104 protected SearchExpression searchExpression;
105 protected FieldOrder fieldOrder;
106
107 protected String serverVersion;
108 protected String timezoneContext;
109 private HttpResponse response;
110
111
112
113
114
115
116
117 public EWSMethod(String itemType, String methodName) {
118 this(itemType, methodName, itemType + 's');
119 }
120
121
122
123
124
125
126
127
128 public EWSMethod(String itemType, String methodName, String responseCollectionName) {
129 super(URI.create("/ews/exchange.asmx"));
130 this.itemType = itemType;
131 this.methodName = methodName;
132 this.responseCollectionName = responseCollectionName;
133 if (Settings.getBooleanProperty("davmail.acceptEncodingGzip", true) &&
134 !Level.DEBUG.toString().equals(Settings.getProperty("log4j.logger.httpclient.wire"))) {
135 setHeader("Accept-Encoding", "gzip");
136 }
137
138 AbstractHttpEntity httpEntity = new AbstractHttpEntity() {
139 byte[] content;
140
141 @Override
142 public boolean isRepeatable() {
143 return true;
144 }
145
146 @Override
147 public long getContentLength() {
148 if (content == null) {
149 content = generateSoapEnvelope();
150 }
151 return content.length;
152 }
153
154 @Override
155 public InputStream getContent() throws UnsupportedOperationException {
156 if (content == null) {
157 content = generateSoapEnvelope();
158 }
159 return new ByteArrayInputStream(content);
160 }
161
162 @Override
163 public void writeTo(OutputStream outputStream) throws IOException {
164 boolean firstPass = content == null;
165 if (content == null) {
166 content = generateSoapEnvelope();
167 }
168 if (content.length < CHUNK_LENGTH) {
169 outputStream.write(content);
170 } else {
171 int i = 0;
172 while (i < content.length) {
173 int length = CHUNK_LENGTH;
174 if (i + CHUNK_LENGTH > content.length) {
175 length = content.length - i;
176 }
177 outputStream.write(content, i, length);
178 if (!firstPass) {
179 DavGatewayTray.debug(new BundleMessage("LOG_UPLOAD_PROGRESS", String.valueOf((i + length) / 1024), (i + length) * 100 / content.length));
180 DavGatewayTray.switchIcon();
181 }
182 i += CHUNK_LENGTH;
183 }
184 }
185 }
186
187 @Override
188 public boolean isStreaming() {
189 return false;
190 }
191 };
192
193 httpEntity.setContentType(CONTENT_TYPE);
194 setEntity(httpEntity);
195 }
196
197 protected void addAdditionalProperty(FieldURI additionalProperty) {
198 if (additionalProperties == null) {
199 additionalProperties = new HashSet<>();
200 }
201 additionalProperties.add(additionalProperty);
202 }
203
204 protected void addMethodOption(AttributeOption attributeOption) {
205 if (methodOptions == null) {
206 methodOptions = new HashSet<>();
207 }
208 methodOptions.add(attributeOption);
209 }
210
211 protected void setSearchExpression(SearchExpression searchExpression) {
212 this.searchExpression = searchExpression;
213 }
214
215 protected void setFieldOrder(FieldOrder fieldOrder) {
216 this.fieldOrder = fieldOrder;
217 }
218
219 protected void writeShape(Writer writer) throws IOException {
220 if (baseShape != null) {
221 writer.write("<m:");
222 writer.write(itemType);
223 writer.write("Shape>");
224 baseShape.write(writer);
225 if (includeMimeContent) {
226 writer.write("<t:IncludeMimeContent>true</t:IncludeMimeContent>");
227 }
228 if (additionalProperties != null) {
229 writer.write("<t:AdditionalProperties>");
230 StringBuilder buffer = new StringBuilder();
231 for (FieldURI fieldURI : additionalProperties) {
232 fieldURI.appendTo(buffer);
233 }
234 writer.write(buffer.toString());
235 writer.write("</t:AdditionalProperties>");
236 }
237 writer.write("</m:");
238 writer.write(itemType);
239 writer.write("Shape>");
240 }
241 }
242
243 protected void writeItemId(Writer writer) throws IOException {
244 if (itemId != null || itemIds != null) {
245 if (updates == null) {
246 writer.write("<m:ItemIds>");
247 }
248 if (itemId != null) {
249 itemId.write(writer);
250 }
251 if (itemIds != null) {
252 for (ItemId localItemId : itemIds) {
253 localItemId.write(writer);
254 }
255 }
256 if (updates == null) {
257 writer.write("</m:ItemIds>");
258 }
259 }
260 }
261
262 protected void writeParentItemId(Writer writer) throws IOException {
263 if (parentItemId != null) {
264 writer.write("<m:ParentItemId Id=\"");
265 writer.write(parentItemId.id);
266 if (parentItemId.changeKey != null) {
267 writer.write("\" ChangeKey=\"");
268 writer.write(parentItemId.changeKey);
269 }
270 writer.write("\"/>");
271 }
272 }
273
274 protected void writeFolderId(Writer writer) throws IOException {
275 if (folderId != null) {
276 if (updates == null) {
277 writer.write("<m:FolderIds>");
278 }
279 folderId.write(writer);
280 if (updates == null) {
281 writer.write("</m:FolderIds>");
282 }
283 }
284 }
285
286 protected void writeSavedItemFolderId(Writer writer) throws IOException {
287 if (savedItemFolderId != null) {
288 writer.write("<m:SavedItemFolderId>");
289 savedItemFolderId.write(writer);
290 writer.write("</m:SavedItemFolderId>");
291 }
292 }
293
294 protected void writeToFolderId(Writer writer) throws IOException {
295 if (toFolderId != null) {
296 writer.write("<m:ToFolderId>");
297 toFolderId.write(writer);
298 writer.write("</m:ToFolderId>");
299 }
300 }
301
302 protected void writeParentFolderId(Writer writer) throws IOException {
303 if (parentFolderId != null) {
304 writer.write("<m:ParentFolderId");
305 if (item == null) {
306 writer.write("s");
307 }
308 writer.write(">");
309 parentFolderId.write(writer);
310 writer.write("</m:ParentFolderId");
311 if (item == null) {
312 writer.write("s");
313 }
314 writer.write(">");
315 }
316 }
317
318 protected void writeItem(Writer writer) throws IOException {
319 if (item != null) {
320 writer.write("<m:");
321 writer.write(itemType);
322 writer.write("s>");
323 item.write(writer);
324 writer.write("</m:");
325 writer.write(itemType);
326 writer.write("s>");
327 }
328 }
329
330 protected void writeRestriction(Writer writer) throws IOException {
331 if (searchExpression != null) {
332 writer.write("<m:Restriction>");
333 StringBuilder buffer = new StringBuilder();
334 searchExpression.appendTo(buffer);
335 writer.write(buffer.toString());
336 writer.write("</m:Restriction>");
337 }
338 }
339
340 protected void writeSortOrder(Writer writer) throws IOException {
341 if (fieldOrder != null) {
342 writer.write("<m:SortOrder>");
343 StringBuilder buffer = new StringBuilder();
344 fieldOrder.appendTo(buffer);
345 writer.write(buffer.toString());
346 writer.write("</m:SortOrder>");
347 }
348 }
349
350 protected void startChanges(Writer writer) throws IOException {
351
352 if (updates != null) {
353 writer.write("<m:");
354 writer.write(itemType);
355 writer.write("Changes>");
356 writer.write("<t:");
357 writer.write(itemType);
358 writer.write("Change>");
359 }
360 }
361
362 protected void writeUpdates(Writer writer) throws IOException {
363 if (updates != null) {
364 writer.write("<t:Updates>");
365 for (FieldUpdate fieldUpdate : updates) {
366 fieldUpdate.write(itemType, writer);
367 }
368 writer.write("</t:Updates>");
369 }
370 }
371
372 protected void writeUnresolvedEntry(Writer writer) throws IOException {
373 if (unresolvedEntry != null) {
374 unresolvedEntry.write(writer);
375 }
376 }
377
378 protected void endChanges(Writer writer) throws IOException {
379
380 if (updates != null) {
381 writer.write("</t:");
382 writer.write(itemType);
383 writer.write("Change>");
384 writer.write("</m:");
385 writer.write(itemType);
386 writer.write("Changes>");
387 }
388 }
389
390 protected byte[] generateSoapEnvelope() {
391 ByteArrayOutputStream baos = new ByteArrayOutputStream();
392 try {
393 OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8);
394 writer.write("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
395 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" " +
396 "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\">");
397 writer.write("<soap:Header>");
398 if (serverVersion != null) {
399 writer.write("<t:RequestServerVersion Version=\"");
400 writer.write(serverVersion);
401 writer.write("\"/>");
402 }
403 if (timezoneContext != null) {
404 writer.write("<t:TimeZoneContext><t:TimeZoneDefinition Id=\"");
405 writer.write(timezoneContext);
406 writer.write("\"/></t:TimeZoneContext>");
407 }
408 writer.write("</soap:Header>");
409
410 writer.write("<soap:Body>");
411 writer.write("<m:");
412 writer.write(methodName);
413 if (traversal != null) {
414 traversal.write(writer);
415 }
416 if (deleteType != null) {
417 deleteType.write(writer);
418 }
419 if (methodOptions != null) {
420 for (AttributeOption attributeOption : methodOptions) {
421 attributeOption.write(writer);
422 }
423 }
424 writer.write(">");
425 writeSoapBody(writer);
426 writer.write("</m:");
427 writer.write(methodName);
428 writer.write(">");
429 writer.write("</soap:Body>" +
430 "</soap:Envelope>");
431 writer.flush();
432 } catch (IOException e) {
433 throw new RuntimeException(e);
434 }
435 return baos.toByteArray();
436 }
437
438 protected void writeSoapBody(Writer writer) throws IOException {
439 startChanges(writer);
440 writeShape(writer);
441 writeIndexedPageView(writer);
442 writeRestriction(writer);
443 writeSortOrder(writer);
444 writeParentFolderId(writer);
445 writeToFolderId(writer);
446 writeItemId(writer);
447 writeParentItemId(writer);
448 writeAttachments(writer);
449 writeAttachmentId(writer);
450 writeFolderId(writer);
451 writeSavedItemFolderId(writer);
452 writeItem(writer);
453 writeUpdates(writer);
454 writeUnresolvedEntry(writer);
455 endChanges(writer);
456 }
457
458
459 protected void writeIndexedPageView(Writer writer) throws IOException {
460 if (maxCount > 0) {
461 writer.write("<m:IndexedPage" + itemType + "View MaxEntriesReturned=\"");
462 writer.write(String.valueOf(maxCount));
463 writer.write("\" Offset=\"");
464 writer.write(String.valueOf(offset));
465 writer.write("\" BasePoint=\"Beginning\"/>");
466
467 }
468 }
469
470 protected void writeAttachmentId(Writer writer) throws IOException {
471 if (attachmentId != null) {
472 if ("CreateAttachment".equals(methodName)) {
473 writer.write("<m:AttachmentShape>");
474 writer.write("<t:IncludeMimeContent>true</t:IncludeMimeContent>");
475 writer.write("</m:AttachmentShape>");
476 }
477 writer.write("<m:AttachmentIds>");
478 writer.write("<t:AttachmentId Id=\"");
479 writer.write(attachmentId);
480 writer.write("\"/>");
481 writer.write("</m:AttachmentIds>");
482 }
483 }
484
485 protected void writeAttachments(Writer writer) throws IOException {
486 if (attachment != null) {
487 writer.write("<m:Attachments>");
488 attachment.write(writer);
489 writer.write("</m:Attachments>");
490 }
491 }
492
493
494
495
496
497
498 public String getServerVersion() {
499 return serverVersion;
500 }
501
502
503
504
505
506
507 public void setServerVersion(String serverVersion) {
508 this.serverVersion = serverVersion;
509 }
510
511
512
513
514
515
516 public void setTimezoneContext(String timezoneContext) {
517 this.timezoneContext = timezoneContext;
518 }
519
520
521
522
523 public static class Attendee {
524
525
526
527 public String role;
528
529
530
531 public String email;
532
533
534
535 public String partstat;
536
537
538
539 public String name;
540 }
541
542
543
544
545 public static class Occurrence {
546
547
548
549 public String originalStart;
550
551
552
553
554 public ItemId itemId;
555 }
556
557
558
559
560 public static class Item extends HashMap<String, String> {
561
562
563
564 public String type;
565 protected byte[] mimeContent;
566 protected List<FieldUpdate> fieldUpdates;
567 protected List<FileAttachment> attachments;
568 protected List<Attendee> attendees;
569 protected final List<String> fieldNames = new ArrayList<>();
570 protected List<Occurrence> occurrences;
571 protected List<String> members;
572 protected ItemId referenceItemId;
573
574 @Override
575 public String toString() {
576 return "type: " + type + ' ' + super.toString();
577 }
578
579 @Override
580 public String put(String key, String value) {
581 if (value != null) {
582 if (get(key) == null) {
583 fieldNames.add(key);
584 }
585 return super.put(key, value);
586 } else {
587 return null;
588 }
589 }
590
591
592
593
594
595
596
597 public void write(Writer writer) throws IOException {
598 writer.write("<t:");
599 writer.write(type);
600 writer.write(">");
601 if (mimeContent != null) {
602 writer.write("<t:MimeContent>");
603 for (byte c : mimeContent) {
604 writer.write(c);
605 }
606 writer.write("</t:MimeContent>");
607 }
608
609 for (String key : fieldNames) {
610 if ("MeetingTimeZone".equals(key)) {
611 writer.write("<t:MeetingTimeZone TimeZoneName=\"");
612 writer.write(StringUtil.xmlEncodeAttribute(get(key)));
613 writer.write("\"></t:MeetingTimeZone>");
614 } else if ("StartTimeZone".equals(key)) {
615 writer.write("<t:StartTimeZone Id=\"");
616 writer.write(StringUtil.xmlEncodeAttribute(get(key)));
617 writer.write("\"></t:StartTimeZone>");
618 } else if ("Body".equals(key)) {
619 writer.write("<t:Body BodyType=\"Text\">");
620 writer.write(StringUtil.xmlEncode(get(key)));
621 writer.write("</t:Body>");
622 } else {
623 writer.write("<t:");
624 writer.write(key);
625 writer.write(">");
626 writer.write(StringUtil.xmlEncode(get(key)));
627 writer.write("</t:");
628 writer.write(key);
629 writer.write(">");
630 }
631 }
632 if (fieldUpdates != null) {
633 for (FieldUpdate fieldUpdate : fieldUpdates) {
634 fieldUpdate.write(null, writer);
635 }
636 }
637 if (referenceItemId != null) {
638 referenceItemId.write(writer);
639 }
640 writer.write("</t:");
641 writer.write(type);
642 writer.write(">");
643 }
644
645
646
647
648
649
650 public void setFieldUpdates(List<FieldUpdate> fieldUpdates) {
651 this.fieldUpdates = fieldUpdates;
652 }
653
654
655
656
657
658
659
660 public int getInt(String key) {
661 int result = 0;
662 String value = get(key);
663 if (value != null && !value.isEmpty()) {
664 result = Integer.parseInt(value);
665 }
666 return result;
667 }
668
669
670
671
672
673
674
675 public long getLong(String key) {
676 long result = 0;
677 String value = get(key);
678 if (value != null && !value.isEmpty()) {
679 result = Long.parseLong(value);
680 }
681 return result;
682 }
683
684
685
686
687
688
689
690
691 public boolean getBoolean(String key) {
692 boolean result = false;
693 String value = get(key);
694 if (value != null && !value.isEmpty()) {
695 result = Boolean.parseBoolean(value);
696 }
697 return result;
698 }
699
700
701
702
703
704
705
706 public FileAttachment getAttachmentByName(String attachmentName) {
707 FileAttachment result = null;
708 if (attachments != null) {
709 for (FileAttachment fileAttachment : attachments) {
710 if (attachmentName.equals(fileAttachment.name)) {
711 result = fileAttachment;
712 break;
713 }
714 }
715 }
716 return result;
717 }
718
719
720
721
722
723
724 public List<Attendee> getAttendees() {
725 return attendees;
726 }
727
728
729
730
731
732
733 public void addAttendee(Attendee attendee) {
734 if (attendees == null) {
735 attendees = new ArrayList<>();
736 }
737 attendees.add(attendee);
738 }
739
740
741
742
743
744
745 public void addOccurrence(Occurrence occurrence) {
746 if (occurrences == null) {
747 occurrences = new ArrayList<>();
748 }
749 occurrences.add(occurrence);
750 }
751
752
753
754
755
756
757 public List<Occurrence> getOccurrences() {
758 return occurrences;
759 }
760
761
762
763
764
765
766 public void addMember(String member) {
767 if (members == null) {
768 members = new ArrayList<>();
769 }
770 members.add(member);
771 }
772
773
774
775
776
777
778 public List<String> getMembers() {
779 return members;
780 }
781 }
782
783
784
785
786
787
788 public void checkSuccess() throws EWSException {
789 if ("The server cannot service this request right now. Try again later.".equals(errorDetail)) {
790 throw new EWSThrottlingException(errorDetail);
791 }
792 if (errorDetail != null && (!"ErrorAccessDenied".equals(errorDetail)
793 && !"ErrorMailRecipientNotFound".equals(errorDetail)
794 && !"ErrorItemNotFound".equals(errorDetail)
795 && !"ErrorCalendarOccurrenceIsDeletedFromRecurrence".equals(errorDetail)
796 )) {
797 throw new EWSException(errorDetail
798 + ' ' + ((errorDescription != null) ? errorDescription : "")
799 + ' ' + ((errorValue != null) ? errorValue : "")
800 + "\n request: " + new String(generateSoapEnvelope(), StandardCharsets.UTF_8));
801
802 }
803 if (getStatusCode() == HttpStatus.SC_BAD_REQUEST || getStatusCode() == HttpStatus.SC_INSUFFICIENT_STORAGE) {
804 throw new EWSException(response.getStatusLine().getReasonPhrase());
805 }
806 }
807
808 public int getStatusCode() {
809 if ("ErrorAccessDenied".equals(errorDetail)) {
810 return HttpStatus.SC_FORBIDDEN;
811 } else if ("ErrorItemNotFound".equals(errorDetail)) {
812 return HttpStatus.SC_NOT_FOUND;
813 } else {
814 return response.getStatusLine().getStatusCode();
815 }
816 }
817
818
819
820
821
822
823
824 public List<Item> getResponseItems() throws EWSException {
825 checkSuccess();
826 if (responseItems != null) {
827 return responseItems;
828 } else {
829 return new ArrayList<>();
830 }
831 }
832
833
834
835
836
837
838
839 public Item getResponseItem() throws EWSException {
840 checkSuccess();
841 if (responseItems != null && !responseItems.isEmpty()) {
842 return responseItems.get(0);
843 } else {
844 return null;
845 }
846 }
847
848
849
850
851
852
853
854 public byte[] getMimeContent() throws EWSException {
855 checkSuccess();
856 Item responseItem = getResponseItem();
857 if (responseItem != null) {
858 return responseItem.mimeContent;
859 } else {
860 return null;
861 }
862 }
863
864 protected String handleTag(XMLStreamReader reader, String localName) throws XMLStreamException {
865 StringBuilder result = null;
866 int event = reader.getEventType();
867 if (event == XMLStreamConstants.START_ELEMENT && localName.equals(reader.getLocalName())) {
868 result = new StringBuilder();
869 while (reader.hasNext() &&
870 !((event == XMLStreamConstants.END_ELEMENT && localName.equals(reader.getLocalName())))) {
871 event = reader.next();
872 if (event == XMLStreamConstants.CHARACTERS) {
873 result.append(reader.getText());
874 } else if ("MessageXml".equals(localName) && event == XMLStreamConstants.START_ELEMENT) {
875 String attributeValue = null;
876 for (int i = 0; i < reader.getAttributeCount(); i++) {
877 if (result.length() > 0) {
878 result.append(", ");
879 }
880 attributeValue = reader.getAttributeValue(i);
881 result.append(reader.getAttributeLocalName(i)).append(": ").append(reader.getAttributeValue(i));
882 }
883
884 if ("BackOffMilliseconds".equals(attributeValue)) {
885 try {
886 backOffMilliseconds = Long.parseLong(reader.getElementText());
887 } catch (NumberFormatException e) {
888 LOGGER.error("Unable to parse BackOffMilliseconds");
889 }
890 }
891 }
892 }
893 }
894 if (result != null && result.length() > 0) {
895 return result.toString();
896 } else {
897 return null;
898 }
899 }
900
901 protected void handleErrors(XMLStreamReader reader) throws XMLStreamException {
902 String result = handleTag(reader, "ResponseCode");
903
904 String messageText = handleTag(reader, "MessageText");
905 if (messageText != null) {
906 errorDescription = messageText;
907 }
908 String messageXml = handleTag(reader, "MessageXml");
909 if (messageXml != null) {
910
911 errorValue = messageXml;
912 }
913 if (errorDetail == null && result != null
914 && !"NoError".equals(result)
915 && !"ErrorNameResolutionMultipleResults".equals(result)
916 && !"ErrorNameResolutionNoResults".equals(result)
917 && !"ErrorFolderExists".equals(result)
918 ) {
919 errorDetail = result;
920 }
921 if (XMLStreamUtil.isStartTag(reader, "faultstring")) {
922 errorDetail = XMLStreamUtil.getElementText(reader);
923 }
924 }
925
926 protected Item handleItem(XMLStreamReader reader) throws XMLStreamException {
927 Item responseItem = new Item();
928 responseItem.type = reader.getLocalName();
929 while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, responseItem.type)) {
930 reader.next();
931 if (XMLStreamUtil.isStartTag(reader)) {
932 String tagLocalName = reader.getLocalName();
933 String value = null;
934 if ("ExtendedProperty".equals(tagLocalName)) {
935 addExtendedPropertyValue(reader, responseItem);
936 } else if ("Members".equals(tagLocalName)) {
937 handleMembers(reader, responseItem);
938 } else if (tagLocalName.endsWith("MimeContent")) {
939 handleMimeContent(reader, responseItem);
940 } else if ("Attachments".equals(tagLocalName)) {
941 responseItem.attachments = handleAttachments(reader);
942 } else if ("EmailAddresses".equals(tagLocalName)) {
943 handleEmailAddresses(reader, responseItem);
944 } else if ("RequiredAttendees".equals(tagLocalName) || "OptionalAttendees".equals(tagLocalName)) {
945 handleAttendees(reader, responseItem, tagLocalName);
946 } else if ("ModifiedOccurrences".equals(tagLocalName)) {
947 handleModifiedOccurrences(reader, responseItem);
948 } else {
949 if (tagLocalName.endsWith("Id")) {
950 value = getAttributeValue(reader, "Id");
951
952 responseItem.put("ChangeKey", getAttributeValue(reader, "ChangeKey"));
953 }
954 if (value == null) {
955 value = getTagContent(reader);
956 }
957 if (value != null) {
958 responseItem.put(tagLocalName, value);
959 }
960 }
961 }
962 }
963 return responseItem;
964 }
965
966 protected void handleEmailAddresses(XMLStreamReader reader, Item item) throws XMLStreamException {
967 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "EmailAddresses"))) {
968 reader.next();
969 if (XMLStreamUtil.isStartTag(reader)) {
970 String tagLocalName = reader.getLocalName();
971 if ("Entry".equals(tagLocalName)) {
972 item.put(reader.getAttributeValue(null, "Key"), XMLStreamUtil.getElementText(reader));
973 }
974 }
975 }
976 }
977
978 protected void handleAttendees(XMLStreamReader reader, Item item, String attendeeType) throws XMLStreamException {
979 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, attendeeType))) {
980 reader.next();
981 if (XMLStreamUtil.isStartTag(reader)) {
982 String tagLocalName = reader.getLocalName();
983 if ("Attendee".equals(tagLocalName)) {
984 handleAttendee(reader, item, attendeeType);
985 }
986 }
987 }
988 }
989
990 protected void handleModifiedOccurrences(XMLStreamReader reader, Item item) throws XMLStreamException {
991 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "ModifiedOccurrences"))) {
992 reader.next();
993 if (XMLStreamUtil.isStartTag(reader)) {
994 String tagLocalName = reader.getLocalName();
995 if ("Occurrence".equals(tagLocalName)) {
996 handleOccurrence(reader, item);
997 }
998 }
999 }
1000 }
1001
1002 protected void handleOccurrence(XMLStreamReader reader, Item item) throws XMLStreamException {
1003 Occurrence occurrence = new Occurrence();
1004 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Occurrence"))) {
1005 reader.next();
1006 if (XMLStreamUtil.isStartTag(reader)) {
1007 String tagLocalName = reader.getLocalName();
1008 if ("ItemId".equals(tagLocalName)) {
1009 occurrence.itemId = new ItemId("ItemId", getAttributeValue(reader, "Id"), getAttributeValue(reader, "ChangeKey"));
1010 }
1011 if ("OriginalStart".equals(tagLocalName)) {
1012 occurrence.originalStart = XMLStreamUtil.getElementText(reader);
1013 }
1014 }
1015 }
1016 item.addOccurrence(occurrence);
1017 }
1018
1019 protected void handleMembers(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
1020 while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "Members")) {
1021 reader.next();
1022 if (XMLStreamUtil.isStartTag(reader)) {
1023 String tagLocalName = reader.getLocalName();
1024 if ("Member".equals(tagLocalName)) {
1025 handleMember(reader, responseItem);
1026 }
1027 }
1028 }
1029 }
1030
1031 protected void handleMember(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
1032 String member = null;
1033 while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "Member")) {
1034 reader.next();
1035 if (XMLStreamUtil.isStartTag(reader)) {
1036 String tagLocalName = reader.getLocalName();
1037 if ("EmailAddress".equals(tagLocalName) && member == null) {
1038 member = "mailto:" + XMLStreamUtil.getElementText(reader);
1039 }
1040 }
1041 }
1042 if (member != null) {
1043 responseItem.addMember(member);
1044 }
1045 }
1046
1047
1048
1049
1050
1051
1052
1053 public static String responseTypeToPartstat(String responseType) {
1054 if ("Accept".equals(responseType) || "Organizer".equals(responseType)) {
1055 return "ACCEPTED";
1056 } else if ("Tentative".equals(responseType)) {
1057 return "TENTATIVE";
1058 } else if ("Decline".equals(responseType)) {
1059 return "DECLINED";
1060 } else {
1061 return "NEEDS-ACTION";
1062 }
1063 }
1064
1065 protected void handleAttendee(XMLStreamReader reader, Item item, String attendeeType) throws XMLStreamException {
1066 Attendee attendee = new Attendee();
1067 if ("RequiredAttendees".equals(attendeeType)) {
1068 attendee.role = "REQ-PARTICIPANT";
1069 } else {
1070 attendee.role = "OPT-PARTICIPANT";
1071 }
1072 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Attendee"))) {
1073 reader.next();
1074 if (XMLStreamUtil.isStartTag(reader)) {
1075 String tagLocalName = reader.getLocalName();
1076 if ("EmailAddress".equals(tagLocalName)) {
1077 attendee.email = reader.getElementText();
1078 } else if ("Name".equals(tagLocalName)) {
1079 attendee.name = XMLStreamUtil.getElementText(reader);
1080 } else if ("ResponseType".equals(tagLocalName)) {
1081 String responseType = XMLStreamUtil.getElementText(reader);
1082 attendee.partstat = responseTypeToPartstat(responseType);
1083 }
1084 }
1085 }
1086 item.addAttendee(attendee);
1087 }
1088
1089 protected List<FileAttachment> handleAttachments(XMLStreamReader reader) throws XMLStreamException {
1090 List<FileAttachment> attachments = new ArrayList<>();
1091 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Attachments"))) {
1092 reader.next();
1093 if (XMLStreamUtil.isStartTag(reader)) {
1094 String tagLocalName = reader.getLocalName();
1095 if ("FileAttachment".equals(tagLocalName)) {
1096 attachments.add(handleFileAttachment(reader));
1097 }
1098 }
1099 }
1100 return attachments;
1101 }
1102
1103 protected FileAttachment handleFileAttachment(XMLStreamReader reader) throws XMLStreamException {
1104 FileAttachment fileAttachment = new FileAttachment();
1105 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "FileAttachment"))) {
1106 reader.next();
1107 if (XMLStreamUtil.isStartTag(reader)) {
1108 String tagLocalName = reader.getLocalName();
1109 if ("AttachmentId".equals(tagLocalName)) {
1110 fileAttachment.attachmentId = getAttributeValue(reader, "Id");
1111 } else if ("Name".equals(tagLocalName)) {
1112 fileAttachment.name = getTagContent(reader);
1113 } else if ("ContentType".equals(tagLocalName)) {
1114 fileAttachment.contentType = getTagContent(reader);
1115 }
1116 }
1117 }
1118 return fileAttachment;
1119 }
1120
1121
1122 protected void handleMimeContent(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
1123 if (reader instanceof TypedXMLStreamReader) {
1124
1125 responseItem.mimeContent = ((TypedXMLStreamReader) reader).getElementAsBinary();
1126 } else {
1127
1128 responseItem.mimeContent = Base64.decodeBase64(reader.getElementText().getBytes(StandardCharsets.US_ASCII));
1129 }
1130 }
1131
1132 protected void addExtendedPropertyValue(XMLStreamReader reader, Item item) throws XMLStreamException {
1133 String propertyTag = null;
1134 String propertyValue = null;
1135 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "ExtendedProperty"))) {
1136 reader.next();
1137 if (XMLStreamUtil.isStartTag(reader)) {
1138 String tagLocalName = reader.getLocalName();
1139 if ("ExtendedFieldURI".equals(tagLocalName)) {
1140 propertyTag = getAttributeValue(reader, "PropertyTag");
1141
1142 if (propertyTag == null) {
1143 propertyTag = getAttributeValue(reader, "PropertyId");
1144 }
1145 if (propertyTag == null) {
1146 propertyTag = getAttributeValue(reader, "PropertyName");
1147 }
1148 } else if ("Value".equals(tagLocalName)) {
1149 propertyValue = XMLStreamUtil.getElementText(reader);
1150 } else if ("Values".equals(tagLocalName)) {
1151 StringBuilder buffer = new StringBuilder();
1152 while (reader.hasNext() && !(XMLStreamUtil.isEndTag(reader, "Values"))) {
1153 reader.next();
1154 if (XMLStreamUtil.isStartTag(reader)) {
1155
1156 if (buffer.length() > 0) {
1157 buffer.append(',');
1158 }
1159 String singleValue = XMLStreamUtil.getElementText(reader);
1160 if (singleValue != null) {
1161 buffer.append(singleValue);
1162 }
1163 }
1164 }
1165 propertyValue = buffer.toString();
1166 }
1167 }
1168 }
1169 if ((propertyTag != null) && (propertyValue != null)) {
1170 item.put(propertyTag, propertyValue);
1171 }
1172 }
1173
1174 protected String getTagContent(XMLStreamReader reader) throws XMLStreamException {
1175 String tagLocalName = reader.getLocalName();
1176 while (reader.hasNext() && !(reader.getEventType() == XMLStreamConstants.END_ELEMENT)) {
1177 reader.next();
1178 if (reader.getEventType() == XMLStreamConstants.CHARACTERS) {
1179 return reader.getText();
1180 }
1181 }
1182
1183 if (reader.hasNext()) {
1184 return null;
1185 } else {
1186 throw new XMLStreamException("End element for " + tagLocalName + " not found");
1187 }
1188 }
1189
1190 protected String getAttributeValue(XMLStreamReader reader, String attributeName) {
1191 for (int i = 0; i < reader.getAttributeCount(); i++) {
1192 if (attributeName.equals(reader.getAttributeLocalName(i))) {
1193 return reader.getAttributeValue(i);
1194 }
1195 }
1196 return null;
1197 }
1198
1199 @Override
1200 public EWSMethod handleResponse(HttpResponse response) {
1201 this.response = response;
1202 org.apache.http.Header contentTypeHeader = response.getFirstHeader("Content-Type");
1203 if (contentTypeHeader != null && "text/xml; charset=utf-8".equals(contentTypeHeader.getValue())) {
1204 try (
1205 InputStream inputStream = response.getEntity().getContent()
1206 ) {
1207 if (HttpClientAdapter.isGzipEncoded(response)) {
1208 processResponseStream(new GZIPInputStream(inputStream));
1209 } else {
1210 processResponseStream(inputStream);
1211 }
1212 } catch (IOException e) {
1213 LOGGER.error("Error while parsing soap response: " + e, e);
1214 }
1215 }
1216 return this;
1217 }
1218
1219 protected void processResponseStream(InputStream inputStream) {
1220 responseItems = new ArrayList<>();
1221 XMLStreamReader reader = null;
1222 try {
1223 inputStream = new FilterInputStream(inputStream) {
1224 int totalCount;
1225 int lastLogCount;
1226
1227 @Override
1228 public int read(byte[] buffer, int offset, int length) throws IOException {
1229 int count = super.read(buffer, offset, length);
1230 totalCount += count;
1231 if (totalCount - lastLogCount > 1024 * 128) {
1232 DavGatewayTray.debug(new BundleMessage("LOG_DOWNLOAD_PROGRESS", String.valueOf(totalCount / 1024), EWSMethod.this.getURI()));
1233 DavGatewayTray.switchIcon();
1234 lastLogCount = totalCount;
1235 }
1236
1237
1238
1239 return count;
1240 }
1241 };
1242 reader = XMLStreamUtil.createXMLStreamReader(inputStream);
1243 while (reader.hasNext()) {
1244 reader.next();
1245 handleErrors(reader);
1246 if (serverVersion == null && XMLStreamUtil.isStartTag(reader, "ServerVersionInfo")) {
1247 String majorVersion = getAttributeValue(reader, "MajorVersion");
1248 String minorVersion = getAttributeValue(reader, "MinorVersion");
1249 if ("15".equals(majorVersion)) {
1250 if ("0".equals(minorVersion)) {
1251 serverVersion = "Exchange2013";
1252 } else {
1253 serverVersion = "Exchange2013_SP1";
1254 }
1255 } else if ("14".equals(majorVersion)) {
1256 if ("0".equals(minorVersion)) {
1257 serverVersion = "Exchange2010";
1258 } else {
1259 serverVersion = "Exchange2010_SP1";
1260 }
1261 } else {
1262 serverVersion = "Exchange2007_SP1";
1263 }
1264 } else if (XMLStreamUtil.isStartTag(reader, "RootFolder")) {
1265 includesLastItemInRange = "true".equals(reader.getAttributeValue(null, "IncludesLastItemInRange"));
1266 } else if (XMLStreamUtil.isStartTag(reader, responseCollectionName)) {
1267 handleItems(reader);
1268 } else {
1269 handleCustom(reader);
1270 }
1271 }
1272 } catch (XMLStreamException e) {
1273 errorDetail = e.getMessage();
1274 LOGGER.error("Error while parsing soap response: " + e, e);
1275 if (reader != null) {
1276 try {
1277 String content = reader.getText();
1278 if (content != null && content.length() > 4096) {
1279 content = content.substring(0, 4096)+" ...";
1280 }
1281 LOGGER.debug("Current text: " + content);
1282 } catch (IllegalStateException ise) {
1283 LOGGER.error(e + " " + e.getMessage());
1284 }
1285 }
1286 }
1287 if (errorDetail != null) {
1288 LOGGER.debug(errorDetail);
1289 }
1290 }
1291
1292 @SuppressWarnings({"NoopMethodInAbstractClass"})
1293 protected void handleCustom(XMLStreamReader reader) throws XMLStreamException {
1294
1295 }
1296
1297 private void handleItems(XMLStreamReader reader) throws XMLStreamException {
1298 while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, responseCollectionName)) {
1299 reader.next();
1300 if (XMLStreamUtil.isStartTag(reader)) {
1301 responseItems.add(handleItem(reader));
1302 }
1303 }
1304
1305 }
1306
1307 }