1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package davmail.exchange.graph;
21
22 import davmail.BundleMessage;
23 import davmail.Settings;
24 import davmail.exception.DavMailException;
25 import davmail.exception.HttpForbiddenException;
26 import davmail.exception.HttpNotFoundException;
27 import davmail.exchange.ExchangeSession;
28 import davmail.exchange.VCalendar;
29 import davmail.exchange.VObject;
30 import davmail.exchange.VProperty;
31 import davmail.exchange.auth.O365Token;
32 import davmail.exchange.ews.EwsExchangeSession;
33 import davmail.exchange.ews.ExtendedFieldURI;
34 import davmail.exchange.ews.Field;
35 import davmail.exchange.ews.FieldURI;
36 import davmail.exchange.ews.SearchExpression;
37 import davmail.http.HttpClientAdapter;
38 import davmail.http.URIUtil;
39 import davmail.ui.tray.DavGatewayTray;
40 import davmail.util.IOUtil;
41 import davmail.util.StringUtil;
42 import org.apache.http.HttpStatus;
43 import org.apache.http.client.methods.CloseableHttpResponse;
44 import org.apache.http.client.methods.HttpDelete;
45 import org.apache.http.client.methods.HttpGet;
46 import org.apache.http.client.methods.HttpPatch;
47 import org.apache.http.client.methods.HttpPost;
48 import org.apache.http.client.methods.HttpPut;
49 import org.apache.http.client.methods.HttpRequestBase;
50 import org.codehaus.jettison.json.JSONArray;
51 import org.codehaus.jettison.json.JSONException;
52 import org.codehaus.jettison.json.JSONObject;
53 import org.htmlcleaner.HtmlCleaner;
54 import org.htmlcleaner.TagNode;
55
56 import javax.mail.MessagingException;
57 import javax.mail.internet.MimeMessage;
58 import java.io.ByteArrayInputStream;
59 import java.io.FilterInputStream;
60 import java.io.IOException;
61 import java.io.InputStream;
62 import java.io.StringReader;
63 import java.net.URI;
64 import java.nio.charset.StandardCharsets;
65 import java.text.ParseException;
66 import java.text.SimpleDateFormat;
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.Date;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.List;
73 import java.util.Locale;
74 import java.util.Map;
75 import java.util.MissingResourceException;
76 import java.util.NoSuchElementException;
77 import java.util.ResourceBundle;
78 import java.util.Set;
79 import java.util.TimeZone;
80 import java.util.zip.GZIPInputStream;
81
82 import static davmail.exchange.graph.GraphObject.convertTimezoneFromExchange;
83
84
85
86
87 public class GraphExchangeSession extends ExchangeSession {
88
89
90
91
92 protected class Folder extends ExchangeSession.Folder {
93 public FolderId folderId;
94 protected String specialFlag = "";
95
96 protected void setSpecialFlag(String specialFlag) {
97 this.specialFlag = "\\" + specialFlag + " ";
98 }
99
100
101
102
103
104
105 @Override
106 public String getFlags() {
107 if (noInferiors) {
108 return specialFlag + "\\NoInferiors";
109 } else if (hasChildren) {
110 return specialFlag + "\\HasChildren";
111 } else {
112 return specialFlag + "\\HasNoChildren";
113 }
114 }
115 }
116
117 protected class Event extends ExchangeSession.Event {
118
119 public FolderId folderId;
120
121 public String id;
122
123 protected GraphObject graphObject;
124
125 public Event(FolderId folderId, GraphObject graphObject) {
126 this.folderId = folderId;
127
128 if ("IPF.Appointment".equals(folderId.folderClass) && graphObject.optString("taskstatus") != null) {
129
130 try {
131 this.folderId = getFolderId(TASKS);
132 } catch (IOException e) {
133 LOGGER.warn("Unable to replace folder with tasks");
134 }
135 }
136
137 this.graphObject = graphObject;
138
139 id = graphObject.optString("id");
140 etag = graphObject.optString("changeKey");
141
142 displayName = graphObject.optString("subject");
143 subject = graphObject.optString("subject");
144
145 itemName = StringUtil.base64ToUrl(id) + ".EML";
146 }
147
148 public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
149 super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
150 folderId = getFolderId(folderPath);
151 }
152
153 @Override
154 public byte[] getEventContent() throws IOException {
155 byte[] content;
156 if (LOGGER.isDebugEnabled()) {
157 LOGGER.debug("Get event: " + itemName);
158 }
159 try {
160 if ("IPF.Task".equals(folderId.folderClass)) {
161 VCalendar localVCalendar = new VCalendar();
162 VObject vTodo = new VObject();
163 vTodo.type = "VTODO";
164 localVCalendar.setTimezone(getVTimezone());
165 vTodo.setPropertyValue("LAST-MODIFIED", convertDateFromExchange(graphObject.optString("lastModifiedDateTime")));
166 vTodo.setPropertyValue("CREATED", convertDateFromExchange(graphObject.optString("createdDateTime")));
167
168 vTodo.setPropertyValue("UID", graphObject.optString("id"));
169 vTodo.setPropertyValue("TITLE", graphObject.optString("title"));
170 vTodo.setPropertyValue("SUMMARY", graphObject.optString("title"));
171
172 vTodo.addProperty(convertBodyToVproperty("DESCRIPTION", graphObject));
173
174
175 vTodo.setPropertyValue("PRIORITY", convertPriorityFromExchange(graphObject.optString("importance")));
176
177
178 vTodo.setPropertyValue("STATUS", taskTovTodoStatusMap.get(graphObject.optString("status")));
179
180 vTodo.setPropertyValue("DUE;VALUE=DATE", convertDateTimeTimeZoneToTaskDate(graphObject.optDateTimeTimeZone("dueDateTime")));
181 vTodo.setPropertyValue("DTSTART;VALUE=DATE", convertDateTimeTimeZoneToTaskDate(graphObject.optDateTimeTimeZone("startDateTime")));
182 vTodo.setPropertyValue("COMPLETED;VALUE=DATE", convertDateTimeTimeZoneToTaskDate(graphObject.optDateTimeTimeZone("completedDateTime")));
183
184 vTodo.setPropertyValue("CATEGORIES", graphObject.optString("categories"));
185
186 localVCalendar.addVObject(vTodo);
187 content = localVCalendar.toString().getBytes(StandardCharsets.UTF_8);
188 } else {
189
190
191 VCalendar localVCalendar = new VCalendar();
192
193 localVCalendar.setTimezone(getVTimezone());
194 VObject vEvent = new VObject();
195 vEvent.type = "VEVENT";
196 localVCalendar.addVObject(vEvent);
197 localVCalendar.setFirstVeventPropertyValue("UID", graphObject.optString("iCalUId"));
198 localVCalendar.setFirstVeventPropertyValue("SUMMARY", graphObject.optString("subject"));
199
200 localVCalendar.addFirstVeventProperty(convertBodyToVproperty("DESCRIPTION", graphObject));
201
202 localVCalendar.setFirstVeventPropertyValue("LAST-MODIFIED", convertDateFromExchange(graphObject.optString("lastModifiedDateTime")));
203 localVCalendar.setFirstVeventPropertyValue("DTSTAMP", convertDateFromExchange(graphObject.optString("lastModifiedDateTime")));
204 localVCalendar.addFirstVeventProperty(convertDateTimeTimeZoneToVproperty("DTSTART", graphObject.optJSONObject("start")));
205 localVCalendar.addFirstVeventProperty(convertDateTimeTimeZoneToVproperty("DTEND", graphObject.optJSONObject("end")));
206
207 localVCalendar.setFirstVeventPropertyValue("CLASS", convertClassFromExchange(graphObject.optString("sensitivity")));
208
209
210 localVCalendar.setFirstVeventPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS", graphObject.optString("showAs").toUpperCase());
211 localVCalendar.setFirstVeventPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT", graphObject.optString("isAllDay").toUpperCase());
212 localVCalendar.setFirstVeventPropertyValue("X-MICROSOFT-CDO-ISRESPONSEREQUESTED", graphObject.optString("responseRequested").toUpperCase());
213
214 handleException(localVCalendar, graphObject);
215
216 handleRecurrence(localVCalendar, graphObject);
217
218 localVCalendar.setFirstVeventPropertyValue("X-MOZ-SEND-INVITATIONS", graphObject.optString("xmozsendinvitations"));
219 localVCalendar.setFirstVeventPropertyValue("X-MOZ-LASTACK", graphObject.optString("xmozlastack"));
220 localVCalendar.setFirstVeventPropertyValue("X-MOZ-SNOOZE-TIME", graphObject.optString("xmozsnoozetime"));
221
222 setAttendees(localVCalendar.getFirstVevent());
223
224 content = localVCalendar.toString().getBytes(StandardCharsets.UTF_8);
225 }
226 } catch (Exception e) {
227 throw new IOException(e.getMessage(), e);
228 }
229 return content;
230 }
231
232 private void handleException(VCalendar localVCalendar, GraphObject graphObject) throws DavMailException {
233 JSONArray cancelledOccurrences = graphObject.optJSONArray("cancelledOccurrences");
234 if (cancelledOccurrences != null) {
235 for (int i = 0; i < cancelledOccurrences.length(); i++) {
236 String cancelledOccurrence = null;
237 try {
238 cancelledOccurrence = cancelledOccurrences.getString(i);
239 cancelledOccurrence = cancelledOccurrence.substring(cancelledOccurrence.lastIndexOf('.')+1);
240 String cancelledDate = convertDateFromExchange(cancelledOccurrence);
241
242 VProperty startDate = localVCalendar.getFirstVevent().getProperty("DTSTART");
243 VProperty exDate = new VProperty("EXDATE", cancelledDate.substring(0, 8)+startDate.getValue().substring(8));
244 exDate.setParam("TZID", startDate.getParamValue("TZID"));
245 localVCalendar.addFirstVeventProperty(exDate);
246 } catch (IndexOutOfBoundsException | JSONException e) {
247 LOGGER.warn("Invalid cancelled occurrence: "+cancelledOccurrence);
248 }
249 }
250 }
251 }
252
253 private void handleRecurrence(VCalendar localVCalendar, GraphObject graphObject) throws JSONException, DavMailException {
254
255 JSONObject recurrence = graphObject.optJSONObject("recurrence");
256 if (recurrence != null) {
257 StringBuilder rruleValue = new StringBuilder();
258 JSONObject pattern = recurrence.getJSONObject("pattern");
259 JSONObject range = recurrence.getJSONObject("range");
260
261 String patternType = pattern.getString("type");
262 int interval = pattern.getInt("interval");
263
264 String index = pattern.optString("index", null);
265
266 if ("first".equals(index)) {
267 index = "1";
268 } else if ("second".equals(index)) {
269 index = "2";
270 } else if ("third".equals(index)) {
271 index = "3";
272 } else if ("fourth".equals(index)) {
273 index = "4";
274 } else if ("last".equals(index)) {
275 index = "-1";
276 }
277
278 String month = pattern.getString("month");
279 if ("0".equals(month)) {
280 month = null;
281 }
282
283 String firstDayOfWeek = pattern.getString("firstDayOfWeek");
284
285 String dayOfMonth = pattern.getString("dayOfMonth");
286 if ("0".equals(dayOfMonth)) {
287 dayOfMonth = null;
288 }
289
290 JSONArray daysOfWeek = pattern.optJSONArray("daysOfWeek");
291 String rangeType = range.getString("type");
292
293 rruleValue.append("FREQ=");
294 if (patternType.startsWith("absolute") || patternType.startsWith("relative")) {
295 rruleValue.append(patternType.substring(8).toUpperCase());
296 } else {
297 rruleValue.append(patternType.toUpperCase());
298 }
299 if (rangeType.equals("endDate")) {
300
301
302 String endDate = buildUntilDate(range.getString("endDate"), range.getString("recurrenceTimeZone"), graphObject.optJSONObject("start"));
303 rruleValue.append(";UNTIL=").append(endDate);
304 }
305 if (interval > 0) {
306 rruleValue.append(";INTERVAL=").append(interval);
307 }
308 if (dayOfMonth != null && !dayOfMonth.isEmpty()) {
309 rruleValue.append(";BYMONTHDAY=").append(dayOfMonth);
310 }
311 if (month != null && !month.isEmpty()) {
312 rruleValue.append(";BYMONTH=").append(month);
313 }
314 if (daysOfWeek != null && daysOfWeek.length() > 0) {
315 ArrayList<String> days = new ArrayList<>();
316 for (int i=0;i<daysOfWeek.length();i++) {
317 StringBuilder byDay = new StringBuilder();
318 if (index != null && !"weekly".equals(patternType)) {
319 byDay.append(index);
320 }
321 byDay.append(daysOfWeek.getString(i).substring(0, 2).toUpperCase());
322 days.add(byDay.toString());
323 }
324 rruleValue.append(";BYDAY=").append(String.join(",", days));
325 }
326 if ("weekly".equals(patternType) && firstDayOfWeek.length() >= 2) {
327 rruleValue.append(";WKST=").append(firstDayOfWeek.substring(0, 2).toUpperCase());
328 }
329
330 localVCalendar.addFirstVeventProperty(new VProperty("RRULE", rruleValue.toString()));
331 }
332 }
333
334 private String buildUntilDate(String date, String timeZone, JSONObject startDate) throws DavMailException {
335 String result = null;
336 if (date != null && date.length() == 10) {
337 String startDateTimeZone = startDate.optString("timeZone");
338 String startDateDateTime = startDate.optString("dateTime");
339 String untilDateTime = date+startDateDateTime.substring(10);
340
341 if (timeZone == null) {
342 timeZone = startDateTimeZone;
343 }
344
345 SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
346 SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
347 formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
348 parser.setTimeZone(TimeZone.getTimeZone(convertTimezoneFromExchange(timeZone)));
349 try {
350 result = formatter.format(parser.parse(untilDateTime));
351 } catch (ParseException e) {
352 throw new DavMailException("EXCEPTION_INVALID_DATE", date);
353 }
354 }
355 return result;
356 }
357
358
359 private void setAttendees(VObject vEvent) throws JSONException {
360
361 JSONObject organizer = graphObject.optJSONObject("organizer");
362 if (organizer != null) {
363 vEvent.addProperty(convertEmailAddressToVproperty("ORGANIZER", organizer.optJSONObject("emailAddress")));
364 }
365
366 JSONArray attendees = graphObject.optJSONArray("attendees");
367 if (attendees != null) {
368 for (int i = 0; i < attendees.length(); i++) {
369 JSONObject attendee = attendees.getJSONObject(i);
370 JSONObject emailAddress = attendee.getJSONObject("emailAddress");
371 VProperty attendeeProperty = convertEmailAddressToVproperty("ATTENDEE", emailAddress);
372
373
374 String responseType = attendee.getJSONObject("status").optString("response");
375 String myResponseType = graphObject.optString("responseStatus", "response");
376
377
378 if (email.equalsIgnoreCase(emailAddress.optString("address")) && myResponseType != null) {
379 attendeeProperty.addParam("PARTSTAT", responseTypeToPartstat(myResponseType));
380 } else {
381 attendeeProperty.addParam("PARTSTAT", responseTypeToPartstat(responseType));
382 }
383
384 String type = attendee.optString("type");
385 if ("required".equals(type)) {
386 attendeeProperty.addParam("ROLE", "REQ-PARTICIPANT");
387 } else if ("optional".equals(type)) {
388 attendeeProperty.addParam("ROLE", "OPT-PARTICIPANT");
389 }
390
391 vEvent.addProperty(attendeeProperty);
392 }
393 }
394 }
395
396
397
398
399
400
401
402 private String responseTypeToPartstat(String responseType) {
403
404 if ("accepted".equals(responseType) || "organizer".equals(responseType)) {
405 return "ACCEPTED";
406 } else if ("tentativelyAccepted".equals(responseType)) {
407 return "TENTATIVE";
408 } else if ("declined".equals(responseType)) {
409 return "DECLINED";
410 } else {
411 return "NEEDS-ACTION";
412 }
413 }
414
415 @Override
416 public ItemResult createOrUpdate() throws IOException {
417
418 String id = null;
419 String currentEtag = null;
420 JSONObject jsonEvent = getEventIfExists(folderId, itemName);
421 if (jsonEvent != null) {
422 id = jsonEvent.optString("id", null);
423 currentEtag = new GraphObject(jsonEvent).optString("changeKey");
424 }
425
426 ItemResult itemResult = new ItemResult();
427 if ("*".equals(noneMatch)) {
428
429 if (id != null) {
430 itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
431 return itemResult;
432 }
433 } else if (etag != null) {
434
435 if (id == null || !etag.equals(currentEtag)) {
436 itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
437 return itemResult;
438 }
439 }
440
441 VObject vEvent = vCalendar.getFirstVevent();
442 try {
443 JSONObject jsonObject = new JSONObject();
444 jsonObject.put("subject", vEvent.getPropertyValue("SUMMARY"));
445
446
447 VProperty dtStart = vEvent.getProperty("DTSTART");
448 String dtStartTzid = dtStart.getParamValue("TZID");
449 jsonObject.put("start", new JSONObject().put("dateTime", vCalendar.convertCalendarDateToGraph(dtStart.getValue(), dtStartTzid)).put("timeZone", dtStartTzid));
450
451 VProperty dtEnd = vEvent.getProperty("DTEND");
452 String dtEndTzid = dtEnd.getParamValue("TZID");
453 jsonObject.put("end", new JSONObject().put("dateTime", vCalendar.convertCalendarDateToGraph(dtEnd.getValue(), dtEndTzid)).put("timeZone", dtEndTzid));
454
455 VProperty descriptionProperty = vEvent.getProperty("DESCRIPTION");
456 String description = null;
457 if (descriptionProperty != null) {
458 description = vEvent.getProperty("DESCRIPTION").getParamValue("ALTREP");
459 }
460 if (description != null && description.startsWith("data:text/html,")) {
461 description = URIUtil.decode(description.replaceFirst("data:text/html,", ""));
462 jsonObject.put("body", new JSONObject().put("content", description).put("contentType", "html"));
463 } else {
464 description = vEvent.getPropertyValue("DESCRIPTION");
465 jsonObject.put("body", new JSONObject().put("content", description).put("contentType", "text"));
466 }
467
468 GraphRequestBuilder graphRequestBuilder = new GraphRequestBuilder();
469 if (id == null) {
470 graphRequestBuilder.setMethod(HttpPost.METHOD_NAME)
471 .setMailbox(folderId.mailbox)
472 .setObjectType("calendars")
473 .setObjectId(folderId.id)
474 .setChildType("events")
475 .setJsonBody(jsonObject);
476 } else {
477 graphRequestBuilder.setMethod(HttpPatch.METHOD_NAME)
478 .setMailbox(folderId.mailbox)
479 .setObjectType("events")
480 .setObjectId(id)
481 .setJsonBody(jsonObject);
482 }
483
484 GraphObject graphResponse = executeGraphRequest(graphRequestBuilder);
485 itemResult.status = graphResponse.statusCode;
486
487
488 itemResult.itemName = graphResponse.optString("id") + ".EML";
489 itemResult.etag = graphResponse.optString("changeKey");
490
491
492 } catch (JSONException e) {
493 throw new IOException(e);
494 }
495
496
497
498 return itemResult;
499 }
500
501 }
502
503 private String convertHtmlToText(String htmlText) {
504 StringBuilder builder = new StringBuilder();
505
506 HtmlCleaner cleaner = new HtmlCleaner();
507 cleaner.getProperties().setDeserializeEntities(true);
508 try {
509 TagNode node = cleaner.clean(new StringReader(htmlText));
510 for (TagNode childNode : node.getAllElementsList(true)) {
511 builder.append(childNode.getText());
512 }
513 } catch (IOException e) {
514 LOGGER.error("Error converting html to text", e);
515 }
516 return builder.toString();
517 }
518
519 private VProperty convertBodyToVproperty(String propertyName, GraphObject graphObject) {
520 JSONObject jsonBody = graphObject.optJSONObject("body");
521 String bodyPreview = graphObject.optString("bodyPreview");
522
523 if (jsonBody == null) {
524 return new VProperty(propertyName, bodyPreview);
525 } else {
526
527 String content = jsonBody.optString("content");
528 String contentType = jsonBody.optString("contentType");
529 VProperty vProperty;
530
531 if ("text".equals(contentType)) {
532 vProperty = new VProperty(propertyName, content);
533 } else {
534
535 if (content != null) {
536 vProperty = new VProperty(propertyName, convertHtmlToText(content));
537
538 content = content.replace("\n", "").replace("\r", "");
539 vProperty.addParam("ALTREP", "data:text/html," + URIUtil.encodeWithinQuery(content));
540 } else {
541 vProperty = new VProperty(propertyName, null);
542 }
543
544 }
545 return vProperty;
546 }
547 }
548
549 private VProperty convertDateTimeTimeZoneToVproperty(String vPropertyName, JSONObject jsonDateTimeTimeZone) throws DavMailException {
550
551 if (jsonDateTimeTimeZone != null) {
552 String timeZone = jsonDateTimeTimeZone.optString("timeZone");
553 String dateTime = jsonDateTimeTimeZone.optString("dateTime");
554 VProperty vProperty = new VProperty(vPropertyName, convertDateFromExchange(dateTime));
555 vProperty.addParam("TZID", timeZone);
556 return vProperty;
557 }
558 return new VProperty(vPropertyName, null);
559 }
560
561 private VProperty convertEmailAddressToVproperty(String propertyName, JSONObject jsonEmailAddress) {
562 VProperty attendeeProperty = new VProperty(propertyName, "mailto:" + jsonEmailAddress.optString("address"));
563 attendeeProperty.addParam("CN", jsonEmailAddress.optString("name"));
564 return attendeeProperty;
565 }
566
567 private String convertDateTimeTimeZoneToTaskDate(Date exchangeDateValue) {
568 String zuluDateValue = null;
569 if (exchangeDateValue != null) {
570 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
571 dateFormat.setTimeZone(GMT_TIMEZONE);
572 zuluDateValue = dateFormat.format(exchangeDateValue);
573 }
574 return zuluDateValue;
575
576 }
577
578 protected class Contact extends ExchangeSession.Contact {
579
580 FolderId folderId;
581 String id;
582
583 protected Contact(GraphObject response) throws DavMailException {
584 id = response.optString("id");
585 etag = response.optString("changeKey");
586
587 displayName = response.optString("displayname");
588
589 itemName = StringUtil.decodeUrlcompname(response.optString("urlcompname"));
590
591 if (itemName == null) {
592 itemName = StringUtil.base64ToUrl(id) + ".EML";
593 }
594
595 for (String attributeName : ExchangeSession.CONTACT_ATTRIBUTES) {
596 if (!attributeName.startsWith("smtpemail")) {
597 String value = response.optString(attributeName);
598 if (value != null && !value.isEmpty()) {
599 if ("bday".equals(attributeName) || "anniversary".equals(attributeName) || "lastmodified".equals(attributeName) || "datereceived".equals(attributeName)) {
600 value = convertDateFromExchange(value);
601 }
602 put(attributeName, value);
603 }
604 }
605 }
606
607
608
609
610
611
612 JSONArray emailAddresses = response.optJSONArray("emailAddresses");
613 for (int i = 0; i < emailAddresses.length(); i++) {
614 JSONObject emailAddress = emailAddresses.optJSONObject(i);
615 if (emailAddress != null) {
616 String email = emailAddress.optString("address");
617 String type = emailAddress.optString("type");
618 if (email != null && !email.isEmpty()) {
619 if ("other".equals(type)) {
620 put("smtpemail3", email);
621 } else if ("personal".equals(type)) {
622 put("smtpemail2", email);
623 } else if ("work".equals(type)) {
624 put("smtpemail1", email);
625 }
626 }
627 }
628 }
629
630 for (int i = 0; i < emailAddresses.length(); i++) {
631 JSONObject emailAddress = emailAddresses.optJSONObject(i);
632 if (emailAddress != null) {
633 String email = emailAddress.optString("address");
634 String type = emailAddress.optString("type");
635 if (email != null && !email.isEmpty()) {
636 if ("unknown".equals(type)) {
637 if (get("smtpemail1") == null) {
638 put("smtpemail1", email);
639 } else if (get("smtpemail2") == null) {
640 put("smtpemail2", email);
641 } else if (get("smtpemail3") == null) {
642 put("smtpemail3", email);
643 }
644 }
645 }
646 }
647 }
648 }
649
650 protected Contact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) {
651 super(folderPath, itemName, properties, etag, noneMatch);
652 }
653
654
655
656
657 protected Contact() {
658 }
659
660
661
662
663
664
665
666
667 @Override
668 public ItemResult createOrUpdate() throws IOException {
669
670 FolderId folderId = getFolderId(folderPath);
671 String id = null;
672 String currentEtag = null;
673 JSONObject jsonContact = getContactIfExists(folderId, itemName);
674 if (jsonContact != null) {
675 id = jsonContact.optString("id", null);
676 currentEtag = new GraphObject(jsonContact).optString("changeKey");
677 }
678
679 ItemResult itemResult = new ItemResult();
680 if ("*".equals(noneMatch)) {
681
682 if (id != null) {
683 itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
684 return itemResult;
685 }
686 } else if (etag != null) {
687
688 if (id == null || !etag.equals(currentEtag)) {
689 itemResult.status = HttpStatus.SC_PRECONDITION_FAILED;
690 return itemResult;
691 }
692 }
693
694 try {
695 JSONObject jsonObject = new JSONObject();
696 GraphObject graphObject = new GraphObject(jsonObject);
697 for (Map.Entry<String, String> entry : entrySet()) {
698 if ("keywords".equals(entry.getKey())) {
699 graphObject.setCategories(entry.getValue());
700 } else if ("bday".equals(entry.getKey())) {
701 graphObject.put(entry.getKey(), convertZuluToIso(entry.getValue()));
702 } else if ("anniversary".equals(entry.getKey())) {
703 graphObject.put(entry.getKey(), convertZuluToDate(entry.getValue()));
704 } else if ("photo".equals(entry.getKey())) {
705
706 graphObject.put("haspicture", (get("photo") != null) ? "true" : "false");
707 } else if (!entry.getKey().startsWith("email") && !entry.getKey().startsWith("smtpemail")
708 && !"usersmimecertificate".equals(entry.getKey())
709 && !"msexchangecertificate".equals(entry.getKey())
710 && !"pager".equals(entry.getKey()) && !"otherTelephone".equals(entry.getKey())
711 ) {
712
713 graphObject.put(entry.getKey(), entry.getValue());
714 }
715 }
716
717
718 String pager = get("pager");
719 if (pager == null) {
720 pager = get("otherTelephone");
721 }
722
723 graphObject.put("pager", pager);
724
725
726
727 graphObject.put("urlcompname", convertItemNameToEML(itemName));
728
729
730 JSONArray emailAddresses = new JSONArray();
731 String smtpemail1 = get("smtpemail1");
732 if (smtpemail1 != null) {
733 JSONObject emailAddress = new JSONObject();
734 emailAddress.put("address", smtpemail1);
735 emailAddress.put("type", "work");
736 emailAddresses.put(emailAddress);
737 }
738
739 String smtpemail2 = get("smtpemail2");
740 if (smtpemail2 != null) {
741 JSONObject emailAddress = new JSONObject();
742 emailAddress.put("address", smtpemail2);
743 emailAddress.put("type", "personal");
744 emailAddresses.put(emailAddress);
745 }
746
747 String smtpemail3 = get("smtpemail3");
748 if (smtpemail3 != null) {
749 JSONObject emailAddress = new JSONObject();
750 emailAddress.put("address", smtpemail3);
751 emailAddress.put("type", "other");
752 emailAddresses.put(emailAddress);
753 }
754
755 graphObject.put("emailAddresses", emailAddresses);
756
757 GraphRequestBuilder graphRequestBuilder = new GraphRequestBuilder();
758 if (id == null) {
759 graphRequestBuilder.setMethod(HttpPost.METHOD_NAME)
760 .setMailbox(folderId.mailbox)
761 .setObjectType("contactFolders")
762 .setObjectId(folderId.id)
763 .setChildType("contacts")
764 .setJsonBody(jsonObject);
765 } else {
766 graphRequestBuilder.setMethod(HttpPatch.METHOD_NAME)
767 .setMailbox(folderId.mailbox)
768 .setObjectType("contactFolders")
769 .setObjectId(folderId.id)
770 .setChildType("contacts")
771 .setChildId(id)
772 .setJsonBody(jsonObject);
773 }
774
775 GraphObject graphResponse = executeGraphRequest(graphRequestBuilder);
776
777 if (LOGGER.isDebugEnabled()) {
778 LOGGER.debug(graphResponse.toString(4));
779 }
780
781 itemResult.status = graphResponse.statusCode;
782
783 updatePhoto(folderId, graphResponse.optString("id"));
784
785
786 graphResponse = new GraphObject(getContactIfExists(folderId, itemName));
787
788 itemResult.itemName = graphResponse.optString("id");
789 itemResult.etag = graphResponse.optString("etag");
790
791 } catch (JSONException e) {
792 throw new IOException(e);
793 }
794 if (itemResult.status == HttpStatus.SC_CREATED) {
795 LOGGER.debug("Created contact " + getHref());
796 } else {
797 LOGGER.debug("Updated contact " + getHref());
798 }
799
800 return itemResult;
801 }
802
803 private void updatePhoto(FolderId folderId, String contactId) throws IOException {
804 String photo = get("photo");
805 if (photo != null) {
806
807 byte[] resizedImageBytes = IOUtil.resizeImage(IOUtil.decodeBase64(photo), 90);
808
809 JSONObject jsonResponse = executeJsonRequest(new GraphRequestBuilder()
810 .setMethod(HttpPut.METHOD_NAME)
811 .setMailbox(folderId.mailbox)
812 .setObjectType("contactFolders")
813 .setObjectId(folderId.id)
814 .setChildType("contacts")
815 .setChildId(contactId)
816 .setChildSuffix("photo/$value")
817 .setContentType("image/jpeg")
818 .setMimeContent(resizedImageBytes));
819
820 if (LOGGER.isDebugEnabled()) {
821 LOGGER.debug(jsonResponse);
822 }
823 } else {
824 executeJsonRequest(new GraphRequestBuilder()
825 .setMethod(HttpDelete.METHOD_NAME)
826 .setMailbox(folderId.mailbox)
827 .setObjectType("contactFolders")
828 .setObjectId(folderId.id)
829 .setChildType("contacts")
830 .setChildId(contactId)
831 .setChildSuffix("photo"));
832 }
833 }
834 }
835
836
837
838
839
840
841
842
843
844 private String convertZuluToIso(String value) {
845 if (value != null) {
846 return value.replace(".000Z", "Z");
847 } else {
848 return value;
849 }
850 }
851
852
853
854
855
856
857
858
859
860
861
862 private String convertZuluToDate(String value) {
863 if (value != null && value.contains("T")) {
864 return value.substring(0, value.indexOf("T"));
865 } else {
866 return value;
867 }
868 }
869
870
871 @SuppressWarnings("SpellCheckingInspection")
872 public enum WellKnownFolderName {
873 archive,
874 deleteditems,
875 calendar, contacts, tasks,
876 drafts, inbox, outbox, sentitems, junkemail,
877 msgfolderroot,
878 searchfolders
879 }
880
881
882 protected static HashMap<String, String> wellKnownFolderMap = new HashMap<>();
883
884 static {
885 wellKnownFolderMap.put(WellKnownFolderName.inbox.name(), ExchangeSession.INBOX);
886 wellKnownFolderMap.put(WellKnownFolderName.archive.name(), ExchangeSession.ARCHIVE);
887 wellKnownFolderMap.put(WellKnownFolderName.drafts.name(), ExchangeSession.DRAFTS);
888 wellKnownFolderMap.put(WellKnownFolderName.junkemail.name(), ExchangeSession.JUNK);
889 wellKnownFolderMap.put(WellKnownFolderName.sentitems.name(), ExchangeSession.SENT);
890 wellKnownFolderMap.put(WellKnownFolderName.deleteditems.name(), ExchangeSession.TRASH);
891 }
892
893 protected static final HashSet<FieldURI> IMAP_MESSAGE_ATTRIBUTES = new HashSet<>();
894
895 static {
896
897 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("permanenturl"));
898 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("urlcompname"));
899 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("uid"));
900 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("messageSize"));
901 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("imapUid"));
902 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("junk"));
903 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("flagStatus"));
904 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("messageFlags"));
905 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("lastVerbExecuted"));
906 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("read"));
907 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("deleted"));
908 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("date"));
909 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("lastmodified"));
910
911 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("contentclass"));
912 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("keywords"));
913
914
915 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("to"));
916 IMAP_MESSAGE_ATTRIBUTES.add(Field.get("messageheaders"));
917 }
918
919 protected static final HashSet<FieldURI> CONTACT_ATTRIBUTES = new HashSet<>();
920
921 static {
922 CONTACT_ATTRIBUTES.add(Field.get("imapUid"));
923 CONTACT_ATTRIBUTES.add(Field.get("etag"));
924 CONTACT_ATTRIBUTES.add(Field.get("urlcompname"));
925
926 CONTACT_ATTRIBUTES.add(Field.get("extensionattribute1"));
927 CONTACT_ATTRIBUTES.add(Field.get("extensionattribute2"));
928 CONTACT_ATTRIBUTES.add(Field.get("extensionattribute3"));
929 CONTACT_ATTRIBUTES.add(Field.get("extensionattribute4"));
930 CONTACT_ATTRIBUTES.add(Field.get("bday"));
931 CONTACT_ATTRIBUTES.add(Field.get("anniversary"));
932 CONTACT_ATTRIBUTES.add(Field.get("businesshomepage"));
933 CONTACT_ATTRIBUTES.add(Field.get("personalHomePage"));
934 CONTACT_ATTRIBUTES.add(Field.get("cn"));
935 CONTACT_ATTRIBUTES.add(Field.get("co"));
936 CONTACT_ATTRIBUTES.add(Field.get("department"));
937
938
939
940 CONTACT_ATTRIBUTES.add(Field.get("facsimiletelephonenumber"));
941 CONTACT_ATTRIBUTES.add(Field.get("givenName"));
942 CONTACT_ATTRIBUTES.add(Field.get("homeCity"));
943 CONTACT_ATTRIBUTES.add(Field.get("homeCountry"));
944 CONTACT_ATTRIBUTES.add(Field.get("homePhone"));
945 CONTACT_ATTRIBUTES.add(Field.get("homePostalCode"));
946 CONTACT_ATTRIBUTES.add(Field.get("homeState"));
947 CONTACT_ATTRIBUTES.add(Field.get("homeStreet"));
948 CONTACT_ATTRIBUTES.add(Field.get("homepostofficebox"));
949 CONTACT_ATTRIBUTES.add(Field.get("l"));
950 CONTACT_ATTRIBUTES.add(Field.get("manager"));
951 CONTACT_ATTRIBUTES.add(Field.get("mobile"));
952 CONTACT_ATTRIBUTES.add(Field.get("namesuffix"));
953 CONTACT_ATTRIBUTES.add(Field.get("nickname"));
954 CONTACT_ATTRIBUTES.add(Field.get("o"));
955 CONTACT_ATTRIBUTES.add(Field.get("pager"));
956 CONTACT_ATTRIBUTES.add(Field.get("personaltitle"));
957 CONTACT_ATTRIBUTES.add(Field.get("postalcode"));
958 CONTACT_ATTRIBUTES.add(Field.get("postofficebox"));
959 CONTACT_ATTRIBUTES.add(Field.get("profession"));
960 CONTACT_ATTRIBUTES.add(Field.get("roomnumber"));
961 CONTACT_ATTRIBUTES.add(Field.get("secretarycn"));
962 CONTACT_ATTRIBUTES.add(Field.get("sn"));
963 CONTACT_ATTRIBUTES.add(Field.get("spousecn"));
964 CONTACT_ATTRIBUTES.add(Field.get("st"));
965 CONTACT_ATTRIBUTES.add(Field.get("street"));
966 CONTACT_ATTRIBUTES.add(Field.get("telephoneNumber"));
967 CONTACT_ATTRIBUTES.add(Field.get("title"));
968 CONTACT_ATTRIBUTES.add(Field.get("description"));
969 CONTACT_ATTRIBUTES.add(Field.get("im"));
970 CONTACT_ATTRIBUTES.add(Field.get("middlename"));
971 CONTACT_ATTRIBUTES.add(Field.get("lastmodified"));
972 CONTACT_ATTRIBUTES.add(Field.get("otherstreet"));
973 CONTACT_ATTRIBUTES.add(Field.get("otherstate"));
974 CONTACT_ATTRIBUTES.add(Field.get("otherpostofficebox"));
975 CONTACT_ATTRIBUTES.add(Field.get("otherpostalcode"));
976 CONTACT_ATTRIBUTES.add(Field.get("othercountry"));
977 CONTACT_ATTRIBUTES.add(Field.get("othercity"));
978 CONTACT_ATTRIBUTES.add(Field.get("haspicture"));
979 CONTACT_ATTRIBUTES.add(Field.get("keywords"));
980 CONTACT_ATTRIBUTES.add(Field.get("othermobile"));
981 CONTACT_ATTRIBUTES.add(Field.get("otherTelephone"));
982 CONTACT_ATTRIBUTES.add(Field.get("gender"));
983 CONTACT_ATTRIBUTES.add(Field.get("private"));
984 CONTACT_ATTRIBUTES.add(Field.get("sensitivity"));
985 CONTACT_ATTRIBUTES.add(Field.get("fburl"));
986
987
988 }
989
990 private static final Set<FieldURI> TODO_PROPERTIES = new HashSet<>();
991
992 static {
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008 }
1009
1010
1011
1012
1013 protected static final String EVENT_SELECT = "allowNewTimeProposals,attendees,body,bodyPreview,cancelledOccurrences,categories,changeKey,createdDateTime,end,exceptionOccurrences,hasAttachments,iCalUId,id,importance,isAllDay,isOnlineMeeting,isOrganizer,isReminderOn,lastModifiedDateTime,location,organizer,originalStart,recurrence,reminderMinutesBeforeStart,responseRequested,sensitivity,showAs,start,subject,type";
1014 protected static final HashSet<FieldURI> EVENT_ATTRIBUTES = new HashSet<>();
1015
1016 static {
1017
1018 }
1019
1020 protected static class FolderId {
1021 protected String mailbox;
1022 protected String id;
1023 protected String parentFolderId;
1024 protected String folderClass;
1025
1026 public FolderId() {
1027 }
1028
1029 public FolderId(String mailbox, String id) {
1030 this.mailbox = mailbox;
1031 this.id = id;
1032 }
1033
1034 public FolderId(String mailbox, String id, String folderClass) {
1035 this.mailbox = mailbox;
1036 this.id = id;
1037 this.folderClass = folderClass;
1038 }
1039
1040 public FolderId(String mailbox, WellKnownFolderName wellKnownFolderName) {
1041 this.mailbox = mailbox;
1042 this.id = wellKnownFolderName.name();
1043 }
1044
1045 public FolderId(String mailbox, WellKnownFolderName wellKnownFolderName, String folderClass) {
1046 this.mailbox = mailbox;
1047 this.id = wellKnownFolderName.name();
1048 this.folderClass = folderClass;
1049 }
1050 }
1051
1052 HttpClientAdapter httpClient;
1053 O365Token token;
1054
1055
1056
1057
1058 protected static final HashSet<FieldURI> FOLDER_PROPERTIES = new HashSet<>();
1059
1060 static {
1061
1062 FOLDER_PROPERTIES.add(Field.get("lastmodified"));
1063 FOLDER_PROPERTIES.add(Field.get("folderclass"));
1064 FOLDER_PROPERTIES.add(Field.get("ctag"));
1065 FOLDER_PROPERTIES.add(Field.get("uidNext"));
1066 }
1067
1068 public GraphExchangeSession(HttpClientAdapter httpClient, O365Token token, String userName) throws IOException {
1069 this.httpClient = httpClient;
1070 this.token = token;
1071 this.userName = userName;
1072
1073 buildSessionInfo(httpClient.getUri());
1074 }
1075
1076 @Override
1077 public void close() {
1078 httpClient.close();
1079 }
1080
1081
1082
1083
1084
1085
1086
1087
1088 @Override
1089 public String formatSearchDate(Date date) {
1090 SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_T_HHMMSS_Z, Locale.ENGLISH);
1091 dateFormatter.setTimeZone(GMT_TIMEZONE);
1092 return dateFormatter.format(date);
1093 }
1094
1095 @Override
1096 protected void buildSessionInfo(URI uri) throws IOException {
1097
1098 currentMailboxPath = "/users/" + userName.toLowerCase();
1099
1100
1101 email = userName;
1102 alias = userName.substring(0, email.indexOf("@"));
1103
1104 LOGGER.debug("Current user email is " + email + ", alias is " + alias);
1105 }
1106
1107 @Override
1108 public ExchangeSession.Message createMessage(String folderPath, String messageName, HashMap<String, String> properties, MimeMessage mimeMessage) throws IOException {
1109 byte[] mimeContent = IOUtil.encodeBase64(mimeMessage);
1110
1111
1112
1113 boolean isDraft = properties != null && ("8".equals(properties.get("draft")) || "9".equals(properties.get("draft")));
1114
1115
1116
1117 FolderId folderId = getFolderId(folderPath);
1118
1119
1120 GraphObject graphResponse = executeGraphRequest(new GraphRequestBuilder()
1121 .setMethod(HttpPost.METHOD_NAME)
1122 .setContentType("text/plain")
1123 .setMimeContent(mimeContent)
1124 .setChildType("messages"));
1125 if (isDraft) {
1126 try {
1127 graphResponse = executeGraphRequest(new GraphRequestBuilder().setMethod(HttpPost.METHOD_NAME)
1128 .setMailbox(folderId.mailbox)
1129 .setObjectType("messages")
1130 .setObjectId(graphResponse.optString("id"))
1131 .setChildType("move")
1132 .setJsonBody(new JSONObject().put("destinationId", folderId.id)));
1133
1134
1135 applyMessageProperties(graphResponse, properties);
1136 graphResponse = executeGraphRequest(new GraphRequestBuilder()
1137 .setMethod(HttpPatch.METHOD_NAME)
1138 .setMailbox(folderId.mailbox)
1139 .setObjectType("messages")
1140 .setObjectId(graphResponse.optString("id"))
1141 .setJsonBody(graphResponse.jsonObject));
1142 } catch (JSONException e) {
1143 throw new IOException(e);
1144 }
1145 } else {
1146 String draftMessageId = null;
1147 try {
1148
1149 draftMessageId = graphResponse.getString("id");
1150
1151
1152
1153 graphResponse.put("singleValueExtendedProperties",
1154 new JSONArray().put(new JSONObject()
1155 .put("id", Field.get("messageFlags").getGraphId())
1156 .put("value", "4")));
1157 applyMessageProperties(graphResponse, properties);
1158
1159
1160 graphResponse = executeGraphRequest(new GraphRequestBuilder()
1161 .setMethod(HttpPost.METHOD_NAME)
1162 .setMailbox(folderId.mailbox)
1163 .setObjectType("mailFolders")
1164 .setObjectId(folderId.id)
1165 .setJsonBody(graphResponse.jsonObject)
1166 .setChildType("messages"));
1167
1168 } catch (JSONException e) {
1169 throw new IOException(e);
1170 } finally {
1171
1172 if (draftMessageId != null) {
1173 executeJsonRequest(new GraphRequestBuilder()
1174 .setMethod(HttpDelete.METHOD_NAME)
1175 .setObjectType("messages")
1176 .setObjectId(draftMessageId));
1177 }
1178 }
1179
1180 }
1181 return buildMessage(executeJsonRequest(new GraphRequestBuilder()
1182 .setMethod(HttpGet.METHOD_NAME)
1183 .setObjectType("messages")
1184 .setMailbox(folderId.mailbox)
1185 .setObjectId(graphResponse.optString("id"))
1186 .setExpandFields(IMAP_MESSAGE_ATTRIBUTES)));
1187 }
1188
1189 private void applyMessageProperties(GraphObject graphResponse, Map<String, String> properties) throws JSONException {
1190 if (properties != null) {
1191 for (Map.Entry<String, String> entry : properties.entrySet()) {
1192
1193 if ("read".equals(entry.getKey())) {
1194 graphResponse.put(entry.getKey(), "1".equals(entry.getValue()));
1195 } else if ("junk".equals(entry.getKey())) {
1196 graphResponse.put(entry.getKey(), entry.getValue());
1197 } else if ("flagged".equals(entry.getKey())) {
1198 graphResponse.put("flagStatus", entry.getValue());
1199 } else if ("answered".equals(entry.getKey())) {
1200 graphResponse.put("lastVerbExecuted", entry.getValue());
1201 if ("102".equals(entry.getValue())) {
1202 graphResponse.put("iconIndex", "261");
1203 }
1204 } else if ("forwarded".equals(entry.getKey())) {
1205 graphResponse.put("lastVerbExecuted", entry.getValue());
1206 if ("104".equals(entry.getValue())) {
1207 graphResponse.put("iconIndex", "262");
1208 }
1209 } else if ("deleted".equals(entry.getKey())) {
1210 graphResponse.put(entry.getKey(), entry.getValue());
1211 } else if ("datereceived".equals(entry.getKey())) {
1212 graphResponse.put(entry.getKey(), entry.getValue());
1213 } else if ("keywords".equals(entry.getKey())) {
1214 graphResponse.setCategories(entry.getValue());
1215 }
1216 }
1217 }
1218 }
1219
1220 class Message extends ExchangeSession.Message {
1221 protected FolderId folderId;
1222 protected String id;
1223 protected String changeKey;
1224
1225 @Override
1226 public String getPermanentId() {
1227 return id;
1228 }
1229
1230 @Override
1231 protected InputStream getMimeHeaders() {
1232 InputStream result = null;
1233 try {
1234 HashSet<FieldURI> expandFields = new HashSet<>();
1235
1236 expandFields.add(Field.get("from"));
1237 expandFields.add(Field.get("messageheaders"));
1238
1239 JSONObject response = executeJsonRequest(new GraphRequestBuilder()
1240 .setMethod(HttpGet.METHOD_NAME)
1241 .setMailbox(folderId.mailbox)
1242 .setObjectType("messages")
1243 .setObjectId(id)
1244 .setExpandFields(expandFields));
1245
1246 String messageHeaders = null;
1247
1248 JSONArray singleValueExtendedProperties = response.optJSONArray("singleValueExtendedProperties");
1249 if (singleValueExtendedProperties != null) {
1250 for (int i = 0; i < singleValueExtendedProperties.length(); i++) {
1251 try {
1252 JSONObject responseValue = singleValueExtendedProperties.getJSONObject(i);
1253 String responseId = responseValue.optString("id");
1254 if (Field.get("messageheaders").getGraphId().equals(responseId)) {
1255 messageHeaders = responseValue.optString("value");
1256 }
1257 } catch (JSONException e) {
1258 LOGGER.warn("Error parsing json response value");
1259 }
1260 }
1261 }
1262
1263
1264
1265 if (messageHeaders != null
1266
1267 && messageHeaders.toLowerCase().contains("message-id:")) {
1268
1269 if (!messageHeaders.contains("From:")) {
1270
1271 String from = response.optString("from");
1272 messageHeaders = "From: " + from + '\n' + messageHeaders;
1273 }
1274
1275 result = new ByteArrayInputStream(messageHeaders.getBytes(StandardCharsets.UTF_8));
1276 }
1277 } catch (Exception e) {
1278 LOGGER.warn(e.getMessage());
1279 }
1280
1281 return result;
1282
1283 }
1284 }
1285
1286 private Message buildMessage(JSONObject response) {
1287 Message message = new Message();
1288
1289 try {
1290
1291 message.id = response.getString("id");
1292 message.changeKey = response.getString("changeKey");
1293
1294 message.read = response.getBoolean("isRead");
1295 message.draft = response.getBoolean("isDraft");
1296 message.date = convertDateFromExchange(response.getString("receivedDateTime"));
1297
1298 String lastmodified = convertDateFromExchange(response.optString("lastModifiedDateTime"));
1299 message.recent = !message.read && lastmodified != null && lastmodified.equals(message.date);
1300
1301 } catch (JSONException | DavMailException e) {
1302 LOGGER.warn("Error parsing message " + e.getMessage(), e);
1303 }
1304
1305 JSONArray singleValueExtendedProperties = response.optJSONArray("singleValueExtendedProperties");
1306 if (singleValueExtendedProperties != null) {
1307 for (int i = 0; i < singleValueExtendedProperties.length(); i++) {
1308 try {
1309 JSONObject responseValue = singleValueExtendedProperties.getJSONObject(i);
1310 String responseId = responseValue.optString("id");
1311 if (Field.get("imapUid").getGraphId().equals(responseId)) {
1312 message.imapUid = responseValue.getLong("value");
1313
1314
1315
1316
1317
1318
1319
1320 } else if ("Integer 0xe08".equals(responseId)) {
1321 message.size = responseValue.getInt("value");
1322 } else if ("Binary 0xff9".equals(responseId)) {
1323 message.uid = responseValue.getString("value");
1324
1325 } else if ("String 0x670E".equals(responseId)) {
1326
1327 message.permanentUrl = responseValue.getString("value");
1328 } else if ("Integer 0x1081".equals(responseId)) {
1329 String lastVerbExecuted = responseValue.getString("value");
1330 message.answered = "102".equals(lastVerbExecuted) || "103".equals(lastVerbExecuted);
1331 message.forwarded = "104".equals(lastVerbExecuted);
1332 } else if ("String {00020386-0000-0000-C000-000000000046} Name content-class".equals(responseId)) {
1333
1334 message.contentClass = responseValue.getString("value");
1335 } else if ("Integer 0x1083".equals(responseId)) {
1336 message.junk = "1".equals(responseValue.getString("value"));
1337 } else if ("Integer 0x1090".equals(responseId)) {
1338 message.flagged = "2".equals(responseValue.getString("value"));
1339 } else if ("Integer {00062008-0000-0000-c000-000000000046} Name 0x8570".equals(responseId)) {
1340 message.deleted = "1".equals(responseValue.getString("value"));
1341 }
1342
1343 } catch (JSONException e) {
1344 LOGGER.warn("Error parsing json response value");
1345 }
1346 }
1347 }
1348
1349 JSONArray multiValueExtendedProperties = response.optJSONArray("multiValueExtendedProperties");
1350 if (multiValueExtendedProperties != null) {
1351 for (int i = 0; i < multiValueExtendedProperties.length(); i++) {
1352 try {
1353 JSONObject responseValue = multiValueExtendedProperties.getJSONObject(i);
1354 String responseId = responseValue.optString("id");
1355 if (Field.get("keywords").getGraphId().equals(responseId)) {
1356 JSONArray keywordsJsonArray = responseValue.getJSONArray("value");
1357 HashSet<String> keywords = new HashSet<>();
1358 for (int j = 0; j < keywordsJsonArray.length(); j++) {
1359 keywords.add(keywordsJsonArray.getString(j));
1360 }
1361 message.keywords = StringUtil.join(keywords, ",");
1362 }
1363
1364 } catch (JSONException e) {
1365 LOGGER.warn("Error parsing json response value");
1366 }
1367 }
1368 }
1369
1370 if (LOGGER.isDebugEnabled()) {
1371 StringBuilder buffer = new StringBuilder();
1372 buffer.append("Message");
1373 if (message.imapUid != 0) {
1374 buffer.append(" IMAP uid: ").append(message.imapUid);
1375 }
1376 if (message.uid != null) {
1377 buffer.append(" uid: ").append(message.uid);
1378 }
1379 buffer.append(" ItemId: ").append(message.id);
1380 buffer.append(" ChangeKey: ").append(message.changeKey);
1381 LOGGER.debug(buffer.toString());
1382 }
1383
1384 return message;
1385
1386 }
1387
1388
1389
1390
1391
1392
1393
1394
1395 protected String convertDateFromExchange(String exchangeDateValue) throws DavMailException {
1396
1397 if (exchangeDateValue == null) {
1398 return null;
1399 } else {
1400 StringBuilder buffer = new StringBuilder();
1401 if (exchangeDateValue.length() >= 25 || exchangeDateValue.length() == 20 || exchangeDateValue.length() == 10) {
1402 for (int i = 0; i < exchangeDateValue.length(); i++) {
1403 if (i == 4 || i == 7 || i == 13 || i == 16) {
1404 i++;
1405 } else if (exchangeDateValue.length() >= 25 && i == 19) {
1406 i = exchangeDateValue.length() - 1;
1407 }
1408 buffer.append(exchangeDateValue.charAt(i));
1409 }
1410 if (exchangeDateValue.length() == 10) {
1411 buffer.append("T000000Z");
1412 }
1413 } else {
1414 throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue);
1415 }
1416 return buffer.toString();
1417 }
1418 }
1419
1420 @Override
1421 public void updateMessage(ExchangeSession.Message message, Map<String, String> properties) throws IOException {
1422 try {
1423 GraphObject graphObject = new GraphObject(new JSONObject());
1424
1425 applyMessageProperties(graphObject, properties);
1426 executeJsonRequest(new GraphRequestBuilder()
1427 .setMethod(HttpPatch.METHOD_NAME)
1428 .setMailbox(((Message) message).folderId.mailbox)
1429 .setObjectType("messages")
1430 .setObjectId(((Message) message).id)
1431 .setJsonBody(graphObject.jsonObject));
1432 } catch (JSONException e) {
1433 throw new IOException(e);
1434 }
1435 }
1436
1437 @Override
1438 public void deleteMessage(ExchangeSession.Message message) throws IOException {
1439 executeJsonRequest(new GraphRequestBuilder()
1440 .setMethod(HttpDelete.METHOD_NAME)
1441 .setMailbox(((Message) message).folderId.mailbox)
1442 .setObjectType("messages")
1443 .setObjectId(((Message) message).id));
1444 }
1445
1446 @Override
1447 protected byte[] getContent(ExchangeSession.Message message) throws IOException {
1448 GraphRequestBuilder graphRequestBuilder = new GraphRequestBuilder()
1449 .setMethod(HttpGet.METHOD_NAME)
1450 .setMailbox(((Message) message).folderId.mailbox)
1451 .setObjectType("messages")
1452 .setObjectId(message.getPermanentId())
1453 .setChildType("$value")
1454 .setAccessToken(token.getAccessToken());
1455
1456
1457 byte[] mimeContent;
1458 try (
1459 CloseableHttpResponse response = httpClient.execute(graphRequestBuilder.build());
1460 InputStream inputStream = response.getEntity().getContent()
1461 ) {
1462
1463 FilterInputStream filterInputStream = new FilterInputStream(inputStream) {
1464 int totalCount;
1465 int lastLogCount;
1466
1467 @Override
1468 public int read(byte[] buffer, int offset, int length) throws IOException {
1469 int count = super.read(buffer, offset, length);
1470 totalCount += count;
1471 if (totalCount - lastLogCount > 1024 * 128) {
1472 DavGatewayTray.debug(new BundleMessage("LOG_DOWNLOAD_PROGRESS", String.valueOf(totalCount / 1024), message.getPermanentId()));
1473 DavGatewayTray.switchIcon();
1474 lastLogCount = totalCount;
1475 }
1476
1477
1478
1479 return count;
1480 }
1481 };
1482 if (HttpClientAdapter.isGzipEncoded(response)) {
1483 mimeContent = IOUtil.readFully(new GZIPInputStream(filterInputStream));
1484 } else {
1485 mimeContent = IOUtil.readFully(filterInputStream);
1486 }
1487 }
1488 return mimeContent;
1489 }
1490
1491 @Override
1492 public MessageList searchMessages(String folderName, Set<String> attributes, Condition condition) throws IOException {
1493 MessageList messageList = new MessageList();
1494 FolderId folderId = getFolderId(folderName);
1495
1496 GraphRequestBuilder httpRequestBuilder = new GraphRequestBuilder()
1497 .setMethod(HttpGet.METHOD_NAME)
1498 .setMailbox(folderId.mailbox)
1499 .setObjectType("mailFolders")
1500 .setObjectId(folderId.id)
1501 .setChildType("messages")
1502 .setExpandFields(IMAP_MESSAGE_ATTRIBUTES);
1503 LOGGER.debug("searchMessages " + folderId.mailbox + " " + folderName);
1504 if (condition != null && !condition.isEmpty()) {
1505 StringBuilder filter = new StringBuilder();
1506 condition.appendTo(filter);
1507 LOGGER.debug("search filter " + filter);
1508 httpRequestBuilder.setFilter(filter.toString());
1509 }
1510
1511 GraphIterator graphIterator = executeSearchRequest(httpRequestBuilder);
1512
1513 while (graphIterator.hasNext()) {
1514 Message message = buildMessage(graphIterator.next());
1515 message.messageList = messageList;
1516 message.folderId = folderId;
1517 messageList.add(message);
1518 }
1519 Collections.sort(messageList);
1520 return messageList;
1521 }
1522
1523 static class AttributeCondition extends ExchangeSession.AttributeCondition {
1524
1525 protected AttributeCondition(String attributeName, Operator operator, String value) {
1526 super(attributeName, operator, value);
1527 }
1528
1529 protected FieldURI getFieldURI() {
1530 FieldURI fieldURI = Field.get(attributeName);
1531
1532
1533 if (fieldURI == null) {
1534 throw new IllegalArgumentException("Unknown field: " + attributeName);
1535 }
1536 return fieldURI;
1537 }
1538
1539 private String convertOperator(Operator operator) {
1540 if (Operator.IsEqualTo.equals(operator)) {
1541 return "eq";
1542 }
1543
1544 return operator.toString();
1545 }
1546
1547 @Override
1548 public void appendTo(StringBuilder buffer) {
1549 FieldURI fieldURI = getFieldURI();
1550 if ("String {00020386-0000-0000-c000-000000000046} Name to".equals(fieldURI.getGraphId())) {
1551
1552 buffer.append("singleValueExtendedProperties/Any(ep: ep/id eq 'String {00020386-0000-0000-c000-000000000046} Name to' and contains(ep/value,'")
1553 .append(StringUtil.escapeQuotes(value)).append("'))");
1554 } else if (Operator.StartsWith.equals(operator)) {
1555 buffer.append("startswith(").append(getFieldURI().getGraphId()).append(",'").append(StringUtil.escapeQuotes(value)).append("')");
1556 } else if (Operator.Contains.equals(operator)) {
1557 buffer.append("contains(").append(getFieldURI().getGraphId()).append(",'").append(StringUtil.escapeQuotes(value)).append("')");
1558 } else if (fieldURI instanceof ExtendedFieldURI) {
1559 buffer.append("singleValueExtendedProperties/Any(ep: ep/id eq '").append(getFieldURI().getGraphId())
1560 .append("' and ep/value ").append(convertOperator(operator)).append(" '").append(StringUtil.escapeQuotes(value)).append("')");
1561 } else {
1562 buffer.append(getFieldURI().getGraphId()).append(" ").append(convertOperator(operator)).append(" '").append(StringUtil.escapeQuotes(value)).append("'");
1563 }
1564 }
1565
1566 @Override
1567 public boolean isMatch(ExchangeSession.Contact contact) {
1568 return false;
1569 }
1570 }
1571
1572 protected static class HeaderCondition extends AttributeCondition {
1573
1574 protected HeaderCondition(String attributeName, String value) {
1575 super(attributeName, Operator.Contains, value);
1576 }
1577
1578 @Override
1579 protected FieldURI getFieldURI() {
1580 return new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, attributeName);
1581 }
1582 }
1583
1584 protected static class IsNullCondition implements ExchangeSession.Condition, SearchExpression {
1585 protected final String attributeName;
1586
1587 protected IsNullCondition(String attributeName) {
1588 this.attributeName = attributeName;
1589 }
1590
1591 public void appendTo(StringBuilder buffer) {
1592 FieldURI fieldURI = Field.get(attributeName);
1593 if (fieldURI instanceof ExtendedFieldURI) {
1594 buffer.append("singleValueExtendedProperties/Any(ep: ep/id eq '").append(fieldURI.getGraphId())
1595 .append("' and ep/value eq null)");
1596 } else {
1597 buffer.append(fieldURI.getGraphId()).append(" eq null");
1598 }
1599 }
1600
1601 public boolean isEmpty() {
1602 return false;
1603 }
1604
1605 public boolean isMatch(ExchangeSession.Contact contact) {
1606 String actualValue = contact.get(attributeName);
1607 return actualValue == null;
1608 }
1609
1610 }
1611
1612 protected static class ExistsCondition implements ExchangeSession.Condition, SearchExpression {
1613 protected final String attributeName;
1614
1615 protected ExistsCondition(String attributeName) {
1616 this.attributeName = attributeName;
1617 }
1618
1619 public void appendTo(StringBuilder buffer) {
1620 buffer.append(Field.get(attributeName).getGraphId()).append(" ne null");
1621 }
1622
1623 public boolean isEmpty() {
1624 return false;
1625 }
1626
1627 public boolean isMatch(ExchangeSession.Contact contact) {
1628 String actualValue = contact.get(attributeName);
1629 return actualValue == null;
1630 }
1631
1632 }
1633
1634
1635 static class MultiCondition extends ExchangeSession.MultiCondition {
1636
1637 protected MultiCondition(Operator operator, Condition... conditions) {
1638 super(operator, conditions);
1639 }
1640
1641 @Override
1642 public void appendTo(StringBuilder buffer) {
1643 int actualConditionCount = 0;
1644 for (Condition condition : conditions) {
1645 if (!condition.isEmpty()) {
1646 actualConditionCount++;
1647 }
1648 }
1649 if (actualConditionCount > 0) {
1650 boolean isFirst = true;
1651
1652 for (Condition condition : conditions) {
1653 if (isFirst) {
1654 isFirst = false;
1655
1656 } else {
1657 buffer.append(" ").append(operator.toString()).append(" ");
1658 }
1659 condition.appendTo(buffer);
1660 }
1661 }
1662 }
1663 }
1664
1665 static class NotCondition extends ExchangeSession.NotCondition {
1666
1667 protected NotCondition(Condition condition) {
1668 super(condition);
1669 }
1670
1671 @Override
1672 public void appendTo(StringBuilder buffer) {
1673 buffer.append("not ");
1674 condition.appendTo(buffer);
1675 }
1676 }
1677
1678 @Override
1679 public MultiCondition and(Condition... conditions) {
1680 return new MultiCondition(Operator.And, conditions);
1681 }
1682
1683 @Override
1684 public MultiCondition or(Condition... conditions) {
1685 return new MultiCondition(Operator.Or, conditions);
1686 }
1687
1688 @Override
1689 public Condition not(Condition condition) {
1690 return new NotCondition(condition);
1691 }
1692
1693 @Override
1694 public Condition isEqualTo(String attributeName, String value) {
1695 return new AttributeCondition(attributeName, Operator.IsEqualTo, value);
1696 }
1697
1698 @Override
1699 public Condition isEqualTo(String attributeName, int value) {
1700 return new AttributeCondition(attributeName, Operator.IsEqualTo, String.valueOf(value));
1701 }
1702
1703 @Override
1704 public Condition headerIsEqualTo(String headerName, String value) {
1705 return new HeaderCondition(headerName, value);
1706 }
1707
1708 @Override
1709 public Condition gte(String attributeName, String value) {
1710 return new AttributeCondition(attributeName, Operator.IsGreaterThanOrEqualTo, value);
1711 }
1712
1713 @Override
1714 public Condition gt(String attributeName, String value) {
1715 return new AttributeCondition(attributeName, Operator.IsGreaterThan, value);
1716 }
1717
1718 @Override
1719 public Condition lt(String attributeName, String value) {
1720 return new AttributeCondition(attributeName, Operator.IsLessThan, value);
1721 }
1722
1723 @Override
1724 public Condition lte(String attributeName, String value) {
1725 return new AttributeCondition(attributeName, Operator.IsLessThanOrEqualTo, value);
1726 }
1727
1728 @Override
1729 public Condition contains(String attributeName, String value) {
1730 return new AttributeCondition(attributeName, Operator.Contains, value);
1731 }
1732
1733 @Override
1734 public Condition startsWith(String attributeName, String value) {
1735 return new AttributeCondition(attributeName, Operator.StartsWith, value);
1736 }
1737
1738 @Override
1739 public Condition isNull(String attributeName) {
1740 return new IsNullCondition(attributeName);
1741 }
1742
1743 @Override
1744 public Condition exists(String attributeName) {
1745 return new ExistsCondition(attributeName);
1746 }
1747
1748 @Override
1749 public Condition isTrue(String attributeName) {
1750 return new AttributeCondition(attributeName, Operator.IsEqualTo, "true");
1751 }
1752
1753 @Override
1754 public Condition isFalse(String attributeName) {
1755 return new AttributeCondition(attributeName, Operator.IsEqualTo, "false");
1756 }
1757
1758 @Override
1759 public List<ExchangeSession.Folder> getSubFolders(String folderPath, Condition condition, boolean recursive) throws IOException {
1760 String baseFolderPath = folderPath;
1761 if (baseFolderPath.startsWith("/users/")) {
1762 int index = baseFolderPath.indexOf('/', "/users/".length());
1763 if (index >= 0) {
1764 baseFolderPath = baseFolderPath.substring(index + 1);
1765 }
1766 }
1767 List<ExchangeSession.Folder> folders = new ArrayList<>();
1768 appendSubFolders(folders, baseFolderPath, getFolderId(folderPath), condition, recursive);
1769 return folders;
1770 }
1771
1772 protected void appendSubFolders(List<ExchangeSession.Folder> folders,
1773 String parentFolderPath, FolderId parentFolderId,
1774 Condition condition, boolean recursive) throws IOException {
1775
1776 GraphRequestBuilder httpRequestBuilder = new GraphRequestBuilder()
1777 .setMethod(HttpGet.METHOD_NAME)
1778 .setObjectType("mailFolders")
1779 .setMailbox(parentFolderId.mailbox)
1780 .setObjectId(parentFolderId.id)
1781 .setChildType("childFolders")
1782 .setExpandFields(FOLDER_PROPERTIES);
1783 LOGGER.debug("appendSubFolders " + (parentFolderId.mailbox != null ? parentFolderId.mailbox : "me") + " " + parentFolderPath);
1784 if (condition != null && !condition.isEmpty()) {
1785 StringBuilder filter = new StringBuilder();
1786 condition.appendTo(filter);
1787 LOGGER.debug("search filter " + filter);
1788 httpRequestBuilder.setFilter(filter.toString());
1789 }
1790
1791 GraphIterator graphIterator = executeSearchRequest(httpRequestBuilder);
1792
1793 while (graphIterator.hasNext()) {
1794 Folder folder = buildFolder(graphIterator.next());
1795 folder.folderId.mailbox = parentFolderId.mailbox;
1796
1797 if (parentFolderId.id.equals(folder.folderId.parentFolderId)) {
1798 if (!parentFolderPath.isEmpty()) {
1799 if (parentFolderPath.endsWith("/")) {
1800 folder.folderPath = parentFolderPath + folder.displayName;
1801 } else {
1802 folder.folderPath = parentFolderPath + '/' + folder.displayName;
1803 }
1804
1805 } else {
1806 folder.folderPath = folder.displayName;
1807 }
1808 folders.add(folder);
1809 if (recursive && folder.hasChildren) {
1810 appendSubFolders(folders, folder.folderPath, folder.folderId, condition, true);
1811 }
1812 } else {
1813 LOGGER.debug("appendSubFolders skip " + folder.folderId.mailbox + " " + folder.folderId.id + " " + folder.displayName + " not a child of " + parentFolderPath);
1814 }
1815 }
1816
1817 }
1818
1819
1820 @Override
1821 public void sendMessage(MimeMessage mimeMessage) throws IOException, MessagingException {
1822
1823 executeJsonRequest(new GraphRequestBuilder()
1824 .setMethod(HttpPost.METHOD_NAME)
1825 .setObjectType("sendMail")
1826 .setContentType("text/plain")
1827 .setMimeContent(IOUtil.encodeBase64(mimeMessage)));
1828
1829 }
1830
1831 @Override
1832 protected Folder internalGetFolder(String folderPath) throws IOException {
1833 FolderId folderId = getFolderId(folderPath);
1834
1835
1836 GraphRequestBuilder httpRequestBuilder = new GraphRequestBuilder()
1837 .setMethod(HttpGet.METHOD_NAME)
1838 .setMailbox(folderId.mailbox)
1839 .setObjectType("mailFolders")
1840 .setObjectId(folderId.id)
1841 .setExpandFields(FOLDER_PROPERTIES);
1842 if ("IPF.Appointment".equals(folderId.folderClass)) {
1843 httpRequestBuilder.setObjectType("calendars");
1844 } else if ("IPF.Contact".equals(folderId.folderClass)) {
1845 httpRequestBuilder.setObjectType("contactFolders");
1846 } else {
1847 httpRequestBuilder.setObjectType("mailFolders");
1848 }
1849
1850 JSONObject jsonResponse = executeJsonRequest(httpRequestBuilder);
1851
1852 Folder folder = buildFolder(jsonResponse);
1853 folder.folderPath = folderPath;
1854
1855 return folder;
1856 }
1857
1858 private Folder buildFolder(JSONObject jsonResponse) throws IOException {
1859 try {
1860 Folder folder = new Folder();
1861 folder.folderId = new FolderId();
1862 folder.folderId.id = jsonResponse.getString("id");
1863 folder.folderId.parentFolderId = jsonResponse.optString("parentFolderId", null);
1864 if (folder.folderId.parentFolderId == null) {
1865
1866 folder.displayName = EwsExchangeSession.encodeFolderName(jsonResponse.optString("name"));
1867 } else {
1868 String wellKnownName = wellKnownFolderMap.get(jsonResponse.optString("wellKnownName"));
1869 if (ExchangeSession.INBOX.equals(wellKnownName)) {
1870 folder.displayName = wellKnownName;
1871 } else {
1872 if (wellKnownName != null) {
1873 folder.setSpecialFlag(wellKnownName);
1874 }
1875
1876
1877 folder.displayName = EwsExchangeSession.encodeFolderName(jsonResponse.getString("displayName"));
1878 }
1879
1880 folder.messageCount = jsonResponse.optInt("totalItemCount");
1881 folder.unreadCount = jsonResponse.optInt("unreadItemCount");
1882
1883 folder.recent = folder.unreadCount;
1884
1885 folder.hasChildren = jsonResponse.optInt("childFolderCount") > 0;
1886 }
1887
1888
1889 JSONArray singleValueExtendedProperties = jsonResponse.optJSONArray("singleValueExtendedProperties");
1890 if (singleValueExtendedProperties != null) {
1891 for (int i = 0; i < singleValueExtendedProperties.length(); i++) {
1892 JSONObject singleValueProperty = singleValueExtendedProperties.getJSONObject(i);
1893 String singleValueId = singleValueProperty.getString("id");
1894 String singleValue = singleValueProperty.getString("value");
1895 if (Field.get("lastmodified").getGraphId().equals(singleValueId)) {
1896 folder.etag = singleValue;
1897 } else if (Field.get("folderclass").getGraphId().equals(singleValueId)) {
1898 folder.folderClass = singleValue;
1899 folder.folderId.folderClass = folder.folderClass;
1900 } else if (Field.get("uidNext").getGraphId().equals(singleValueId)) {
1901 folder.uidNext = Long.parseLong(singleValue);
1902 } else if (Field.get("ctag").getGraphId().equals(singleValueId)) {
1903 folder.ctag = singleValue;
1904 }
1905
1906 }
1907 }
1908
1909 return folder;
1910 } catch (JSONException e) {
1911 throw new IOException(e.getMessage(), e);
1912 }
1913 }
1914
1915
1916
1917
1918
1919
1920 private FolderId getFolderId(String folderPath) throws IOException {
1921 FolderId folderId = getFolderIdIfExists(folderPath);
1922 if (folderId == null) {
1923 throw new HttpNotFoundException("Folder '" + folderPath + "' not found");
1924 }
1925 return folderId;
1926 }
1927
1928 protected static final String USERS_ROOT = "/users/";
1929 protected static final String ARCHIVE_ROOT = "/archive/";
1930
1931
1932 private FolderId getFolderIdIfExists(String folderPath) throws IOException {
1933 String lowerCaseFolderPath = folderPath.toLowerCase();
1934 if (lowerCaseFolderPath.equals(currentMailboxPath)) {
1935 return getSubFolderIdIfExists(null, "");
1936 } else if (lowerCaseFolderPath.startsWith(currentMailboxPath + '/')) {
1937 return getSubFolderIdIfExists(null, folderPath.substring(currentMailboxPath.length() + 1));
1938 } else if (folderPath.startsWith(USERS_ROOT)) {
1939 int slashIndex = folderPath.indexOf('/', USERS_ROOT.length());
1940 String mailbox;
1941 String subFolderPath;
1942 if (slashIndex >= 0) {
1943 mailbox = folderPath.substring(USERS_ROOT.length(), slashIndex);
1944 subFolderPath = folderPath.substring(slashIndex + 1);
1945 } else {
1946 mailbox = folderPath.substring(USERS_ROOT.length());
1947 subFolderPath = "";
1948 }
1949 return getSubFolderIdIfExists(mailbox, subFolderPath);
1950 } else {
1951 return getSubFolderIdIfExists(null, folderPath);
1952 }
1953 }
1954
1955 private FolderId getSubFolderIdIfExists(String mailbox, String folderPath) throws IOException {
1956 String[] folderNames;
1957 FolderId currentFolderId;
1958
1959
1960 if ("/public".equals(folderPath)) {
1961 throw new UnsupportedOperationException("public folders not supported on Graph");
1962 } else if ("/archive".equals(folderPath)) {
1963 return getWellKnownFolderId(mailbox, WellKnownFolderName.archive);
1964 } else if (isSubFolderOf(folderPath, PUBLIC_ROOT)) {
1965 throw new UnsupportedOperationException("public folders not supported on Graph");
1966 } else if (isSubFolderOf(folderPath, ARCHIVE_ROOT)) {
1967 currentFolderId = getWellKnownFolderId(mailbox, WellKnownFolderName.archive);
1968 folderNames = folderPath.substring(ARCHIVE_ROOT.length()).split("/");
1969 } else if (isSubFolderOf(folderPath, INBOX) ||
1970 isSubFolderOf(folderPath, LOWER_CASE_INBOX) ||
1971 isSubFolderOf(folderPath, MIXED_CASE_INBOX)) {
1972 currentFolderId = getWellKnownFolderId(mailbox, WellKnownFolderName.inbox);
1973 folderNames = folderPath.substring(INBOX.length()).split("/");
1974 } else if (isSubFolderOf(folderPath, CALENDAR)) {
1975 currentFolderId = new FolderId(mailbox, WellKnownFolderName.calendar, "IPF.Appointment");
1976
1977 folderNames = folderPath.substring(CALENDAR.length()).split("/");
1978 } else if (isSubFolderOf(folderPath, TASKS)) {
1979 currentFolderId = getWellKnownFolderId(mailbox, WellKnownFolderName.tasks);
1980 folderNames = folderPath.substring(TASKS.length()).split("/");
1981 } else if (isSubFolderOf(folderPath, CONTACTS)) {
1982 currentFolderId = new FolderId(mailbox, WellKnownFolderName.contacts, "IPF.Contact");
1983 folderNames = folderPath.substring(CONTACTS.length()).split("/");
1984 } else if (isSubFolderOf(folderPath, SENT)) {
1985 currentFolderId = new FolderId(mailbox, WellKnownFolderName.sentitems);
1986 folderNames = folderPath.substring(SENT.length()).split("/");
1987 } else if (isSubFolderOf(folderPath, DRAFTS)) {
1988 currentFolderId = new FolderId(mailbox, WellKnownFolderName.drafts);
1989 folderNames = folderPath.substring(DRAFTS.length()).split("/");
1990 } else if (isSubFolderOf(folderPath, TRASH)) {
1991 currentFolderId = new FolderId(mailbox, WellKnownFolderName.deleteditems);
1992 folderNames = folderPath.substring(TRASH.length()).split("/");
1993 } else if (isSubFolderOf(folderPath, JUNK)) {
1994 currentFolderId = new FolderId(mailbox, WellKnownFolderName.junkemail);
1995 folderNames = folderPath.substring(JUNK.length()).split("/");
1996 } else if (isSubFolderOf(folderPath, UNSENT)) {
1997 currentFolderId = new FolderId(mailbox, WellKnownFolderName.outbox);
1998 folderNames = folderPath.substring(UNSENT.length()).split("/");
1999 } else {
2000
2001 currentFolderId = getWellKnownFolderId(mailbox, WellKnownFolderName.msgfolderroot);
2002 folderNames = folderPath.split("/");
2003 }
2004 String folderClass = currentFolderId.folderClass;
2005 for (String folderName : folderNames) {
2006 if (!folderName.isEmpty()) {
2007 currentFolderId = getSubFolderByName(currentFolderId, folderName);
2008 if (currentFolderId == null) {
2009 break;
2010 }
2011 currentFolderId.folderClass = folderClass;
2012 }
2013 }
2014 return currentFolderId;
2015 }
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025 private FolderId getWellKnownFolderId(String mailbox, WellKnownFolderName wellKnownFolderName) throws IOException {
2026 if (wellKnownFolderName == WellKnownFolderName.tasks) {
2027
2028 GraphIterator graphIterator = executeSearchRequest(new GraphRequestBuilder()
2029 .setMethod(HttpGet.METHOD_NAME)
2030 .setMailbox(mailbox)
2031 .setObjectType("todo/lists"));
2032 while (graphIterator.hasNext()) {
2033 JSONObject jsonResponse = graphIterator.next();
2034 if (jsonResponse.optString("wellknownListName").equals("defaultList")) {
2035 return new FolderId(mailbox, jsonResponse.optString("id"), "IPF.Task");
2036 }
2037 }
2038
2039 throw new HttpNotFoundException("Folder '" + wellKnownFolderName.name() + "' not found");
2040
2041 } else {
2042 JSONObject jsonResponse = executeJsonRequest(new GraphRequestBuilder()
2043 .setMethod(HttpGet.METHOD_NAME)
2044 .setMailbox(mailbox)
2045 .setObjectType("mailFolders")
2046 .setObjectId(wellKnownFolderName.name())
2047 .setExpandFields(FOLDER_PROPERTIES));
2048
2049 return new FolderId(mailbox, jsonResponse.optString("id"), "IPF.Note");
2050 }
2051 }
2052
2053
2054
2055
2056
2057
2058
2059
2060 protected FolderId getSubFolderByName(FolderId currentFolderId, String folderName) throws IOException {
2061
2062 GraphRequestBuilder httpRequestBuilder;
2063 if ("IPF.Appointment".equals(currentFolderId.folderClass)) {
2064 httpRequestBuilder = new GraphRequestBuilder()
2065 .setMethod(HttpGet.METHOD_NAME)
2066 .setMailbox(currentFolderId.mailbox)
2067 .setObjectType("calendars")
2068 .setExpandFields(FOLDER_PROPERTIES)
2069 .setFilter("name eq '" + StringUtil.escapeQuotes(EwsExchangeSession.decodeFolderName(folderName)) + "'");
2070 } else if ("IPF.Task".equals(currentFolderId.folderClass)) {
2071 httpRequestBuilder = new GraphRequestBuilder()
2072 .setMethod(HttpGet.METHOD_NAME)
2073 .setMailbox(currentFolderId.mailbox)
2074 .setObjectType("todo/lists")
2075 .setExpandFields(FOLDER_PROPERTIES)
2076 .setFilter("displayName eq '" + StringUtil.escapeQuotes(EwsExchangeSession.decodeFolderName(folderName)) + "'");
2077 } else {
2078 String objectType = "mailFolders";
2079 if ("IPF.Contact".equals(currentFolderId.folderClass)) {
2080 objectType = "contactFolders";
2081 }
2082 httpRequestBuilder = new GraphRequestBuilder()
2083 .setMethod(HttpGet.METHOD_NAME)
2084 .setMailbox(currentFolderId.mailbox)
2085 .setObjectType(objectType)
2086 .setObjectId(currentFolderId.id)
2087 .setChildType("childFolders")
2088 .setExpandFields(FOLDER_PROPERTIES)
2089 .setFilter("displayName eq '" + StringUtil.escapeQuotes(EwsExchangeSession.decodeFolderName(folderName)) + "'");
2090 }
2091
2092 JSONObject jsonResponse = executeJsonRequest(httpRequestBuilder);
2093
2094 FolderId folderId = null;
2095 try {
2096 JSONArray values = jsonResponse.getJSONArray("value");
2097 if (values.length() > 0) {
2098 folderId = new FolderId(currentFolderId.mailbox, values.getJSONObject(0).getString("id"), currentFolderId.folderClass);
2099 folderId.parentFolderId = currentFolderId.id;
2100 }
2101 } catch (JSONException e) {
2102 throw new IOException(e.getMessage(), e);
2103 }
2104
2105 return folderId;
2106 }
2107
2108 private boolean isSubFolderOf(String folderPath, String baseFolder) {
2109 if (PUBLIC_ROOT.equals(baseFolder) || ARCHIVE_ROOT.equals(baseFolder)) {
2110 return folderPath.startsWith(baseFolder);
2111 } else {
2112 return folderPath.startsWith(baseFolder)
2113 && (folderPath.length() == baseFolder.length() || folderPath.charAt(baseFolder.length()) == '/');
2114 }
2115 }
2116
2117 @Override
2118 public int createFolder(String folderPath, String folderClass, Map<String, String> properties) throws IOException {
2119 if ("IPF.Appointment".equals(folderClass) && folderPath.startsWith("calendar/")) {
2120
2121 String calendarName = folderPath.substring(folderPath.indexOf('/') + 1);
2122
2123 try {
2124 executeJsonRequest(new GraphRequestBuilder()
2125 .setMethod(HttpPost.METHOD_NAME)
2126
2127
2128 .setObjectType("calendars")
2129 .setJsonBody(new JSONObject().put("name", calendarName)));
2130
2131 } catch (JSONException e) {
2132 throw new IOException(e);
2133 }
2134 } else {
2135 FolderId parentFolderId;
2136 String folderName;
2137 if (folderPath.contains("/")) {
2138 String parentFolderPath = folderPath.substring(0, folderPath.lastIndexOf('/'));
2139 parentFolderId = getFolderId(parentFolderPath);
2140 folderName = EwsExchangeSession.decodeFolderName(folderPath.substring(folderPath.lastIndexOf('/') + 1));
2141 } else {
2142 parentFolderId = getFolderId("");
2143 folderName = EwsExchangeSession.decodeFolderName(folderPath);
2144 }
2145
2146 try {
2147 String objectType = "mailFolders";
2148 if ("IPF.Contact".equals(folderClass)) {
2149 objectType = "contactFolders";
2150 }
2151 executeJsonRequest(new GraphRequestBuilder()
2152 .setMethod(HttpPost.METHOD_NAME)
2153
2154 .setMailbox(parentFolderId.mailbox)
2155 .setObjectType(objectType)
2156 .setObjectId(parentFolderId.id)
2157 .setChildType("childFolders")
2158 .setJsonBody(new JSONObject().put("displayName", folderName)));
2159
2160 } catch (JSONException e) {
2161 throw new IOException(e);
2162 }
2163 }
2164
2165 return HttpStatus.SC_CREATED;
2166
2167 }
2168
2169 @Override
2170 public int updateFolder(String folderName, Map<String, String> properties) throws IOException {
2171 return 0;
2172 }
2173
2174 @Override
2175 public void deleteFolder(String folderPath) throws IOException {
2176 FolderId folderId = getFolderIdIfExists(folderPath);
2177 if (folderPath.startsWith("calendar/")) {
2178
2179 if (folderId != null) {
2180 executeJsonRequest(new GraphRequestBuilder()
2181 .setMethod(HttpDelete.METHOD_NAME)
2182
2183 .setObjectType("calendars")
2184 .setObjectId(folderId.id));
2185 }
2186 } else {
2187 if (folderId != null) {
2188 String objectType = "mailFolders";
2189 if ("IPF.Contact".equals(folderId.folderClass)) {
2190 objectType = "contactFolders";
2191 }
2192 executeJsonRequest(new GraphRequestBuilder()
2193 .setMethod(HttpDelete.METHOD_NAME)
2194 .setMailbox(folderId.mailbox)
2195 .setObjectType(objectType)
2196 .setObjectId(folderId.id));
2197 }
2198 }
2199
2200 }
2201
2202 @Override
2203 public void copyMessage(ExchangeSession.Message message, String targetFolder) throws IOException {
2204 try {
2205 FolderId targetFolderId = getFolderId(targetFolder);
2206
2207 executeJsonRequest(new GraphRequestBuilder().setMethod(HttpPost.METHOD_NAME)
2208 .setMailbox(((Message) message).folderId.mailbox)
2209 .setObjectType("messages")
2210 .setObjectId(((Message) message).id)
2211 .setChildType("copy")
2212 .setJsonBody(new JSONObject().put("destinationId", targetFolderId.id)));
2213
2214 } catch (JSONException e) {
2215 throw new IOException(e);
2216 }
2217 }
2218
2219 @Override
2220 public void moveMessage(ExchangeSession.Message message, String targetFolder) throws IOException {
2221 try {
2222 FolderId targetFolderId = getFolderId(targetFolder);
2223
2224 executeJsonRequest(new GraphRequestBuilder().setMethod(HttpPost.METHOD_NAME)
2225 .setMailbox(((Message) message).folderId.mailbox)
2226 .setObjectType("messages")
2227 .setObjectId(((Message) message).id)
2228 .setChildType("move")
2229 .setJsonBody(new JSONObject().put("destinationId", targetFolderId.id)));
2230 } catch (JSONException e) {
2231 throw new IOException(e);
2232 }
2233 }
2234
2235 @Override
2236 public void moveFolder(String folderPath, String targetFolderPath) throws IOException {
2237 FolderId folderId = getFolderId(folderPath);
2238 String targetFolderName;
2239 String targetFolderParentPath;
2240 if (targetFolderPath.contains("/")) {
2241 targetFolderParentPath = targetFolderPath.substring(0, targetFolderPath.lastIndexOf('/'));
2242 targetFolderName = EwsExchangeSession.decodeFolderName(targetFolderPath.substring(targetFolderPath.lastIndexOf('/') + 1));
2243 } else {
2244 targetFolderParentPath = "";
2245 targetFolderName = EwsExchangeSession.decodeFolderName(targetFolderPath);
2246 }
2247 FolderId targetFolderId = getFolderId(targetFolderParentPath);
2248
2249
2250 try {
2251 executeJsonRequest(new GraphRequestBuilder().setMethod(HttpPatch.METHOD_NAME)
2252 .setMailbox(folderId.mailbox)
2253 .setObjectType("mailFolders")
2254 .setObjectId(folderId.id)
2255 .setJsonBody(new JSONObject().put("displayName", targetFolderName)));
2256 } catch (JSONException e) {
2257 throw new IOException(e);
2258 }
2259
2260 try {
2261 executeJsonRequest(new GraphRequestBuilder().setMethod(HttpPost.METHOD_NAME)
2262 .setMailbox(folderId.mailbox)
2263 .setObjectType("mailFolders")
2264 .setObjectId(folderId.id)
2265 .setChildType("move")
2266 .setJsonBody(new JSONObject().put("destinationId", targetFolderId.id)));
2267 } catch (JSONException e) {
2268 throw new IOException(e);
2269 }
2270 }
2271
2272 @Override
2273 public void moveItem(String sourcePath, String targetPath) throws IOException {
2274
2275 }
2276
2277 @Override
2278 protected void moveToTrash(ExchangeSession.Message message) throws IOException {
2279 moveMessage(message, WellKnownFolderName.deleteditems.name());
2280 }
2281
2282
2283
2284
2285
2286 protected static final Set<String> ITEM_PROPERTIES = new HashSet<>();
2287
2288 static {
2289
2290
2291
2292
2293
2294
2295 }
2296
2297 protected static final HashSet<String> EVENT_REQUEST_PROPERTIES = new HashSet<>();
2298
2299 static {
2300 EVENT_REQUEST_PROPERTIES.add("permanenturl");
2301 EVENT_REQUEST_PROPERTIES.add("etag");
2302 EVENT_REQUEST_PROPERTIES.add("displayname");
2303 EVENT_REQUEST_PROPERTIES.add("subject");
2304 EVENT_REQUEST_PROPERTIES.add("urlcompname");
2305 EVENT_REQUEST_PROPERTIES.add("displayto");
2306 EVENT_REQUEST_PROPERTIES.add("displaycc");
2307
2308 EVENT_REQUEST_PROPERTIES.add("xmozlastack");
2309 EVENT_REQUEST_PROPERTIES.add("xmozsnoozetime");
2310 }
2311
2312 protected static final HashSet<String> CALENDAR_ITEM_REQUEST_PROPERTIES = new HashSet<>();
2313
2314 static {
2315 CALENDAR_ITEM_REQUEST_PROPERTIES.addAll(EVENT_REQUEST_PROPERTIES);
2316 CALENDAR_ITEM_REQUEST_PROPERTIES.add("ismeeting");
2317 CALENDAR_ITEM_REQUEST_PROPERTIES.add("myresponsetype");
2318 }
2319
2320 @Override
2321 protected Set<String> getItemProperties() {
2322 return ITEM_PROPERTIES;
2323 }
2324
2325 @Override
2326 public List<ExchangeSession.Contact> searchContacts(String folderPath, Set<String> attributes, Condition condition, int maxCount) throws IOException {
2327 ArrayList<ExchangeSession.Contact> contactList = new ArrayList<>();
2328 FolderId folderId = getFolderId(folderPath);
2329
2330 GraphRequestBuilder httpRequestBuilder = new GraphRequestBuilder()
2331 .setMethod(HttpGet.METHOD_NAME)
2332 .setMailbox(folderId.mailbox)
2333 .setObjectType("contactFolders")
2334 .setObjectId(folderId.id)
2335 .setChildType("contacts")
2336 .setExpandFields(CONTACT_ATTRIBUTES);
2337 LOGGER.debug("searchContacts " + folderId.mailbox + " " + folderPath);
2338 if (condition != null && !condition.isEmpty()) {
2339 StringBuilder filter = new StringBuilder();
2340 condition.appendTo(filter);
2341 LOGGER.debug("search filter " + filter);
2342 httpRequestBuilder.setFilter(filter.toString());
2343 }
2344
2345 GraphIterator graphIterator = executeSearchRequest(httpRequestBuilder);
2346
2347 while (graphIterator.hasNext()) {
2348 Contact contact = new Contact(new GraphObject(graphIterator.next()));
2349 contact.folderId = folderId;
2350 contactList.add(contact);
2351 }
2352
2353 return contactList;
2354 }
2355
2356 @Override
2357 public List<ExchangeSession.Event> getEventMessages(String folderPath) throws IOException {
2358 return null;
2359 }
2360
2361 @Override
2362 protected Condition getCalendarItemCondition(Condition dateCondition) {
2363 return null;
2364 }
2365
2366
2367
2368
2369
2370
2371
2372 @Override
2373 public List<ExchangeSession.Event> searchTasksOnly(String folderPath) throws IOException {
2374 ArrayList<ExchangeSession.Event> eventList = new ArrayList<>();
2375 FolderId folderId = getFolderId(folderPath);
2376
2377
2378 GraphRequestBuilder httpRequestBuilder = new GraphRequestBuilder()
2379 .setMethod(HttpGet.METHOD_NAME)
2380 .setMailbox(folderId.mailbox)
2381 .setObjectType("todo/lists")
2382 .setObjectId(folderId.id)
2383 .setChildType("tasks")
2384 .setExpandFields(TODO_PROPERTIES);
2385 LOGGER.debug("searchTasksOnly " + folderId.mailbox + " " + folderPath);
2386
2387 GraphIterator graphIterator = executeSearchRequest(httpRequestBuilder);
2388
2389 while (graphIterator.hasNext()) {
2390 Event event = new Event(folderId, new GraphObject(graphIterator.next()));
2391 eventList.add(event);
2392 }
2393
2394 return eventList;
2395 }
2396
2397 @Override
2398 public List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
2399 ArrayList<ExchangeSession.Event> eventList = new ArrayList<>();
2400 FolderId folderId = getFolderId(folderPath);
2401
2402
2403 GraphRequestBuilder httpRequestBuilder = new GraphRequestBuilder()
2404 .setMethod(HttpGet.METHOD_NAME)
2405 .setMailbox(folderId.mailbox)
2406 .setObjectType("calendars")
2407 .setObjectId(folderId.id)
2408 .setChildType("events")
2409 .setExpandFields(EVENT_ATTRIBUTES)
2410 .setTimezone(getVTimezone().getPropertyValue("TZID"));
2411 LOGGER.debug("searchEvents " + folderId.mailbox + " " + folderPath);
2412 if (condition != null && !condition.isEmpty()) {
2413 StringBuilder filter = new StringBuilder();
2414 condition.appendTo(filter);
2415 LOGGER.debug("search filter " + filter);
2416 httpRequestBuilder.setFilter(filter.toString());
2417 }
2418
2419 GraphIterator graphIterator = executeSearchRequest(httpRequestBuilder);
2420
2421 while (graphIterator.hasNext()) {
2422 Event event = new Event(folderId, new GraphObject(graphIterator.next()));
2423 eventList.add(event);
2424 }
2425
2426 return eventList;
2427
2428 }
2429
2430 @Override
2431 public Item getItem(String folderPath, String itemName) throws IOException {
2432 FolderId folderId = getFolderId(folderPath);
2433
2434 if ("IPF.Contact".equals(folderId.folderClass)) {
2435 JSONObject jsonResponse = getContactIfExists(folderId, itemName);
2436 if (jsonResponse != null) {
2437 Contact contact = new Contact(new GraphObject(jsonResponse));
2438 contact.folderId = folderId;
2439 return contact;
2440 } else {
2441 throw new IOException("Item " + folderPath + " " + itemName + " not found");
2442 }
2443 } else if ("IPF.Appointment".equals(folderId.folderClass)) {
2444 JSONObject jsonResponse = getEventIfExists(folderId, itemName);
2445 if (jsonResponse != null) {
2446 Event event = new Event(folderId, new GraphObject(jsonResponse));
2447 return event;
2448 } else {
2449 throw new IOException("Item " + folderPath + " " + itemName + " not found");
2450 }
2451 } else {
2452 throw new UnsupportedOperationException("Item type " + folderId.folderClass + " not supported");
2453 }
2454 }
2455
2456 private JSONObject getEventIfExists(FolderId folderId, String itemName) throws IOException {
2457 String itemId;
2458 if (isItemId(itemName)) {
2459 itemId = itemName.substring(0, itemName.length() - 4);
2460 } else {
2461
2462 return null;
2463 }
2464 try {
2465 return executeJsonRequest(new GraphRequestBuilder()
2466 .setMethod(HttpGet.METHOD_NAME)
2467 .setMailbox(folderId.mailbox)
2468 .setObjectType("events")
2469 .setObjectId(itemId)
2470 .setSelect(EVENT_SELECT)
2471 .setExpandFields(EVENT_ATTRIBUTES)
2472 .setTimezone(getVTimezone().getPropertyValue("TZID"))
2473 );
2474 } catch (HttpNotFoundException e) {
2475
2476 FolderId taskFolderId = getFolderId(TASKS);
2477 return executeJsonRequest(new GraphRequestBuilder()
2478 .setMethod(HttpGet.METHOD_NAME)
2479 .setMailbox(folderId.mailbox)
2480 .setObjectType("todo/lists")
2481 .setObjectId(taskFolderId.id)
2482 .setChildType("tasks")
2483 .setChildId(itemId)
2484 .setExpandFields(TODO_PROPERTIES)
2485 );
2486 }
2487 }
2488
2489 private JSONObject getContactIfExists(FolderId folderId, String itemName) throws IOException {
2490 if (isItemId(itemName)) {
2491
2492 return executeJsonRequest(new GraphRequestBuilder()
2493 .setMethod(HttpGet.METHOD_NAME)
2494 .setMailbox(folderId.mailbox)
2495 .setObjectType("contactFolders")
2496 .setObjectId(folderId.id)
2497 .setChildType("contacts")
2498 .setChildId(itemName.substring(0, itemName.length() - ".EML".length()))
2499 .setExpandFields(CONTACT_ATTRIBUTES)
2500 );
2501
2502 } else {
2503
2504 String filter = "singleValueExtendedProperties/Any(ep: ep/id eq '" + Field.get("urlcompname").getGraphId() + "' and ep/value eq '" + convertItemNameToEML(StringUtil.escapeQuotes(itemName)) + "')";
2505 JSONObject jsonResponse = executeJsonRequest(new GraphRequestBuilder()
2506 .setMethod(HttpGet.METHOD_NAME)
2507 .setMailbox(folderId.mailbox)
2508 .setObjectType("contactFolders")
2509 .setObjectId(folderId.id)
2510 .setChildType("contacts")
2511 .setFilter(filter)
2512 .setExpandFields(CONTACT_ATTRIBUTES)
2513 );
2514
2515 JSONArray values = jsonResponse.optJSONArray("value");
2516 if (values != null && values.length() > 0) {
2517 if (LOGGER.isDebugEnabled()) {
2518 LOGGER.debug("Contact " + values.optJSONObject(0));
2519 }
2520 return values.optJSONObject(0);
2521 }
2522 }
2523 return null;
2524 }
2525
2526 @Override
2527 public ContactPhoto getContactPhoto(ExchangeSession.Contact contact) throws IOException {
2528
2529 GraphRequestBuilder graphRequestBuilder = new GraphRequestBuilder()
2530 .setMethod(HttpGet.METHOD_NAME)
2531 .setMailbox(((Contact) contact).folderId.mailbox)
2532 .setObjectType("contactFolders")
2533 .setObjectId(((Contact) contact).folderId.id)
2534 .setChildType("contacts")
2535 .setChildId(((Contact) contact).id)
2536 .setChildSuffix("photo/$value");
2537
2538 byte[] contactPhotoBytes;
2539 try (
2540 CloseableHttpResponse response = httpClient.execute(graphRequestBuilder.build());
2541 InputStream inputStream = response.getEntity().getContent()
2542 ) {
2543 if (HttpClientAdapter.isGzipEncoded(response)) {
2544 contactPhotoBytes = IOUtil.readFully(new GZIPInputStream(inputStream));
2545 } else {
2546 contactPhotoBytes = IOUtil.readFully(inputStream);
2547 }
2548 }
2549 ContactPhoto contactPhoto = new ContactPhoto();
2550 contactPhoto.contentType = "image/jpeg";
2551 contactPhoto.content = IOUtil.encodeBase64AsString(contactPhotoBytes);
2552
2553 return contactPhoto;
2554 }
2555
2556 @Override
2557 public void deleteItem(String folderPath, String itemName) throws IOException {
2558
2559 }
2560
2561 @Override
2562 public void processItem(String folderPath, String itemName) throws IOException {
2563
2564 }
2565
2566 @Override
2567 public int sendEvent(String icsBody) throws IOException {
2568 return 0;
2569 }
2570
2571 @Override
2572 protected Contact buildContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException {
2573 return new Contact(folderPath, itemName, properties, StringUtil.removeQuotes(etag), noneMatch);
2574 }
2575
2576 @Override
2577 protected ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
2578 return new Event(folderPath, itemName, contentClass, icsBody, StringUtil.removeQuotes(etag), noneMatch).createOrUpdate();
2579 }
2580
2581 @Override
2582 public boolean isSharedFolder(String folderPath) {
2583 return false;
2584 }
2585
2586 @Override
2587 public boolean isMainCalendar(String folderPath) throws IOException {
2588 FolderId folderId = getFolderIdIfExists(folderPath);
2589 return folderId.parentFolderId == null && WellKnownFolderName.calendar.name().equals(folderId.id);
2590 }
2591
2592 @Override
2593 public Map<String, ExchangeSession.Contact> galFind(Condition condition, Set<String> returningAttributes, int sizeLimit) throws IOException {
2594
2595 return null;
2596 }
2597
2598 @Override
2599 protected String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException {
2600
2601
2602 return null;
2603 }
2604
2605 @Override
2606 protected void loadVtimezone() {
2607 try {
2608
2609 String timezoneId = Settings.getProperty("davmail.timezoneId", null);
2610
2611 if (timezoneId == null) {
2612 try {
2613 timezoneId = getMailboxSettings().optString("timeZone", null);
2614 } catch (HttpForbiddenException e) {
2615 LOGGER.warn("token does not grant MailboxSettings.Read");
2616 }
2617 }
2618
2619 if (timezoneId == null) {
2620 LOGGER.warn("Unable to get user timezone, using GMT Standard Time. Set davmail.timezoneId setting to override this.");
2621 timezoneId = "GMT Standard Time";
2622 }
2623 this.vTimezone = new VObject(ResourceBundle.getBundle("vtimezones").getString(timezoneId));
2624
2625 } catch (IOException | MissingResourceException e) {
2626 LOGGER.warn("Unable to get VTIMEZONE info: " + e, e);
2627 }
2628 }
2629
2630 private JSONObject getMailboxSettings() throws IOException {
2631 return executeJsonRequest(new GraphRequestBuilder()
2632 .setMethod(HttpGet.METHOD_NAME)
2633 .setObjectType("mailboxSettings"));
2634 }
2635
2636 class GraphIterator {
2637
2638 private JSONObject jsonObject;
2639 private JSONArray values;
2640 private String nextLink;
2641 private int index;
2642
2643 public GraphIterator(JSONObject jsonObject) throws JSONException {
2644 this.jsonObject = jsonObject;
2645 nextLink = jsonObject.optString("@odata.nextLink", null);
2646 values = jsonObject.getJSONArray("value");
2647 }
2648
2649 public boolean hasNext() throws IOException {
2650 if (index < values.length()) {
2651 return true;
2652 } else if (nextLink != null) {
2653 fetchNextPage();
2654 return values.length() > 0;
2655 } else {
2656 return false;
2657 }
2658 }
2659
2660 public JSONObject next() throws IOException {
2661 if (!hasNext()) {
2662 throw new NoSuchElementException();
2663 }
2664 try {
2665 if (index >= values.length() && nextLink != null) {
2666 fetchNextPage();
2667 }
2668 return values.getJSONObject(index++);
2669 } catch (JSONException e) {
2670 throw new IOException(e.getMessage(), e);
2671 }
2672 }
2673
2674 private void fetchNextPage() throws IOException {
2675 HttpGet request = new HttpGet(nextLink);
2676 request.setHeader("Authorization", "Bearer " + token.getAccessToken());
2677 try (
2678 CloseableHttpResponse response = httpClient.execute(request)
2679 ) {
2680 jsonObject = new JsonResponseHandler().handleResponse(response);
2681 nextLink = jsonObject.optString("@odata.nextLink", null);
2682 values = jsonObject.getJSONArray("value");
2683 index = 0;
2684 } catch (JSONException e) {
2685 throw new IOException(e.getMessage(), e);
2686 }
2687 }
2688 }
2689
2690 private GraphIterator executeSearchRequest(GraphRequestBuilder httpRequestBuilder) throws IOException {
2691 try {
2692 return new GraphIterator(executeJsonRequest(httpRequestBuilder));
2693 } catch (JSONException e) {
2694 throw new IOException(e.getMessage(), e);
2695 }
2696 }
2697
2698 private JSONObject executeJsonRequest(GraphRequestBuilder httpRequestBuilder) throws IOException {
2699
2700 HttpRequestBase request = httpRequestBuilder
2701 .setAccessToken(token.getAccessToken())
2702 .build();
2703
2704
2705
2706
2707 JSONObject jsonResponse;
2708 try (
2709 CloseableHttpResponse response = httpClient.execute(request)
2710 ) {
2711 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_BAD_REQUEST) {
2712 LOGGER.warn("Request returned " + response.getStatusLine());
2713 }
2714 jsonResponse = new JsonResponseHandler().handleResponse(response);
2715 }
2716 return jsonResponse;
2717 }
2718
2719 private GraphObject executeGraphRequest(GraphRequestBuilder httpRequestBuilder) throws IOException {
2720
2721 HttpRequestBase request = httpRequestBuilder
2722 .setAccessToken(token.getAccessToken())
2723 .build();
2724
2725
2726
2727 GraphObject graphObject;
2728 try (
2729 CloseableHttpResponse response = httpClient.execute(request)
2730 ) {
2731 if (response.getStatusLine().getStatusCode() == 400) {
2732 LOGGER.warn("Request returned " + response.getStatusLine());
2733 }
2734 graphObject = new GraphObject(new JsonResponseHandler().handleResponse(response));
2735 graphObject.statusCode = response.getStatusLine().getStatusCode();
2736 }
2737 return graphObject;
2738 }
2739
2740
2741
2742
2743
2744
2745
2746
2747 protected static boolean isItemId(String itemName) {
2748 return itemName.length() >= 140
2749
2750 && itemName.matches("^([A-Za-z0-9-_]{4})*([A-Za-z0-9-_]{4}|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{2}==)\\.EML$")
2751 && itemName.indexOf(' ') < 0;
2752 }
2753
2754
2755 }