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