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