Repository: james-project Updated Branches: refs/heads/master dc90ccda5 -> 9ff71bd26
MAILET-149 ICalToJson should not generate ICAL but take it from raw attachment Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/6cdc4d50 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/6cdc4d50 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/6cdc4d50 Branch: refs/heads/master Commit: 6cdc4d503f071a1639b266595641d1d67877738e Parents: dc90ccd Author: Raphael Ouazana <raphael.ouaz...@linagora.com> Authored: Mon Jan 23 17:15:04 2017 +0100 Committer: Benoit Tellier <btell...@linagora.com> Committed: Tue Jan 24 16:48:58 2017 +0700 ---------------------------------------------------------------------- .../transport/mailets/ICALToJsonAttribute.java | 36 ++- .../james/transport/mailets/model/ICAL.java | 5 +- .../mailets/ICALToJsonAttributeTest.java | 227 +++++++++-------- .../james/transport/mailets/model/ICALTest.java | 75 ++---- .../mailets/ICSAttachmentWorkflowTest.java | 245 ++++++++++++++++++- .../src/test/resources/eml/yahooInvitation.eml | 162 ++++++++++++ 6 files changed, 585 insertions(+), 165 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/6cdc4d50/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java ---------------------------------------------------------------------- diff --git a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java index 1e314af..b62ab33 100644 --- a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java +++ b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/ICALToJsonAttribute.java @@ -75,12 +75,15 @@ import net.fortuna.ical4j.model.Calendar; public class ICALToJsonAttribute extends GenericMailet { public static final String SOURCE_ATTRIBUTE_NAME = "source"; + public static final String RAW_SOURCE_ATTRIBUTE_NAME = "rawSource"; public static final String DESTINATION_ATTRIBUTE_NAME = "destination"; public static final String DEFAULT_SOURCE_ATTRIBUTE_NAME = "icalendar"; + public static final String DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME = "attachments"; public static final String DEFAULT_DESTINATION_ATTRIBUTE_NAME = "icalendarJson"; private final ObjectMapper objectMapper; private String sourceAttributeName; + private String rawSourceAttributeName; private String destinationAttributeName; public ICALToJsonAttribute() { @@ -92,6 +95,10 @@ public class ICALToJsonAttribute extends GenericMailet { return sourceAttributeName; } + public String getRawSourceAttributeName() { + return rawSourceAttributeName; + } + public String getDestinationAttributeName() { return destinationAttributeName; } @@ -104,10 +111,14 @@ public class ICALToJsonAttribute extends GenericMailet { @Override public void init() throws MessagingException { sourceAttributeName = getInitParameter(SOURCE_ATTRIBUTE_NAME, DEFAULT_SOURCE_ATTRIBUTE_NAME); + rawSourceAttributeName = getInitParameter(RAW_SOURCE_ATTRIBUTE_NAME, DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME); destinationAttributeName = getInitParameter(DESTINATION_ATTRIBUTE_NAME, DEFAULT_DESTINATION_ATTRIBUTE_NAME); if (Strings.isNullOrEmpty(sourceAttributeName)) { throw new MessagingException(SOURCE_ATTRIBUTE_NAME + " configuration parameter can not be null or empty"); } + if (Strings.isNullOrEmpty(rawSourceAttributeName)) { + throw new MessagingException(RAW_SOURCE_ATTRIBUTE_NAME + " configuration parameter can not be null or empty"); + } if (Strings.isNullOrEmpty(destinationAttributeName)) { throw new MessagingException(DESTINATION_ATTRIBUTE_NAME + " configuration parameter can not be null or empty"); } @@ -118,15 +129,19 @@ public class ICALToJsonAttribute extends GenericMailet { if (mail.getAttribute(sourceAttributeName) == null) { return; } + if (mail.getAttribute(rawSourceAttributeName) == null) { + return; + } if (mail.getSender() == null) { log("Skipping " + mail.getName() + " because no sender"); return; } try { Map<String, Calendar> calendars = getCalendarMap(mail); + Map<String, byte[]> rawCalendars = getRawCalendarMap(mail); Map<String, byte[]> jsonsInByteForm = calendars.entrySet() .stream() - .flatMap(calendar -> toJson(calendar, mail)) + .flatMap(calendar -> toJson(calendar, rawCalendars, mail)) .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue)); mail.setAttribute(destinationAttributeName, (Serializable) jsonsInByteForm); } catch (ClassCastException e) { @@ -139,10 +154,15 @@ public class ICALToJsonAttribute extends GenericMailet { return (Map<String, Calendar>) mail.getAttribute(sourceAttributeName); } - private Stream<Pair<String, byte[]>> toJson(Map.Entry<String, Calendar> entry, Mail mail) { + @SuppressWarnings("unchecked") + private Map<String, byte[]> getRawCalendarMap(Mail mail) { + return (Map<String, byte[]>) mail.getAttribute(rawSourceAttributeName); + } + + private Stream<Pair<String, byte[]>> toJson(Map.Entry<String, Calendar> entry, Map<String, byte[]> rawCalendars, Mail mail) { return mail.getRecipients() .stream() - .flatMap(recipient -> toICAL(entry.getValue(), recipient, mail.getSender())) + .flatMap(recipient -> toICAL(entry, rawCalendars, recipient, mail.getSender())) .flatMap(ical -> toJson(ical, mail.getName())) .map(json -> Pair.of(UUID.randomUUID().toString(), json.getBytes(Charsets.UTF_8))); } @@ -159,10 +179,16 @@ public class ICALToJsonAttribute extends GenericMailet { } } - private Stream<ICAL> toICAL(Calendar calendar, MailAddress recipient, MailAddress sender) { + private Stream<ICAL> toICAL(Map.Entry<String, Calendar> entry, Map<String, byte[]> rawCalendars, MailAddress recipient, MailAddress sender) { + Calendar calendar = entry.getValue(); + byte[] rawICal = rawCalendars.get(entry.getKey()); + if (rawICal == null) { + log("Cannot find matching raw ICAL from key: " + entry.getKey()); + return Stream.of(); + } try { return Stream.of(ICAL.builder() - .from(calendar) + .from(calendar, rawICal) .recipient(recipient) .sender(sender) .build()); http://git-wip-us.apache.org/repos/asf/james-project/blob/6cdc4d50/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java ---------------------------------------------------------------------- diff --git a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java index b873a93..80f1555 100644 --- a/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java +++ b/mailet/icalendar/src/main/java/org/apache/james/transport/mailets/model/ICAL.java @@ -25,6 +25,7 @@ import java.util.Optional; import org.apache.mailet.MailAddress; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import net.fortuna.ical4j.model.Calendar; @@ -45,8 +46,8 @@ public class ICAL { private Optional<String> method = Optional.empty(); private Optional<String> recurrenceId = Optional.empty(); - public Builder from(Calendar calendar) { - this.ical = calendar.toString(); + public Builder from(Calendar calendar, byte[] originalEvent) { + this.ical = new String(originalEvent, Charsets.UTF_8); VEvent vevent = (VEvent) calendar.getComponent("VEVENT"); this.uid = optionalOf(vevent.getUid()); this.method = optionalOf(calendar.getMethod()); http://git-wip-us.apache.org/repos/asf/james-project/blob/6cdc4d50/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java ---------------------------------------------------------------------- diff --git a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java index fdddcaa..574878b 100644 --- a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java +++ b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/ICALToJsonAttributeTest.java @@ -22,6 +22,8 @@ package org.apache.james.transport.mailets; import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,79 +40,16 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import com.fasterxml.jackson.core.io.JsonStringEncoder; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.model.Calendar; public class ICALToJsonAttributeTest { public static final MailAddress SENDER = MailAddressFixture.ANY_AT_JAMES; - public static final String MEETING_ICS = "\"BEGIN:VCALENDAR\\r\\n" + - "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\\r\\n" + - "CALSCALE:GREGORIAN\\r\\n" + - "X-OBM-TIME:1483703436\\r\\n" + - "VERSION:2.0\\r\\n" + - "METHOD:REQUEST\\r\\n" + - "BEGIN:VEVENT\\r\\n" + - "CREATED:20170106T115035Z\\r\\n" + - "LAST-MODIFIED:20170106T115036Z\\r\\n" + - "DTSTAMP:20170106T115036Z\\r\\n" + - "DTSTART:20170111T090000Z\\r\\n" + - "DURATION:PT1H30M\\r\\n" + - "TRANSP:OPAQUE\\r\\n" + - "SEQUENCE:0\\r\\n" + - "SUMMARY:Sprint planning #23\\r\\n" + - "DESCRIPTION:\\r\\n" + - "CLASS:PUBLIC\\r\\n" + - "PRIORITY:5\\r\\n" + - "ORGANIZER;X-OBM-ID=128;CN=Raphael OUAZANA:MAILTO:ouaz...@linagora.com\\r\\n" + - "X-OBM-DOMAIN:linagora.com\\r\\n" + - "X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922\\r\\n" + - "LOCATION:Hangout\\r\\n" + - "CATEGORIES:\\r\\n" + - "X-OBM-COLOR:\\r\\n" + - "UID:f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Matthieu EXT_BAECHLER;PARTSTAT=NEEDS-ACTION;X-OBM-ID=302:MAILTO:baech...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Laura ROYET;PARTSTAT=NEEDS-ACTION;X-OBM-ID=723:MAILTO:ro...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Raphael OUAZANA;PARTSTAT=ACCEPTED;X-OBM-ID=128:MAILTO:ouaz...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Luc DUZAN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=715:MAILTO:du...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=RESOURCE;CN=Salle de reunion Lyon;PARTSTAT=ACCEPTED;X-OBM-ID=66:MAILTO:nore...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Antoine DUPRAT;PARTSTAT=NEEDS-ACTION;X-OBM-ID=453:MAILTO:dup...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=\\\"Benoît TELLIER\\\";PARTSTAT=NEEDS-ACTION;X-OBM-ID=623:MAILTO:tell...@linagora.com\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Quynh Quynh N NGUYEN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=769:MAILTO:ngu...@linagora.com\\r\\n" + - "END:VEVENT\\r\\n" + - "END:VCALENDAR\\r\\n" + - "\""; - public static final String SMALL_ICS = "\"BEGIN:VCALENDAR\\r\\n" + - "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\\r\\n" + - "CALSCALE:GREGORIAN\\r\\n" + - "X-OBM-TIME:1483439571\\r\\n" + - "VERSION:2.0\\r\\n" + - "METHOD:REQUEST\\r\\n" + - "BEGIN:VEVENT\\r\\n" + - "CREATED:20170103T103250Z\\r\\n" + - "LAST-MODIFIED:20170103T103250Z\\r\\n" + - "DTSTAMP:20170103T103250Z\\r\\n" + - "DTSTART:20170120T100000Z\\r\\n" + - "DURATION:PT30M\\r\\n" + - "TRANSP:OPAQUE\\r\\n" + - "SEQUENCE:0\\r\\n" + - "SUMMARY:Sprint Social #3 Demo\\r\\n" + - "DESCRIPTION:\\r\\n" + - "CLASS:PUBLIC\\r\\n" + - "PRIORITY:5\\r\\n" + - "ORGANIZER;X-OBM-ID=468;CN=Attendee 1:MAILTO:attend...@linagora.com\\r\\n" + - "X-OBM-DOMAIN:linagora.com\\r\\n" + - "X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922\\r\\n" + - "LOCATION:hangout\\r\\n" + - "CATEGORIES:\\r\\n" + - "X-OBM-COLOR:\\r\\n" + - "UID:f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047feb2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe\\r\\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Attendee 2;PARTSTAT=NEEDS-ACTION;X-OBM-ID=348:MAILTO:attend...@linagora.com\\r\\n" + - "END:VEVENT\\r\\n" + - "END:VCALENDAR\\r\\n" + - "\""; @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -145,6 +84,15 @@ public class ICALToJsonAttributeTest { } @Test + public void initShouldThrowOnEmptyRawSourceAttribute() throws Exception { + expectedException.expect(MessagingException.class); + + testee.init(FakeMailetConfig.builder() + .setProperty(ICALToJsonAttribute.RAW_SOURCE_ATTRIBUTE_NAME, "") + .build()); + } + + @Test public void initShouldThrowOnEmptyDestinationAttribute() throws Exception { expectedException.expect(MessagingException.class); @@ -157,13 +105,16 @@ public class ICALToJsonAttributeTest { public void initShouldSetAttributesWhenPresent() throws Exception { String destination = "myDestination"; String source = "mySource"; + String raw = "myRaw"; testee.init(FakeMailetConfig.builder() .setProperty(ICALToJsonAttribute.SOURCE_ATTRIBUTE_NAME, source) .setProperty(ICALToJsonAttribute.DESTINATION_ATTRIBUTE_NAME, destination) + .setProperty(ICALToJsonAttribute.RAW_SOURCE_ATTRIBUTE_NAME, raw) .build()); assertThat(testee.getSourceAttributeName()).isEqualTo(source); assertThat(testee.getDestinationAttributeName()).isEqualTo(destination); + assertThat(testee.getRawSourceAttributeName()).isEqualTo(raw); } @Test @@ -196,12 +147,25 @@ public class ICALToJsonAttributeTest { } @Test - public void serviceShouldNotFailOnWrongAttributeParameter() throws Exception { + public void serviceShouldNotFailOnWrongRawAttributeType() throws Exception { testee.init(FakeMailetConfig.builder().build()); - ImmutableMap<String, String> wrongParametrizedMap = ImmutableMap.<String, String>builder() - .put("key", "value") + Mail mail = FakeMail.builder() + .sender(SENDER) + .recipient(MailAddressFixture.OTHER_AT_JAMES) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, "wrong type") .build(); + testee.service(mail); + + assertThat(mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME)) + .isNull(); + } + + @Test + public void serviceShouldNotFailOnWrongAttributeParameter() throws Exception { + testee.init(FakeMailetConfig.builder().build()); + + ImmutableMap<String, String> wrongParametrizedMap = ImmutableMap.of("key", "value"); Mail mail = FakeMail.builder() .sender(SENDER) .recipient(MailAddressFixture.OTHER_AT_JAMES) @@ -214,16 +178,32 @@ public class ICALToJsonAttributeTest { } @Test - public void serviceShouldFilterMailsWithoutSender() throws Exception { + public void serviceShouldNotFailOnWrongRawAttributeParameter() throws Exception { testee.init(FakeMailetConfig.builder().build()); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); - ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder() - .put("key", calendar) + ImmutableMap<String, String> wrongParametrizedMap = ImmutableMap.of("key", "value"); + Mail mail = FakeMail.builder() + .sender(SENDER) + .recipient(MailAddressFixture.OTHER_AT_JAMES) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, wrongParametrizedMap) .build(); + testee.service(mail); + + assertThat(mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME)) + .isNull(); + } + + @Test + public void serviceShouldFilterMailsWithoutSender() throws Exception { + testee.init(FakeMailetConfig.builder().build()); + + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar); Mail mail = FakeMail.builder() .recipient(MailAddressFixture.OTHER_AT_JAMES) .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, icals) .build(); testee.service(mail); @@ -235,13 +215,14 @@ public class ICALToJsonAttributeTest { public void serviceShouldAttachEmptyListWhenNoRecipient() throws Exception { testee.init(FakeMailetConfig.builder().build()); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); - ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder() - .put("key", calendar) - .build(); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar); + ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics); Mail mail = FakeMail.builder() .sender(SENDER) .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, rawIcals) .build(); testee.service(mail); @@ -254,15 +235,16 @@ public class ICALToJsonAttributeTest { public void serviceShouldAttachJson() throws Exception { testee.init(FakeMailetConfig.builder().build()); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); - ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder() - .put("key", calendar) - .build(); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar); + ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics); MailAddress recipient = MailAddressFixture.ANY_AT_JAMES2; Mail mail = FakeMail.builder() .sender(SENDER) .recipient(recipient) .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, rawIcals) .build(); testee.service(mail); @@ -270,7 +252,7 @@ public class ICALToJsonAttributeTest { assertThat(jsons).hasSize(1); assertThatJson(new String(jsons.values().iterator().next(), Charsets.UTF_8)) .isEqualTo("{" + - "\"ical\": " + MEETING_ICS +"," + + "\"ical\": \"" + toJsonValue(ics) +"\"," + "\"sender\": \"" + SENDER.asString() + "\"," + "\"recipient\": \"" + recipient.asString() + "\"," + "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," + @@ -281,19 +263,24 @@ public class ICALToJsonAttributeTest { "}"); } + private String toJsonValue(byte[] ics) throws UnsupportedEncodingException { + return new String(JsonStringEncoder.getInstance().quoteAsUTF8(new String(ics, Charsets.UTF_8)), Charsets.UTF_8); + } + @SuppressWarnings("unchecked") @Test public void serviceShouldAttachJsonForSeveralRecipient() throws Exception { testee.init(FakeMailetConfig.builder().build()); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); - ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder() - .put("key", calendar) - .build(); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar); + ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics); Mail mail = FakeMail.builder() .sender(SENDER) .recipients(MailAddressFixture.OTHER_AT_JAMES, MailAddressFixture.ANY_AT_JAMES2) .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, rawIcals) .build(); testee.service(mail); @@ -302,7 +289,7 @@ public class ICALToJsonAttributeTest { List<String> actual = toSortedValueList(jsons); assertThatJson(actual.get(0)).isEqualTo("{" + - "\"ical\": " + MEETING_ICS +"," + + "\"ical\": \"" + toJsonValue(ics) +"\"," + "\"sender\": \"" + SENDER.asString() + "\"," + "\"recipient\": \"" + MailAddressFixture.ANY_AT_JAMES2.asString() + "\"," + "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," + @@ -312,7 +299,7 @@ public class ICALToJsonAttributeTest { "\"recurrence-id\": null" + "}"); assertThatJson(actual.get(1)).isEqualTo("{" + - "\"ical\": " + MEETING_ICS +"," + + "\"ical\": \"" + toJsonValue(ics) +"\"," + "\"sender\": \"" + SENDER.asString() + "\"," + "\"recipient\": \"" + MailAddressFixture.OTHER_AT_JAMES.asString() + "\"," + "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," + @@ -328,17 +315,18 @@ public class ICALToJsonAttributeTest { public void serviceShouldAttachJsonForSeveralICALs() throws Exception { testee.init(FakeMailetConfig.builder().build()); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); - Calendar calendar2 = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_2.ics")); - ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder() - .put("key", calendar) - .put("key2", calendar2) - .build(); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + byte[] ics2 = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_2.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + Calendar calendar2 = new CalendarBuilder().build(new ByteArrayInputStream(ics2)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar, "key2", calendar2); + ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics, "key2", ics2); MailAddress recipient = MailAddressFixture.OTHER_AT_JAMES; Mail mail = FakeMail.builder() .sender(SENDER) .recipient(recipient) .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, rawIcals) .build(); testee.service(mail); @@ -347,7 +335,7 @@ public class ICALToJsonAttributeTest { List<String> actual = toSortedValueList(jsons); assertThatJson(actual.get(0)).isEqualTo("{" + - "\"ical\": " + SMALL_ICS +"," + + "\"ical\": \"" + toJsonValue(ics2) +"\"," + "\"sender\": \"" + SENDER.asString() + "\"," + "\"recipient\": \"" + recipient.asString() + "\"," + "\"uid\": \"f1514f44bf39311568d64072ac247c17656ceafde3b4b3eba961c8c5184cdc6ee047feb2aab16e43439a608f28671ab7c10e754c301b1e32001ad51dd20eac2fc7af20abf4093bbe\"," + @@ -357,7 +345,7 @@ public class ICALToJsonAttributeTest { "\"recurrence-id\": null" + "}"); assertThatJson(actual.get(1)).isEqualTo("{" + - "\"ical\": " + MEETING_ICS +"," + + "\"ical\": \"" + toJsonValue(ics) +"\"," + "\"sender\": \"" + SENDER.asString() + "\"," + "\"recipient\": \"" + recipient.asString() + "\"," + "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," + @@ -373,17 +361,54 @@ public class ICALToJsonAttributeTest { public void serviceShouldFilterInvalidICS() throws Exception { testee.init(FakeMailetConfig.builder().build()); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); - Calendar calendar2 = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_uid.ics")); - ImmutableMap<String, Calendar> icals = ImmutableMap.<String, Calendar>builder() - .put("key", calendar) - .put("key2", calendar2) + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + byte[] ics2 = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_without_uid.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + Calendar calendar2 = new CalendarBuilder().build(new ByteArrayInputStream(ics2)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar, "key2", calendar2); + ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics, "key2", ics2); + MailAddress recipient = MailAddressFixture.OTHER_AT_JAMES; + Mail mail = FakeMail.builder() + .sender(SENDER) + .recipient(recipient) + .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, rawIcals) .build(); + testee.service(mail); + + Map<String, byte[]> jsons = (Map<String, byte[]>) mail.getAttribute(ICALToJsonAttribute.DEFAULT_DESTINATION_ATTRIBUTE_NAME); + assertThat(jsons).hasSize(1); + List<String> actual = toSortedValueList(jsons); + + assertThatJson(actual.get(0)).isEqualTo("{" + + "\"ical\": \"" + toJsonValue(ics) + "\"," + + "\"sender\": \"" + SENDER.asString() + "\"," + + "\"recipient\": \"" + recipient.asString() + "\"," + + "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," + + "\"sequence\": \"0\"," + + "\"dtstamp\": \"20170106T115036Z\"," + + "\"method\": \"REQUEST\"," + + "\"recurrence-id\": null" + + "}"); + } + + @SuppressWarnings("unchecked") + @Test + public void serviceShouldFilterNonExistingKeys() throws Exception { + testee.init(FakeMailetConfig.builder().build()); + + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + byte[] ics2 = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_2.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); + Calendar calendar2 = new CalendarBuilder().build(new ByteArrayInputStream(ics2)); + ImmutableMap<String, Calendar> icals = ImmutableMap.of("key", calendar, "key2", calendar2); + ImmutableMap<String, byte[]> rawIcals = ImmutableMap.of("key", ics); MailAddress recipient = MailAddressFixture.OTHER_AT_JAMES; Mail mail = FakeMail.builder() .sender(SENDER) .recipient(recipient) .attribute(ICALToJsonAttribute.DEFAULT_SOURCE_ATTRIBUTE_NAME, icals) + .attribute(ICALToJsonAttribute.DEFAULT_RAW_SOURCE_ATTRIBUTE_NAME, rawIcals) .build(); testee.service(mail); @@ -392,7 +417,7 @@ public class ICALToJsonAttributeTest { List<String> actual = toSortedValueList(jsons); assertThatJson(actual.get(0)).isEqualTo("{" + - "\"ical\": " + MEETING_ICS + "," + + "\"ical\": \"" + toJsonValue(ics) + "\"," + "\"sender\": \"" + SENDER.asString() + "\"," + "\"recipient\": \"" + recipient.asString() + "\"," + "\"uid\": \"f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\"," + http://git-wip-us.apache.org/repos/asf/james-project/blob/6cdc4d50/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java ---------------------------------------------------------------------- diff --git a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java index f8ab4ab..b4ce81e 100644 --- a/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java +++ b/mailet/icalendar/src/test/java/org/apache/james/transport/mailets/model/ICALTest.java @@ -21,12 +21,16 @@ package org.apache.james.transport.mailets.model; import static org.assertj.core.api.Assertions.assertThat; +import java.io.ByteArrayInputStream; + import org.apache.mailet.MailAddress; import org.apache.mailet.base.MailAddressFixture; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import com.google.common.io.ByteStreams; + import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.model.Calendar; import nl.jqno.equalsverifier.EqualsVerifier; @@ -50,11 +54,12 @@ public class ICALTest { public void buildShouldFailWhenNoSender() throws Exception { expectedException.expect(NullPointerException.class); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); ICAL.builder() .recipient(MailAddressFixture.ANY_AT_JAMES) - .from(calendar) + .from(calendar, ics) .build(); } @@ -62,25 +67,27 @@ public class ICALTest { public void buildShouldFailWhenNoRecipient() throws Exception { expectedException.expect(NullPointerException.class); - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); ICAL.builder() .sender(MailAddressFixture.OTHER_AT_JAMES) - .from(calendar) + .from(calendar, ics) .build(); } @Test public void buildShouldWork() throws Exception { - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); MailAddress recipient = MailAddressFixture.ANY_AT_JAMES; MailAddress sender = MailAddressFixture.OTHER_AT_JAMES; ICAL ical = ICAL.builder() .recipient(recipient) .sender(sender) - .from(calendar) + .from(calendar, ics) .build(); assertThat(ical.getRecipient()).isEqualTo(recipient.asString()); @@ -92,41 +99,7 @@ public class ICALTest { assertThat(ical.getRecurrenceId()).isEmpty(); assertThat(ical.getDtstamp()).contains("20170106T115036Z"); assertThat(ical.getSequence()).isEqualTo("0"); - assertThat(ical.getIcal()).isEqualTo("BEGIN:VCALENDAR\r\n" + - "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\r\n" + - "CALSCALE:GREGORIAN\r\n" + - "X-OBM-TIME:1483703436\r\n" + - "VERSION:2.0\r\n" + - "METHOD:REQUEST\r\n" + - "BEGIN:VEVENT\r\n" + - "CREATED:20170106T115035Z\r\n" + - "LAST-MODIFIED:20170106T115036Z\r\n" + - "DTSTAMP:20170106T115036Z\r\n" + - "DTSTART:20170111T090000Z\r\n" + - "DURATION:PT1H30M\r\n" + - "TRANSP:OPAQUE\r\n" + - "SEQUENCE:0\r\n" + - "SUMMARY:Sprint planning #23\r\n" + - "DESCRIPTION:\r\n" + - "CLASS:PUBLIC\r\n" + - "PRIORITY:5\r\n" + - "ORGANIZER;X-OBM-ID=128;CN=Raphael OUAZANA:MAILTO:ouaz...@linagora.com\r\n" + - "X-OBM-DOMAIN:linagora.com\r\n" + - "X-OBM-DOMAIN-UUID:02874f7c-d10e-102f-acda-0015176f7922\r\n" + - "LOCATION:Hangout\r\n" + - "CATEGORIES:\r\n" + - "X-OBM-COLOR:\r\n" + - "UID:f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Matthieu EXT_BAECHLER;PARTSTAT=NEEDS-ACTION;X-OBM-ID=302:MAILTO:baech...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Laura ROYET;PARTSTAT=NEEDS-ACTION;X-OBM-ID=723:MAILTO:ro...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Raphael OUAZANA;PARTSTAT=ACCEPTED;X-OBM-ID=128:MAILTO:ouaz...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Luc DUZAN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=715:MAILTO:du...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=RESOURCE;CN=Salle de reunion Lyon;PARTSTAT=ACCEPTED;X-OBM-ID=66:MAILTO:nore...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Antoine DUPRAT;PARTSTAT=NEEDS-ACTION;X-OBM-ID=453:MAILTO:dup...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=\"Benoît TELLIER\";PARTSTAT=NEEDS-ACTION;X-OBM-ID=623:MAILTO:tell...@linagora.com\r\n" + - "ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Quynh Quynh N NGUYEN;PARTSTAT=NEEDS-ACTION;X-OBM-ID=769:MAILTO:ngu...@linagora.com\r\n" + - "END:VEVENT\r\n" + - "END:VCALENDAR\r\n"); + assertThat(ical.getIcal()).isEqualTo(new String(ics, "UTF-8")); } @Test @@ -136,7 +109,8 @@ public class ICALTest { @Test public void buildShouldThrowOnCalendarWithoutDtstamp() throws Exception { - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_dtstamp.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_without_dtstamp.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); expectedException.expect(IllegalStateException.class); @@ -145,13 +119,14 @@ public class ICALTest { ICAL.builder() .recipient(recipient) .sender(sender) - .from(calendar) + .from(calendar, ics) .build(); } @Test public void buildShouldThrowOnCalendarWithoutUid() throws Exception { - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_uid.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_without_uid.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); expectedException.expect(IllegalStateException.class); @@ -160,13 +135,14 @@ public class ICALTest { ICAL.builder() .recipient(recipient) .sender(sender) - .from(calendar) + .from(calendar, ics) .build(); } @Test public void buildShouldThrowOnCalendarWithoutMethod() throws Exception { - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_method.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_without_method.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); expectedException.expect(IllegalStateException.class); @@ -175,20 +151,21 @@ public class ICALTest { ICAL.builder() .recipient(recipient) .sender(sender) - .from(calendar) + .from(calendar, ics) .build(); } @Test public void buildShouldSetDefaultValueWhenCalendarWithoutSequence() throws Exception { - Calendar calendar = new CalendarBuilder().build(ClassLoader.getSystemResourceAsStream("ics/meeting_without_sequence.ics")); + byte[] ics = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream("ics/meeting_without_sequence.ics")); + Calendar calendar = new CalendarBuilder().build(new ByteArrayInputStream(ics)); MailAddress recipient = MailAddressFixture.ANY_AT_JAMES; MailAddress sender = MailAddressFixture.OTHER_AT_JAMES; ICAL ical = ICAL.builder() .recipient(recipient) .sender(sender) - .from(calendar) + .from(calendar, ics) .build(); assertThat(ical.getSequence()).isEqualTo(ICAL.DEFAULT_SEQUENCE_VALUE); http://git-wip-us.apache.org/repos/asf/james-project/blob/6cdc4d50/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java index 9302fe7..2cd1b5c 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java @@ -33,6 +33,7 @@ import org.apache.james.mailets.configuration.MailetContainer; import org.apache.james.mailets.configuration.ProcessorConfiguration; import org.apache.james.mailets.utils.IMAPMessageReader; import org.apache.james.mailets.utils.SMTPMessageSender; +import org.apache.james.queue.activemq.MimeMessageBlobMessageSource; import org.apache.james.transport.mailets.amqp.AmqpRule; import org.apache.james.util.streams.SwarmGenericContainer; import org.apache.mailet.Mail; @@ -68,7 +69,9 @@ public class ICSAttachmentWorkflowTest { private static final String FROM = "fromUser@" + JAMES_APACHE_ORG; private static final String RECIPIENT = "touser@" + JAMES_APACHE_ORG; - private static final String MAIL_ATTRIBUTE = "my.attribute"; + private static final String MAIL_ATTRIBUTE = "ical.attachments"; + private static final String PARSED_ICAL_MAIL_ATTRIBUTE = "ical.parsed"; + private static final String JSON_MAIL_ATTRIBUTE = "ical.json"; private static final String EXCHANGE_NAME = "myExchange"; private static final String ROUTING_KEY = "myRoutingKey"; @@ -228,8 +231,202 @@ public class ICSAttachmentWorkflowTest { "LU1PRElGSUVEOjIwMTcwMTE5VDE5MTgyM1oNCkxPQ0FUSU9OOg0KU0VRVUVOQ0U6MA0KU1RB\n" + "VFVTOkNPTkZJUk1FRA0KU1VNTUFSWToNClRSQU5TUDpPUEFRVUUNCkVORDpWRVZFTlQNCkVO\n" + "RDpWQ0FMRU5EQVINCg=="; - public static final String ICS_BASE64_UID = "ah86k5m342bmcrbe9khkkhl...@google.com"; - public static final String ICS_BASE64_DTSTAMP = "20170119T191823Z"; + private static final String ICS_BASE64_UID = "ah86k5m342bmcrbe9khkkhl...@google.com"; + private static final String ICS_BASE64_DTSTAMP = "20170119T191823Z"; + private static final String ICS_YAHOO = "BEGIN:VCALENDAR\r\n" + + "PRODID://Yahoo//Calendar//EN\r\n" + + "VERSION:2.0\r\n" + + "METHOD:REQUEST\r\n" + + "BEGIN:VEVENT\r\n" + + "SUMMARY:Test from Yahoo\r\n" + + "CLASS:PUBLIC\r\n" + + "DTSTART;TZID=Europe/Brussels:20170127T150000\r\n" + + "DTEND;TZID=Europe/Brussels:20170127T160000\r\n" + + "LOCATION:Somewhere\r\n" + + "PRIORITY:0\r\n" + + "SEQUENCE:0\r\n" + + "STATUS:CONFIRMED\r\n" + + "UID:5014513f-1026-4b58-82cf-80d4fc060bbe\r\n" + + "DTSTAMP:20170123T121635Z\r\n" + + "ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ_PARTICIPANT;RSVP=TRUE;SCHEDULE-STAT\r\n" + + " US=1.1:mailto:ddolcimasc...@linagora.com\r\n" + + "ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ_PARTICIPANT;RSVP=TRUE;SCHEDULE-STAT\r\n" + + " US=1.1:mailto:rouaz...@linagora.com\r\n" + + "ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ_PARTICIPANT;RSVP=TRUE;SCHEDULE-STAT\r\n" + + " US=1.1:mailto:adup...@linagora.com\r\n" + + "ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ_PARTICIPANT;RSVP=TRUE;SCHEDULE-STAT\r\n" + + " US=1.1:mailto:btell...@linagora.com\r\n" + + "ORGANIZER;CN=OBM Linagora;SENT-BY=\"mailto:obmlinag...@yahoo.fr\":mailto:obml\r\n" + + " inag...@yahoo.fr\r\n" + + "X-YAHOO-YID:obmlinagora\r\n" + + "TRANSP:OPAQUE\r\n" + + "STATUS:CONFIRMED\r\n" + + "X-YAHOO-USER-STATUS:BUSY\r\n" + + "X-YAHOO-EVENT-STATUS:BUSY\r\n" + + "END:VEVENT\r\n" + + "BEGIN:VTIMEZONE\r\n" + + "TZID:Europe/Brussels\r\n" + + "TZURL:http://tzurl.org/zoneinfo/Europe/Brussels\r\n" + + "X-LIC-LOCATION:Europe/Brussels\r\n" + + "BEGIN:DAYLIGHT\r\n" + + "TZOFFSETFROM:+0100\r\n" + + "TZOFFSETTO:+0200\r\n" + + "TZNAME:CEST\r\n" + + "DTSTART:19810329T020000\r\n" + + "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\n" + + "END:DAYLIGHT\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+0200\r\n" + + "TZOFFSETTO:+0100\r\n" + + "TZNAME:CET\r\n" + + "DTSTART:19961027T030000\r\n" + + "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\n" + + "END:STANDARD\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+001730\r\n" + + "TZOFFSETTO:+001730\r\n" + + "TZNAME:BMT\r\n" + + "DTSTART:18800101T000000\r\n" + + "RDATE:18800101T000000\r\n" + + "END:STANDARD\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+001730\r\n" + + "TZOFFSETTO:+0000\r\n" + + "TZNAME:WET\r\n" + + "DTSTART:18920501T120000\r\n" + + "RDATE:18920501T120000\r\n" + + "END:STANDARD\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+0000\r\n" + + "TZOFFSETTO:+0100\r\n" + + "TZNAME:CET\r\n" + + "DTSTART:19141108T000000\r\n" + + "RDATE:19141108T000000\r\n" + + "END:STANDARD\r\n" + + "BEGIN:DAYLIGHT\r\n" + + "TZOFFSETFROM:+0100\r\n" + + "TZOFFSETTO:+0200\r\n" + + "TZNAME:CEST\r\n" + + "DTSTART:19160501T000000\r\n" + + "RDATE:19160501T000000\r\n" + + "RDATE:19170416T020000\r\n" + + "RDATE:19180415T020000\r\n" + + "RDATE:19400520T030000\r\n" + + "RDATE:19430329T020000\r\n" + + "RDATE:19440403T020000\r\n" + + "RDATE:19450402T020000\r\n" + + "RDATE:19460519T020000\r\n" + + "RDATE:19770403T020000\r\n" + + "RDATE:19780402T020000\r\n" + + "RDATE:19790401T020000\r\n" + + "RDATE:19800406T020000\r\n" + + "END:DAYLIGHT\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+0200\r\n" + + "TZOFFSETTO:+0100\r\n" + + "TZNAME:CET\r\n" + + "DTSTART:19161001T010000\r\n" + + "RDATE:19161001T010000\r\n" + + "RDATE:19170917T030000\r\n" + + "RDATE:19180916T030000\r\n" + + "RDATE:19421102T030000\r\n" + + "RDATE:19431004T030000\r\n" + + "RDATE:19440917T030000\r\n" + + "RDATE:19450916T030000\r\n" + + "RDATE:19461007T030000\r\n" + + "RDATE:19770925T030000\r\n" + + "RDATE:19781001T030000\r\n" + + "RDATE:19790930T030000\r\n" + + "RDATE:19800928T030000\r\n" + + "RDATE:19810927T030000\r\n" + + "RDATE:19820926T030000\r\n" + + "RDATE:19830925T030000\r\n" + + "RDATE:19840930T030000\r\n" + + "RDATE:19850929T030000\r\n" + + "RDATE:19860928T030000\r\n" + + "RDATE:19870927T030000\r\n" + + "RDATE:19880925T030000\r\n" + + "RDATE:19890924T030000\r\n" + + "RDATE:19900930T030000\r\n" + + "RDATE:19910929T030000\r\n" + + "RDATE:19920927T030000\r\n" + + "RDATE:19930926T030000\r\n" + + "RDATE:19940925T030000\r\n" + + "RDATE:19950924T030000\r\n" + + "END:STANDARD\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+0100\r\n" + + "TZOFFSETTO:+0000\r\n" + + "TZNAME:WET\r\n" + + "DTSTART:19181111T120000\r\n" + + "RDATE:19181111T120000\r\n" + + "RDATE:19191005T000000\r\n" + + "RDATE:19201024T000000\r\n" + + "RDATE:19211026T000000\r\n" + + "RDATE:19221008T000000\r\n" + + "RDATE:19231007T000000\r\n" + + "RDATE:19241005T000000\r\n" + + "RDATE:19251004T000000\r\n" + + "RDATE:19261003T000000\r\n" + + "RDATE:19271002T000000\r\n" + + "RDATE:19281007T030000\r\n" + + "RDATE:19291006T030000\r\n" + + "RDATE:19301005T030000\r\n" + + "RDATE:19311004T030000\r\n" + + "RDATE:19321002T030000\r\n" + + "RDATE:19331008T030000\r\n" + + "RDATE:19341007T030000\r\n" + + "RDATE:19351006T030000\r\n" + + "RDATE:19361004T030000\r\n" + + "RDATE:19371003T030000\r\n" + + "RDATE:19381002T030000\r\n" + + "RDATE:19391119T030000\r\n" + + "END:STANDARD\r\n" + + "BEGIN:DAYLIGHT\r\n" + + "TZOFFSETFROM:+0000\r\n" + + "TZOFFSETTO:+0100\r\n" + + "TZNAME:WEST\r\n" + + "DTSTART:19190301T230000\r\n" + + "RDATE:19190301T230000\r\n" + + "RDATE:19200214T230000\r\n" + + "RDATE:19210314T230000\r\n" + + "RDATE:19220325T230000\r\n" + + "RDATE:19230421T230000\r\n" + + "RDATE:19240329T230000\r\n" + + "RDATE:19250404T230000\r\n" + + "RDATE:19260417T230000\r\n" + + "RDATE:19270409T230000\r\n" + + "RDATE:19280414T230000\r\n" + + "RDATE:19290421T020000\r\n" + + "RDATE:19300413T020000\r\n" + + "RDATE:19310419T020000\r\n" + + "RDATE:19320403T020000\r\n" + + "RDATE:19330326T020000\r\n" + + "RDATE:19340408T020000\r\n" + + "RDATE:19350331T020000\r\n" + + "RDATE:19360419T020000\r\n" + + "RDATE:19370404T020000\r\n" + + "RDATE:19380327T020000\r\n" + + "RDATE:19390416T020000\r\n" + + "RDATE:19400225T020000\r\n" + + "END:DAYLIGHT\r\n" + + "BEGIN:DAYLIGHT\r\n" + + "TZOFFSETFROM:+0200\r\n" + + "TZOFFSETTO:+0200\r\n" + + "TZNAME:CEST\r\n" + + "DTSTART:19440903T000000\r\n" + + "RDATE:19440903T000000\r\n" + + "END:DAYLIGHT\r\n" + + "BEGIN:STANDARD\r\n" + + "TZOFFSETFROM:+0100\r\n" + + "TZOFFSETTO:+0100\r\n" + + "TZNAME:CET\r\n" + + "DTSTART:19770101T000000\r\n" + + "RDATE:19770101T000000\r\n" + + "END:STANDARD\r\n" + + "END:VTIMEZONE\r\n" + + "END:VCALENDAR\r\n" + + ""; public SwarmGenericContainer rabbitMqContainer = new SwarmGenericContainer("rabbitmq:3") .withAffinityToContainer(); @@ -245,6 +442,7 @@ public class ICSAttachmentWorkflowTest { private MimeMessage messageWithICSAttached; private MimeMessage messageWithICSBase64Attached; private MimeMessage messageWithThreeICSAttached; + private MimeMessage yahooInvitationMessage; @Before public void setup() throws Exception { @@ -276,25 +474,26 @@ public class ICSAttachmentWorkflowTest { .match("All") .clazz("ICalendarParser") .addProperty("sourceAttribute", MAIL_ATTRIBUTE) - .addProperty("destinationAttribute", MAIL_ATTRIBUTE) + .addProperty("destinationAttribute", PARSED_ICAL_MAIL_ATTRIBUTE) .build()) .addMailet(MailetConfiguration.builder() .match("All") .clazz("ICALToHeader") - .addProperty("attribute", MAIL_ATTRIBUTE) + .addProperty("attribute", PARSED_ICAL_MAIL_ATTRIBUTE) .build()) .addMailet(MailetConfiguration.builder() .match("All") .clazz("ICALToJsonAttribute") - .addProperty("source", MAIL_ATTRIBUTE) - .addProperty("destination", MAIL_ATTRIBUTE) + .addProperty("source", PARSED_ICAL_MAIL_ATTRIBUTE) + .addProperty("rawSource", MAIL_ATTRIBUTE) + .addProperty("destination", JSON_MAIL_ATTRIBUTE) .build()) .addMailet(MailetConfiguration.builder() .match("All") .clazz("AmqpForwardAttribute") .addProperty("uri", amqpRule.getAmqpUri()) .addProperty("exchange", EXCHANGE_NAME) - .addProperty("attribute", MAIL_ATTRIBUTE) + .addProperty("attribute", JSON_MAIL_ATTRIBUTE) .addProperty("routing_key", ROUTING_KEY) .build()) .addMailet(MailetConfiguration.builder() @@ -352,6 +551,8 @@ public class ICSAttachmentWorkflowTest { .setSubject("test") .build(); + yahooInvitationMessage = new MimeMessage(null, ClassLoader.getSystemResourceAsStream("yahooInvitation.eml")); + messageWithThreeICSAttached = MimeMessageBuilder.mimeMessageBuilder() .setMultipartWithBodyParts( MimeMessageBuilder.bodyPartBuilder() @@ -531,6 +732,34 @@ public class ICSAttachmentWorkflowTest { } @Test + public void yahooBase64CalendarAttachmentShouldBePublishedInMQWhenMatchingWorkflowConfiguration() throws Exception { + Mail mail = FakeMail.builder() + .mimeMessage(yahooInvitationMessage) + .sender(new MailAddress(FROM)) + .recipient(new MailAddress(RECIPIENT)) + .build(); + + try (SMTPMessageSender messageSender = SMTPMessageSender.noAuthentication(LOCALHOST_IP, SMTP_PORT, JAMES_APACHE_ORG); + IMAPMessageReader imapMessageReader = new IMAPMessageReader(LOCALHOST_IP, IMAP_PORT)) { + messageSender.sendMessage(mail); + calmlyAwait.atMost(Duration.ONE_MINUTE).until(messageSender::messageHasBeenSent); + calmlyAwait.atMost(Duration.ONE_MINUTE).until(() -> imapMessageReader.userReceivedMessage(RECIPIENT, PASSWORD)); + } + + Optional<String> content = amqpRule.readContent(); + assertThat(content).isPresent(); + DocumentContext jsonPath = toJsonPath(content.get()); + assertThat(jsonPath.<String> read("sender")).isEqualTo(FROM); + assertThat(jsonPath.<String> read("recipient")).isEqualTo(RECIPIENT); + assertThat(jsonPath.<String> read("uid")).isEqualTo("5014513f-1026-4b58-82cf-80d4fc060bbe"); + assertThat(jsonPath.<String> read("sequence")).isEqualTo("0"); + assertThat(jsonPath.<String> read("dtstamp")).isEqualTo("20170123T121635Z"); + assertThat(jsonPath.<String> read("method")).isEqualTo("REQUEST"); + assertThat(jsonPath.<String> read("ical")).isEqualTo(ICS_YAHOO); + assertThat(jsonPath.<String> read("recurrence-id")).isNull(); + } + + @Test public void headersShouldBeFilledOnlyWithOneICalAttachmentWhenMailHasSeveral() throws Exception { Mail mail = FakeMail.builder() .mimeMessage(messageWithThreeICSAttached) http://git-wip-us.apache.org/repos/asf/james-project/blob/6cdc4d50/server/mailet/integration-testing/src/test/resources/eml/yahooInvitation.eml ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/resources/eml/yahooInvitation.eml b/server/mailet/integration-testing/src/test/resources/eml/yahooInvitation.eml new file mode 100644 index 0000000..aef88a8 --- /dev/null +++ b/server/mailet/integration-testing/src/test/resources/eml/yahooInvitation.eml @@ -0,0 +1,162 @@ +Return-Path: <do-not-re...@yahoo.com> +Date: 23 Jan 2017 12:16:35 +0000 +To: raph.ouaz...@linagora.com +From: "OBM Linagora" <obmlinag...@yahoo.fr> +Reply-To: "OBM Linagora" <obmlinag...@yahoo.fr> +MIME-Version: 1.0 +X-Yahoo-Newman-Property: calendar-remind +X-Yahoo-Newman-Id: calendar-remind-87cf88d6-0909-4010-99a4-f156b375adbd +Subject: ***SPAM*** =?UTF-8?Q?Invitation=C3=82=C2=A0:?= Test from Yahoo +Content-Type: multipart/mixed; + boundary="_004_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_" +Message-Id: <20170123121641.2505248D3F@obm3-ui.linagora.dc1> + + +--_004_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_ +Content-Type: multipart/alternative; + boundary="_000_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_" + +--_000_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_ +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: quoted-printable + + +--_000_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_ +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: 8bit + +<div style="min-width: 300px; max-width: 585px; padding: 15px 15px 15px 15px; font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 13px; "> + + <table style="margin: 0px 0px 0px 0px" cellspacing="0" cellpadding="0"> + <tr> + <td> +<div style="width:55px;height:55px;border-radius:5px;text-align:center;font-family:Helvetica Neue medium, Helvetica, Arial, sans-serif;color:white;background-color:#400090;"> + <div style="padding-top:5px;line-height:20px"> + <div style="font-size:12px;font-weight:lighter;"> + JANV. + </div> + <div style="font-size:32px;font-weight:bolder;font-family:Helvetica Neue bold, Helvetica, Arial, sans-serif;"> + 27 + </div> + </div> + </div> + </td> + <td width="40px" /> + <td + style="font-size: 24px; vertical-align: middle; color: #454545; font-weight: lighter; line-height: 26px;"> + + "Test from Yahoo" + + + </td> + </tr> + + </table> + + <table style="text-shadow: white; border-collapse: collapse; margin-top: 30px; font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 13px; color: #454545" + cellspacing="0" cellpadding="0"> + <tr> + <td style="color: #888888; vertical-align: top;"> + + Quand + + </td> + + <td width="40px" /> + <td style="line-height: 18px;"> + vendredi, 27 janvier 2017<br> + 03:00 PM à 04:00 PM <br> + (GMT+01:00) Bruxelles/Copenhague/Madrid/Paris <br> + + + </td> + + </tr> + <tr height="30px" /> + + + <tr> + <td style="color: #888888; vertical-align: top;"> + Où + </td> + <td width="40px" /> + <td> + Somewhere + </td> + </tr> + <tr height="30px" /> + + + + + + <tr> + <td style="color: #888888; vertical-align: top;"> + Invités + </td> + + <td width="40px" /> + <td style="line-height: 18px;"> + + ddolcimasc...@linagora.com<br> + rouaz...@linagora.com<br> + adup...@linagora.com<br> + btell...@linagora.com<br> + + </td> + </tr> + <tr height="30px" /> + + <tr> + <td></td> + <td width="40px" /> + <td> + <div style="background-color:#400090;border-radius:5px;height:35px;width:170px;text-align:center;text-shadow:white;line-height:30px;"> + <a target="_top" href="http://calendar.yahoo.com/obmlinagora/rsvp?e=rouaz...@linagora.com&uid=5014513f-1026-4b58-82cf-80d4fc060bbe&tk=0A1DKATbQagrUVo_7LICZiJtPyY-&hh=Ku0PHoAKTgogl3Ops44PvkDuKNM-" + style="text-decoration:none;font-size:15px;color:white;font-weight:normal;">Répondre</a> + </div> + </td> + </tr> + + </table> + <div style="border-bottom: 1px solid #d5d5d5; margin-top: 50px; max-width: 590px; width: 100%;"> + + + + </div> + + <div + style="text-align: center; margin-top: 15px; font-family: helvetical, Arial, sans-serif; color: #878787; font-size: 12px; padding-bottom: 10px; max-width: 590px;"> + <span style="font-size: 12px; font-family: Helvetica Neue, Helvetica, Arial, sans-serif; color: #454545; text-shadow: white;"> Cette invitation à un évènement a été envoyée à partir de <a target="_blank" href="http://calendar.yahoo.com" + style="text-decoration: none; text-shadow: white; color: #2862c5;">Yahoo Agenda</a> + </span> + </div> + </div> +</div> + + + +--_000_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_ +Content-Type: text/calendar; method=REQUEST; charset=UTF-8 +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6Ly9ZYWhvby8vQ2FsZW5kYXIvL0VODQpWRVJTSU9OOjIuMA0KTUVUSE9EOlJFUVVFU1QNCkJFR0lOOlZFVkVOVA0KU1VNTUFSWTpUZXN0IGZyb20gWWFob28NCkNMQVNTOlBVQkxJQw0KRFRTVEFSVDtUWklEPUV1cm9wZS9CcnVzc2VsczoyMDE3MDEyN1QxNTAwMDANCkRURU5EO1RaSUQ9RXVyb3BlL0JydXNzZWxzOjIwMTcwMTI3VDE2MDAwMA0KTE9DQVRJT046U29tZXdoZXJlDQpQUklPUklUWTowDQpTRVFVRU5DRTowDQpTVEFUVVM6Q09ORklSTUVEDQpVSUQ6NTAxNDUxM2YtMTAyNi00YjU4LTgyY2YtODBkNGZjMDYwYmJlDQpEVFNUQU1QOjIwMTcwMTIzVDEyMTYzNVoNCkFUVEVOREVFO1BBUlRTVEFUPU5FRURTLUFDVElPTjtST0xFPVJFUV9QQVJUSUNJUEFOVDtSU1ZQPVRSVUU7U0NIRURVTEUtU1RBVA0KIFVTPTEuMTptYWlsdG86ZGRvbGNpbWFzY29sb0BsaW5hZ29yYS5jb20NCkFUVEVOREVFO1BBUlRTVEFUPU5FRURTLUFDVElPTjtST0xFPVJFUV9QQVJUSUNJUEFOVDtSU1ZQPVRSVUU7U0NIRURVTEUtU1RBVA0KIFVTPTEuMTptYWlsdG86cm91YXphbmFAbGluYWdvcmEuY29tDQpBVFRFTkRFRTtQQVJUU1RBVD1ORUVEUy1BQ1RJT047Uk9MRT1SRVFfUEFSVElDSVBBTlQ7UlNWUD1UUlVFO1NDSEVEVUxFLVNUQVQNCiBVUz0xLjE6bWFpbHRvOmFkdXByYXRAbGluYWdvcmEuY29tDQpBVFRFTkRFRTtQQVJUU1RBVD1ORUVEUy1BQ1RJT047Uk9MRT1SRVFfUEFSVElDSVBBT l + Q7UlNWUD1UUlVFO1NDSEVEVUxFLVNUQVQNCiBVUz0xLjE6bWFpbHRvOmJ0ZWxsaWVyQGxpbmFnb3JhLmNvbQ0KT1JHQU5JWkVSO0NOPU9CTSBMaW5hZ29yYTtTRU5ULUJZPSJtYWlsdG86b2JtbGluYWdvcmFAeWFob28uZnIiOm1haWx0bzpvYm1sDQogaW5hZ29yYUB5YWhvby5mcg0KWC1ZQUhPTy1ZSUQ6b2JtbGluYWdvcmENClRSQU5TUDpPUEFRVUUNClNUQVRVUzpDT05GSVJNRUQNClgtWUFIT08tVVNFUi1TVEFUVVM6QlVTWQ0KWC1ZQUhPTy1FVkVOVC1TVEFUVVM6QlVTWQ0KRU5EOlZFVkVOVA0KQkVHSU46VlRJTUVaT05FDQpUWklEOkV1cm9wZS9CcnVzc2Vscw0KVFpVUkw6aHR0cDovL3R6dXJsLm9yZy96b25laW5mby9FdXJvcGUvQnJ1c3NlbHMNClgtTElDLUxPQ0FUSU9OOkV1cm9wZS9CcnVzc2Vscw0KQkVHSU46REFZTElHSFQNClRaT0ZGU0VURlJPTTorMDEwMA0KVFpPRkZTRVRUTzorMDIwMA0KVFpOQU1FOkNFU1QNCkRUU1RBUlQ6MTk4MTAzMjlUMDIwMDAwDQpSUlVMRTpGUkVRPVlFQVJMWTtCWU1PTlRIPTM7QllEQVk9LTFTVQ0KRU5EOkRBWUxJR0hUDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9NOiswMjAwDQpUWk9GRlNFVFRPOiswMTAwDQpUWk5BTUU6Q0VUDQpEVFNUQVJUOjE5OTYxMDI3VDAzMDAwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT0tMVNVDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOlNUQU5EQVJEDQpUWk9GRlNFVEZST006KzAwMTczMA0KVFpPRkZTRVRUTzorMD A + xNzMwDQpUWk5BTUU6Qk1UDQpEVFNUQVJUOjE4ODAwMTAxVDAwMDAwMA0KUkRBVEU6MTg4MDAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOlNUQU5EQVJEDQpUWk9GRlNFVEZST006KzAwMTczMA0KVFpPRkZTRVRUTzorMDAwMA0KVFpOQU1FOldFVA0KRFRTVEFSVDoxODkyMDUwMVQxMjAwMDANClJEQVRFOjE4OTIwNTAxVDEyMDAwMA0KRU5EOlNUQU5EQVJEDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9NOiswMDAwDQpUWk9GRlNFVFRPOiswMTAwDQpUWk5BTUU6Q0VUDQpEVFNUQVJUOjE5MTQxMTA4VDAwMDAwMA0KUkRBVEU6MTkxNDExMDhUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOkRBWUxJR0hUDQpUWk9GRlNFVEZST006KzAxMDANClRaT0ZGU0VUVE86KzAyMDANClRaTkFNRTpDRVNUDQpEVFNUQVJUOjE5MTYwNTAxVDAwMDAwMA0KUkRBVEU6MTkxNjA1MDFUMDAwMDAwDQpSREFURToxOTE3MDQxNlQwMjAwMDANClJEQVRFOjE5MTgwNDE1VDAyMDAwMA0KUkRBVEU6MTk0MDA1MjBUMDMwMDAwDQpSREFURToxOTQzMDMyOVQwMjAwMDANClJEQVRFOjE5NDQwNDAzVDAyMDAwMA0KUkRBVEU6MTk0NTA0MDJUMDIwMDAwDQpSREFURToxOTQ2MDUxOVQwMjAwMDANClJEQVRFOjE5NzcwNDAzVDAyMDAwMA0KUkRBVEU6MTk3ODA0MDJUMDIwMDAwDQpSREFURToxOTc5MDQwMVQwMjAwMDANClJEQVRFOjE5ODAwNDA2VDAyMDAwMA0KRU5EOkRBWUxJR0hUDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9 N + OiswMjAwDQpUWk9GRlNFVFRPOiswMTAwDQpUWk5BTUU6Q0VUDQpEVFNUQVJUOjE5MTYxMDAxVDAxMDAwMA0KUkRBVEU6MTkxNjEwMDFUMDEwMDAwDQpSREFURToxOTE3MDkxN1QwMzAwMDANClJEQVRFOjE5MTgwOTE2VDAzMDAwMA0KUkRBVEU6MTk0MjExMDJUMDMwMDAwDQpSREFURToxOTQzMTAwNFQwMzAwMDANClJEQVRFOjE5NDQwOTE3VDAzMDAwMA0KUkRBVEU6MTk0NTA5MTZUMDMwMDAwDQpSREFURToxOTQ2MTAwN1QwMzAwMDANClJEQVRFOjE5NzcwOTI1VDAzMDAwMA0KUkRBVEU6MTk3ODEwMDFUMDMwMDAwDQpSREFURToxOTc5MDkzMFQwMzAwMDANClJEQVRFOjE5ODAwOTI4VDAzMDAwMA0KUkRBVEU6MTk4MTA5MjdUMDMwMDAwDQpSREFURToxOTgyMDkyNlQwMzAwMDANClJEQVRFOjE5ODMwOTI1VDAzMDAwMA0KUkRBVEU6MTk4NDA5MzBUMDMwMDAwDQpSREFURToxOTg1MDkyOVQwMzAwMDANClJEQVRFOjE5ODYwOTI4VDAzMDAwMA0KUkRBVEU6MTk4NzA5MjdUMDMwMDAwDQpSREFURToxOTg4MDkyNVQwMzAwMDANClJEQVRFOjE5ODkwOTI0VDAzMDAwMA0KUkRBVEU6MTk5MDA5MzBUMDMwMDAwDQpSREFURToxOTkxMDkyOVQwMzAwMDANClJEQVRFOjE5OTIwOTI3VDAzMDAwMA0KUkRBVEU6MTk5MzA5MjZUMDMwMDAwDQpSREFURToxOTk0MDkyNVQwMzAwMDANClJEQVRFOjE5OTUwOTI0VDAzMDAwMA0KRU5EOlNUQU5EQVJEDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9NOiswMTAwDQpUWk9GRlNFVFRPOisw M + DAwDQpUWk5BTUU6V0VUDQpEVFNUQVJUOjE5MTgxMTExVDEyMDAwMA0KUkRBVEU6MTkxODExMTFUMTIwMDAwDQpSREFURToxOTE5MTAwNVQwMDAwMDANClJEQVRFOjE5MjAxMDI0VDAwMDAwMA0KUkRBVEU6MTkyMTEwMjZUMDAwMDAwDQpSREFURToxOTIyMTAwOFQwMDAwMDANClJEQVRFOjE5MjMxMDA3VDAwMDAwMA0KUkRBVEU6MTkyNDEwMDVUMDAwMDAwDQpSREFURToxOTI1MTAwNFQwMDAwMDANClJEQVRFOjE5MjYxMDAzVDAwMDAwMA0KUkRBVEU6MTkyNzEwMDJUMDAwMDAwDQpSREFURToxOTI4MTAwN1QwMzAwMDANClJEQVRFOjE5MjkxMDA2VDAzMDAwMA0KUkRBVEU6MTkzMDEwMDVUMDMwMDAwDQpSREFURToxOTMxMTAwNFQwMzAwMDANClJEQVRFOjE5MzIxMDAyVDAzMDAwMA0KUkRBVEU6MTkzMzEwMDhUMDMwMDAwDQpSREFURToxOTM0MTAwN1QwMzAwMDANClJEQVRFOjE5MzUxMDA2VDAzMDAwMA0KUkRBVEU6MTkzNjEwMDRUMDMwMDAwDQpSREFURToxOTM3MTAwM1QwMzAwMDANClJEQVRFOjE5MzgxMDAyVDAzMDAwMA0KUkRBVEU6MTkzOTExMTlUMDMwMDAwDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOkRBWUxJR0hUDQpUWk9GRlNFVEZST006KzAwMDANClRaT0ZGU0VUVE86KzAxMDANClRaTkFNRTpXRVNUDQpEVFNUQVJUOjE5MTkwMzAxVDIzMDAwMA0KUkRBVEU6MTkxOTAzMDFUMjMwMDAwDQpSREFURToxOTIwMDIxNFQyMzAwMDANClJEQVRFOjE5MjEwMzE0VDIzMDAwMA0KUkRBVEU6MTkyMjAzMjVUMjMwMDAwDQpSR E + FURToxOTIzMDQyMVQyMzAwMDANClJEQVRFOjE5MjQwMzI5VDIzMDAwMA0KUkRBVEU6MTkyNTA0MDRUMjMwMDAwDQpSREFURToxOTI2MDQxN1QyMzAwMDANClJEQVRFOjE5MjcwNDA5VDIzMDAwMA0KUkRBVEU6MTkyODA0MTRUMjMwMDAwDQpSREFURToxOTI5MDQyMVQwMjAwMDANClJEQVRFOjE5MzAwNDEzVDAyMDAwMA0KUkRBVEU6MTkzMTA0MTlUMDIwMDAwDQpSREFURToxOTMyMDQwM1QwMjAwMDANClJEQVRFOjE5MzMwMzI2VDAyMDAwMA0KUkRBVEU6MTkzNDA0MDhUMDIwMDAwDQpSREFURToxOTM1MDMzMVQwMjAwMDANClJEQVRFOjE5MzYwNDE5VDAyMDAwMA0KUkRBVEU6MTkzNzA0MDRUMDIwMDAwDQpSREFURToxOTM4MDMyN1QwMjAwMDANClJEQVRFOjE5MzkwNDE2VDAyMDAwMA0KUkRBVEU6MTk0MDAyMjVUMDIwMDAwDQpFTkQ6REFZTElHSFQNCkJFR0lOOkRBWUxJR0hUDQpUWk9GRlNFVEZST006KzAyMDANClRaT0ZGU0VUVE86KzAyMDANClRaTkFNRTpDRVNUDQpEVFNUQVJUOjE5NDQwOTAzVDAwMDAwMA0KUkRBVEU6MTk0NDA5MDNUMDAwMDAwDQpFTkQ6REFZTElHSFQNCkJFR0lOOlNUQU5EQVJEDQpUWk9GRlNFVEZST006KzAxMDANClRaT0ZGU0VUVE86KzAxMDANClRaTkFNRTpDRVQNCkRUU1RBUlQ6MTk3NzAxMDFUMDAwMDAwDQpSREFURToxOTc3MDEwMVQwMDAwMDANCkVORDpTVEFOREFSRA0KRU5EOlZUSU1FWk9ORQ0KRU5EOlZDQUxFTkRBUg0K +--_000_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_-- + +--_004_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_ +Content-Type: application/ics; name="invite.ics" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="invite.ics" + +QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6Ly9ZYWhvby8vQ2FsZW5kYXIvL0VODQpWRVJTSU9OOjIuMA0KTUVUSE9EOlJFUVVFU1QNCkJFR0lOOlZFVkVOVA0KU1VNTUFSWTpUZXN0IGZyb20gWWFob28NCkNMQVNTOlBVQkxJQw0KRFRTVEFSVDtUWklEPUV1cm9wZS9CcnVzc2VsczoyMDE3MDEyN1QxNTAwMDANCkRURU5EO1RaSUQ9RXVyb3BlL0JydXNzZWxzOjIwMTcwMTI3VDE2MDAwMA0KTE9DQVRJT046U29tZXdoZXJlDQpQUklPUklUWTowDQpTRVFVRU5DRTowDQpTVEFUVVM6Q09ORklSTUVEDQpVSUQ6NTAxNDUxM2YtMTAyNi00YjU4LTgyY2YtODBkNGZjMDYwYmJlDQpEVFNUQU1QOjIwMTcwMTIzVDEyMTYzNVoNCkFUVEVOREVFO1BBUlRTVEFUPU5FRURTLUFDVElPTjtST0xFPVJFUV9QQVJUSUNJUEFOVDtSU1ZQPVRSVUU7U0NIRURVTEUtU1RBVA0KIFVTPTEuMTptYWlsdG86ZGRvbGNpbWFzY29sb0BsaW5hZ29yYS5jb20NCkFUVEVOREVFO1BBUlRTVEFUPU5FRURTLUFDVElPTjtST0xFPVJFUV9QQVJUSUNJUEFOVDtSU1ZQPVRSVUU7U0NIRURVTEUtU1RBVA0KIFVTPTEuMTptYWlsdG86cm91YXphbmFAbGluYWdvcmEuY29tDQpBVFRFTkRFRTtQQVJUU1RBVD1ORUVEUy1BQ1RJT047Uk9MRT1SRVFfUEFSVElDSVBBTlQ7UlNWUD1UUlVFO1NDSEVEVUxFLVNUQVQNCiBVUz0xLjE6bWFpbHRvOmFkdXByYXRAbGluYWdvcmEuY29tDQpBVFRFTkRFRTtQQVJUU1RBVD1ORUVEUy1BQ1RJT047Uk9MRT1SRVFfUEFSVElDSVBBT l + Q7UlNWUD1UUlVFO1NDSEVEVUxFLVNUQVQNCiBVUz0xLjE6bWFpbHRvOmJ0ZWxsaWVyQGxpbmFnb3JhLmNvbQ0KT1JHQU5JWkVSO0NOPU9CTSBMaW5hZ29yYTtTRU5ULUJZPSJtYWlsdG86b2JtbGluYWdvcmFAeWFob28uZnIiOm1haWx0bzpvYm1sDQogaW5hZ29yYUB5YWhvby5mcg0KWC1ZQUhPTy1ZSUQ6b2JtbGluYWdvcmENClRSQU5TUDpPUEFRVUUNClNUQVRVUzpDT05GSVJNRUQNClgtWUFIT08tVVNFUi1TVEFUVVM6QlVTWQ0KWC1ZQUhPTy1FVkVOVC1TVEFUVVM6QlVTWQ0KRU5EOlZFVkVOVA0KQkVHSU46VlRJTUVaT05FDQpUWklEOkV1cm9wZS9CcnVzc2Vscw0KVFpVUkw6aHR0cDovL3R6dXJsLm9yZy96b25laW5mby9FdXJvcGUvQnJ1c3NlbHMNClgtTElDLUxPQ0FUSU9OOkV1cm9wZS9CcnVzc2Vscw0KQkVHSU46REFZTElHSFQNClRaT0ZGU0VURlJPTTorMDEwMA0KVFpPRkZTRVRUTzorMDIwMA0KVFpOQU1FOkNFU1QNCkRUU1RBUlQ6MTk4MTAzMjlUMDIwMDAwDQpSUlVMRTpGUkVRPVlFQVJMWTtCWU1PTlRIPTM7QllEQVk9LTFTVQ0KRU5EOkRBWUxJR0hUDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9NOiswMjAwDQpUWk9GRlNFVFRPOiswMTAwDQpUWk5BTUU6Q0VUDQpEVFNUQVJUOjE5OTYxMDI3VDAzMDAwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT0tMVNVDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOlNUQU5EQVJEDQpUWk9GRlNFVEZST006KzAwMTczMA0KVFpPRkZTRVRUTzorMD A + xNzMwDQpUWk5BTUU6Qk1UDQpEVFNUQVJUOjE4ODAwMTAxVDAwMDAwMA0KUkRBVEU6MTg4MDAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOlNUQU5EQVJEDQpUWk9GRlNFVEZST006KzAwMTczMA0KVFpPRkZTRVRUTzorMDAwMA0KVFpOQU1FOldFVA0KRFRTVEFSVDoxODkyMDUwMVQxMjAwMDANClJEQVRFOjE4OTIwNTAxVDEyMDAwMA0KRU5EOlNUQU5EQVJEDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9NOiswMDAwDQpUWk9GRlNFVFRPOiswMTAwDQpUWk5BTUU6Q0VUDQpEVFNUQVJUOjE5MTQxMTA4VDAwMDAwMA0KUkRBVEU6MTkxNDExMDhUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOkRBWUxJR0hUDQpUWk9GRlNFVEZST006KzAxMDANClRaT0ZGU0VUVE86KzAyMDANClRaTkFNRTpDRVNUDQpEVFNUQVJUOjE5MTYwNTAxVDAwMDAwMA0KUkRBVEU6MTkxNjA1MDFUMDAwMDAwDQpSREFURToxOTE3MDQxNlQwMjAwMDANClJEQVRFOjE5MTgwNDE1VDAyMDAwMA0KUkRBVEU6MTk0MDA1MjBUMDMwMDAwDQpSREFURToxOTQzMDMyOVQwMjAwMDANClJEQVRFOjE5NDQwNDAzVDAyMDAwMA0KUkRBVEU6MTk0NTA0MDJUMDIwMDAwDQpSREFURToxOTQ2MDUxOVQwMjAwMDANClJEQVRFOjE5NzcwNDAzVDAyMDAwMA0KUkRBVEU6MTk3ODA0MDJUMDIwMDAwDQpSREFURToxOTc5MDQwMVQwMjAwMDANClJEQVRFOjE5ODAwNDA2VDAyMDAwMA0KRU5EOkRBWUxJR0hUDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9 N + OiswMjAwDQpUWk9GRlNFVFRPOiswMTAwDQpUWk5BTUU6Q0VUDQpEVFNUQVJUOjE5MTYxMDAxVDAxMDAwMA0KUkRBVEU6MTkxNjEwMDFUMDEwMDAwDQpSREFURToxOTE3MDkxN1QwMzAwMDANClJEQVRFOjE5MTgwOTE2VDAzMDAwMA0KUkRBVEU6MTk0MjExMDJUMDMwMDAwDQpSREFURToxOTQzMTAwNFQwMzAwMDANClJEQVRFOjE5NDQwOTE3VDAzMDAwMA0KUkRBVEU6MTk0NTA5MTZUMDMwMDAwDQpSREFURToxOTQ2MTAwN1QwMzAwMDANClJEQVRFOjE5NzcwOTI1VDAzMDAwMA0KUkRBVEU6MTk3ODEwMDFUMDMwMDAwDQpSREFURToxOTc5MDkzMFQwMzAwMDANClJEQVRFOjE5ODAwOTI4VDAzMDAwMA0KUkRBVEU6MTk4MTA5MjdUMDMwMDAwDQpSREFURToxOTgyMDkyNlQwMzAwMDANClJEQVRFOjE5ODMwOTI1VDAzMDAwMA0KUkRBVEU6MTk4NDA5MzBUMDMwMDAwDQpSREFURToxOTg1MDkyOVQwMzAwMDANClJEQVRFOjE5ODYwOTI4VDAzMDAwMA0KUkRBVEU6MTk4NzA5MjdUMDMwMDAwDQpSREFURToxOTg4MDkyNVQwMzAwMDANClJEQVRFOjE5ODkwOTI0VDAzMDAwMA0KUkRBVEU6MTk5MDA5MzBUMDMwMDAwDQpSREFURToxOTkxMDkyOVQwMzAwMDANClJEQVRFOjE5OTIwOTI3VDAzMDAwMA0KUkRBVEU6MTk5MzA5MjZUMDMwMDAwDQpSREFURToxOTk0MDkyNVQwMzAwMDANClJEQVRFOjE5OTUwOTI0VDAzMDAwMA0KRU5EOlNUQU5EQVJEDQpCRUdJTjpTVEFOREFSRA0KVFpPRkZTRVRGUk9NOiswMTAwDQpUWk9GRlNFVFRPOisw M + DAwDQpUWk5BTUU6V0VUDQpEVFNUQVJUOjE5MTgxMTExVDEyMDAwMA0KUkRBVEU6MTkxODExMTFUMTIwMDAwDQpSREFURToxOTE5MTAwNVQwMDAwMDANClJEQVRFOjE5MjAxMDI0VDAwMDAwMA0KUkRBVEU6MTkyMTEwMjZUMDAwMDAwDQpSREFURToxOTIyMTAwOFQwMDAwMDANClJEQVRFOjE5MjMxMDA3VDAwMDAwMA0KUkRBVEU6MTkyNDEwMDVUMDAwMDAwDQpSREFURToxOTI1MTAwNFQwMDAwMDANClJEQVRFOjE5MjYxMDAzVDAwMDAwMA0KUkRBVEU6MTkyNzEwMDJUMDAwMDAwDQpSREFURToxOTI4MTAwN1QwMzAwMDANClJEQVRFOjE5MjkxMDA2VDAzMDAwMA0KUkRBVEU6MTkzMDEwMDVUMDMwMDAwDQpSREFURToxOTMxMTAwNFQwMzAwMDANClJEQVRFOjE5MzIxMDAyVDAzMDAwMA0KUkRBVEU6MTkzMzEwMDhUMDMwMDAwDQpSREFURToxOTM0MTAwN1QwMzAwMDANClJEQVRFOjE5MzUxMDA2VDAzMDAwMA0KUkRBVEU6MTkzNjEwMDRUMDMwMDAwDQpSREFURToxOTM3MTAwM1QwMzAwMDANClJEQVRFOjE5MzgxMDAyVDAzMDAwMA0KUkRBVEU6MTkzOTExMTlUMDMwMDAwDQpFTkQ6U1RBTkRBUkQNCkJFR0lOOkRBWUxJR0hUDQpUWk9GRlNFVEZST006KzAwMDANClRaT0ZGU0VUVE86KzAxMDANClRaTkFNRTpXRVNUDQpEVFNUQVJUOjE5MTkwMzAxVDIzMDAwMA0KUkRBVEU6MTkxOTAzMDFUMjMwMDAwDQpSREFURToxOTIwMDIxNFQyMzAwMDANClJEQVRFOjE5MjEwMzE0VDIzMDAwMA0KUkRBVEU6MTkyMjAzMjVUMjMwMDAwDQpSR E + FURToxOTIzMDQyMVQyMzAwMDANClJEQVRFOjE5MjQwMzI5VDIzMDAwMA0KUkRBVEU6MTkyNTA0MDRUMjMwMDAwDQpSREFURToxOTI2MDQxN1QyMzAwMDANClJEQVRFOjE5MjcwNDA5VDIzMDAwMA0KUkRBVEU6MTkyODA0MTRUMjMwMDAwDQpSREFURToxOTI5MDQyMVQwMjAwMDANClJEQVRFOjE5MzAwNDEzVDAyMDAwMA0KUkRBVEU6MTkzMTA0MTlUMDIwMDAwDQpSREFURToxOTMyMDQwM1QwMjAwMDANClJEQVRFOjE5MzMwMzI2VDAyMDAwMA0KUkRBVEU6MTkzNDA0MDhUMDIwMDAwDQpSREFURToxOTM1MDMzMVQwMjAwMDANClJEQVRFOjE5MzYwNDE5VDAyMDAwMA0KUkRBVEU6MTkzNzA0MDRUMDIwMDAwDQpSREFURToxOTM4MDMyN1QwMjAwMDANClJEQVRFOjE5MzkwNDE2VDAyMDAwMA0KUkRBVEU6MTk0MDAyMjVUMDIwMDAwDQpFTkQ6REFZTElHSFQNCkJFR0lOOkRBWUxJR0hUDQpUWk9GRlNFVEZST006KzAyMDANClRaT0ZGU0VUVE86KzAyMDANClRaTkFNRTpDRVNUDQpEVFNUQVJUOjE5NDQwOTAzVDAwMDAwMA0KUkRBVEU6MTk0NDA5MDNUMDAwMDAwDQpFTkQ6REFZTElHSFQNCkJFR0lOOlNUQU5EQVJEDQpUWk9GRlNFVEZST006KzAxMDANClRaT0ZGU0VUVE86KzAxMDANClRaTkFNRTpDRVQNCkRUU1RBUlQ6MTk3NzAxMDFUMDAwMDAwDQpSREFURToxOTc3MDEwMVQwMDAwMDANCkVORDpTVEFOREFSRA0KRU5EOlZUSU1FWk9ORQ0KRU5EOlZDQUxFTkRBUg0K +--_004_CD15A92E9002734AADB6F2075789C4D70108100CB8EDEGLEX07VS01_-- --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org