diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 3c47ae57..89840ee8 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -2316,7 +2316,7 @@ public abstract class ExchangeSession { } - protected static final List ITEM_PROPERTIES = new ArrayList(); + public static final List ITEM_PROPERTIES = new ArrayList(); static { ITEM_PROPERTIES.add("etag"); @@ -3110,9 +3110,9 @@ public abstract class ExchangeSession { } - protected VTimezone vTimezone; + public VTimezone vTimezone; - protected VTimezone getVTimezone() { + public VTimezone getVTimezone() { if (vTimezone == null) { // need to load Timezone info from OWA loadVtimezone(); diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java index 27c07a86..2e616951 100644 --- a/src/java/davmail/exchange/dav/DavExchangeSession.java +++ b/src/java/davmail/exchange/dav/DavExchangeSession.java @@ -1097,6 +1097,19 @@ public class DavExchangeSession extends ExchangeSession { } } + protected byte[] getBinaryPropertyIfExists(DavPropertySet properties, String alias) { + byte[] property = null; + String base64Property = getPropertyIfExists(properties, alias); + if (base64Property != null) { + try { + property = Base64.decodeBase64(base64Property.getBytes("ASCII")); + } catch (UnsupportedEncodingException e) { + LOGGER.warn(e); + } + } + return property; + } + protected Message buildMessage(MultiStatusResponse responseEntity) throws URIException { Message message = new Message(); @@ -1135,7 +1148,7 @@ public class DavExchangeSession extends ExchangeSession { @Override public MessageList searchMessages(String folderPath, List attributes, Condition condition) throws IOException { MessageList messages = new MessageList(); - MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"),condition), FolderQueryTraversal.Shallow); + MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"), condition), FolderQueryTraversal.Shallow); for (MultiStatusResponse response : responses) { Message message = buildMessage(response); @@ -1152,7 +1165,7 @@ public class DavExchangeSession extends ExchangeSession { @Override protected List searchContacts(String folderPath, List attributes, Condition condition) throws IOException { List contacts = new ArrayList(); - MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"),condition), FolderQueryTraversal.Shallow); + MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"), condition), FolderQueryTraversal.Shallow); for (MultiStatusResponse response : responses) { contacts.add(new Contact(response)); } @@ -1162,7 +1175,7 @@ public class DavExchangeSession extends ExchangeSession { @Override protected List searchEvents(String folderPath, List attributes, Condition condition) throws IOException { List events = new ArrayList(); - MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"),condition), FolderQueryTraversal.Shallow); + MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"), condition), FolderQueryTraversal.Shallow); for (MultiStatusResponse response : responses) { String instancetype = getPropertyIfExists(response.getProperties(HttpStatus.SC_OK), "instancetype"); Event event = new Event(response); @@ -1330,8 +1343,14 @@ public class DavExchangeSession extends ExchangeSession { propertyList.add(Field.createDavProperty("contentclass", "urn:content-classes:appointment")); propertyList.add(Field.createDavProperty("outlookmessageclass", "IPM.Appointment")); propertyList.add(Field.createDavProperty("instancetype", "0")); + // get forced timezone id from settings userTimezone.timezoneId = Settings.getProperty("davmail.timezoneId"); + if (userTimezone.timezoneId == null) { + // get timezoneid from OWA settings + userTimezone.timezoneId = getTimezoneIdFromExchange(); + } + // without a timezoneId, use Exchange timezone if (userTimezone.timezoneId != null) { propertyList.add(Field.createDavProperty("timezoneid", userTimezone.timezoneId)); } @@ -1369,6 +1388,40 @@ public class DavExchangeSession extends ExchangeSession { } } + protected String getTimezoneIdFromExchange() { + String timezoneId = null; + + try { + ArrayList attributes = new ArrayList(); + attributes.add("roamingdictionary"); + + MultiStatusResponse[] responses = searchItems("/users/" + getEmail() + "/NON_IPM_SUBTREE", attributes, equals("messageclass", "IPM.Configuration.OWA.UserOptions"), DavExchangeSession.FolderQueryTraversal.Deep); + if (responses.length == 1) { + byte[] roamingdictionary = getBinaryPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "roamingdictionary"); + if (roamingdictionary != null) { + String roamingdictionaryString = new String(roamingdictionary, "UTF-8"); + int startIndex = roamingdictionaryString.lastIndexOf("18-"); + if (startIndex >= 0) { + int endIndex = roamingdictionaryString.indexOf('"', startIndex); + if (endIndex >= 0) { + String timezoneName = roamingdictionaryString.substring(startIndex + 3, endIndex); + try { + timezoneId = ResourceBundle.getBundle("timezoneids").getString(timezoneName); + } catch (MissingResourceException mre) { + LOGGER.warn("Invalid timezone name: " + timezoneName); + } + } + } + } + } + } catch (UnsupportedEncodingException e) { + LOGGER.warn("Unable to retrieve Exchange timezone id: " + e.getMessage(), e); + } catch (IOException e) { + LOGGER.warn("Unable to retrieve Exchange timezone id: " + e.getMessage(), e); + } + return timezoneId; + } + @Override protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException { return new Contact(messageUrl, contentClass, icsBody, etag, noneMatch).createOrUpdate(); diff --git a/src/java/timezoneids.properties b/src/java/timezoneids.properties new file mode 100644 index 00000000..8a54d6b8 --- /dev/null +++ b/src/java/timezoneids.properties @@ -0,0 +1,102 @@ +# Timezone name to id table +Morocco\ Standard\ Time=88 +GMT\ Standard\ Time=1 +Greenwich\ Standard\ Time=31 +W.\ Europe\ Standard\ Time=4 +Romance\ Standard\ Time=3 +Central\ Europe\ Standard\ Time=6 +Central\ European\ Standard\ Time=2 +W.\ Central\ Africa\ Standard\ Time=69 +Jordan\ Standard\ Time=83 +GTB\ Standard\ Time=7 +Egypt\ Standard\ Time=49 +South\ Africa\ Standard\ Time=50 +FLE\ Standard\ Time=59 +Israel\ Standard\ Time=27 +E.\ Europe\ Standard\ Time=5 +Arabic\ Standard\ Time=26 +E.\ Africa\ Standard\ Time=56 +Arab\ Standard\ Time=74 +Russian\ Standard\ Time=51 +Iran\ Standard\ Time=25 +Arabian\ Standard\ Time=24 +Azerbaijan\ Standard\ Time=84 +Caucasus\ Standard\ Time=54 +Mauritius\ Standard\ Time=91 +Georgian\ Standard\ Time=86 +Armenian\ Standard\ Time=85 +Afghanistan\ Standard\ Time=48 +Ekaterinburg\ Standard\ Time=58 +Pakistan\ Standard\ Time=89 +West\ Asia\ Standard\ Time=47 +India\ Standard\ Time=23 +Sri\ Lanka\ Standard\ Time=66 +Nepal\ Standard\ Time=62 +N.\ Central\ Asia\ Standard\ Time=46 +Central\ Asia\ Standard\ Time=71 +Myanmar\ Standard\ Time=61 +SE\ Asia\ Standard\ Time=22 +North\ Asia\ Standard\ Time=64 +China\ Standard\ Time=45 +North\ Asia\ East\ Standard\ Time=63 +Singapore\ Standard\ Time=21 +W.\ Australia\ Standard\ Time=73 +Taipei\ Standard\ Time=75 +Korea\ Standard\ Time=72 +Tokyo\ Standard\ Time=20 +Yakutsk\ Standard\ Time=70 +Cen.\ Australia\ Standard\ Time=19 +AUS\ Central\ Standard\ Time=44 +E.\ Australia\ Standard\ Time=18 +AUS\ Eastern\ Standard\ Time=78 +West\ Pacific\ Standard\ Time=43 +Tasmania\ Standard\ Time=42 +Vladivostok\ Standard\ Time=68 +Central\ Pacific\ Standard\ Time=41 +Fiji\ Standard\ Time=40 +New\ Zealand\ Standard\ Time=17 +Tonga\ Standard\ Time=67 +Azores\ Standard\ Time=29 +Cape\ Verde\ Standard\ Time=53 +Mid-Atlantic\ Standard\ Time=30 +E.\ South\ America\ Standard\ Time=8 +Argentina\ Standard\ Time=87 +SA\ Eastern\ Standard\ Time=32 +Greenland\ Standard\ Time=60 +Montevideo\ Standard\ Time=92 +Newfoundland\ Standard\ Time=28 +Atlantic\ Standard\ Time=9 +SA\ Western\ Standard\ Time=33 +Central\ Brazilian\ Standard\ Time=90 +Pacific\ SA\ Standard\ Time=65 +Venezuela\ Standard\ Time=82 +SA\ Pacific\ Standard\ Time=35 +Eastern\ Standard\ Time=10 +US\ Eastern\ Standard\ Time=34 +Central\ America\ Standard\ Time=55 +Central\ Standard\ Time=11 +Central\ Standard\ Time\ (Mexico)=37 +Canada\ Central\ Standard\ Time=36 +US\ Mountain\ Standard\ Time=38 +Mountain\ Standard\ Time\ (Mexico)=77 +Mountain\ Standard\ Time=12 +Pacific\ Standard\ Time=13 +Pacific\ Standard\ Time\ (Mexico)=81 +Alaskan\ Standard\ Time=14 +Hawaiian\ Standard\ Time=15 +Samoa\ Standard\ Time=16 +Dateline\ Standard\ Time=39 +# Additional +UTC=31 +Middle\ East\ Standard\ Time=7 +Bangladesh\ Standard\ Time=71 +Ulaanbaatar\ Standard\ Time=63 +Kamchatka\ Standard\ Time=40 +UTC-02=30 +Paraguay\ Standard\ Time=65 +Mexico\ Standard\ Time=37 +Mexico\ Standard\ Time\ 2=77 +UTC-11=16 +# Unknown +#UTC+12=N/A +#Namibia\ Standard\ Time=N/A diff --git a/src/test/davmail/exchange/dav/TestDavExchangeSession.java b/src/test/davmail/exchange/dav/TestDavExchangeSession.java index 680c158e..2cbdf90a 100644 --- a/src/test/davmail/exchange/dav/TestDavExchangeSession.java +++ b/src/test/davmail/exchange/dav/TestDavExchangeSession.java @@ -18,13 +18,25 @@ */ package davmail.exchange.dav; +import davmail.BundleMessage; +import davmail.Settings; import davmail.exchange.AbstractExchangeSessionTestCase; +import davmail.exchange.ExchangeSession; +import davmail.ui.tray.DavGatewayTray; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.property.DavProperty; +import java.io.FileOutputStream; import java.io.IOException; +import java.util.*; /** * Webdav specific unit tests */ +@SuppressWarnings({"UseOfSystemOutOrSystemErr"}) public class TestDavExchangeSession extends AbstractExchangeSessionTestCase { DavExchangeSession davSession; @@ -52,37 +64,168 @@ public class TestDavExchangeSession extends AbstractExchangeSessionTestCase { assertEquals("/public/test", davSession.getFolderPath("/public/test")); // caldav folder paths - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getEmail())); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getEmail()+ '/')); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getAlias())); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getAlias()+ '/')); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getEmail())); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getEmail() + '/')); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getAlias())); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getAlias() + '/')); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getEmail().toUpperCase())); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getEmail().toLowerCase())); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getAlias().toUpperCase())); - assertEquals(mailPath, davSession.getFolderPath("/users/"+davSession.getAlias().toLowerCase())); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getEmail().toUpperCase())); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getEmail().toLowerCase())); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getAlias().toUpperCase())); + assertEquals(mailPath, davSession.getFolderPath("/users/" + davSession.getAlias().toLowerCase())); - assertEquals(mailPath+"subfolder", davSession.getFolderPath("/users/"+davSession.getAlias()+ "/subfolder")); - assertEquals(mailPath+"subfolder/", davSession.getFolderPath("/users/"+davSession.getAlias()+ "/subfolder/")); + assertEquals(mailPath + "subfolder", davSession.getFolderPath("/users/" + davSession.getAlias() + "/subfolder")); + assertEquals(mailPath + "subfolder/", davSession.getFolderPath("/users/" + davSession.getAlias() + "/subfolder/")); - assertEquals(rootPath+"anotheruser/", davSession.getFolderPath("/users/anotheruser")); - assertEquals(rootPath+"anotheruser/subfolder", davSession.getFolderPath("/users/anotheruser/subfolder")); + assertEquals(rootPath + "anotheruser/", davSession.getFolderPath("/users/anotheruser")); + assertEquals(rootPath + "anotheruser/subfolder", davSession.getFolderPath("/users/anotheruser/subfolder")); - assertEquals(mailPath+davSession.inboxName, davSession.getFolderPath("/users/"+davSession.getEmail()+"/inbox")); - assertEquals(mailPath+davSession.inboxName+"/subfolder", davSession.getFolderPath("/users/"+davSession.getEmail()+"/inbox/subfolder")); + assertEquals(mailPath + davSession.inboxName, davSession.getFolderPath("/users/" + davSession.getEmail() + "/inbox")); + assertEquals(mailPath + davSession.inboxName + "/subfolder", davSession.getFolderPath("/users/" + davSession.getEmail() + "/inbox/subfolder")); - assertEquals(mailPath+davSession.calendarName, davSession.getFolderPath("/users/"+davSession.getEmail()+"/calendar")); - assertEquals(mailPath+davSession.contactsName, davSession.getFolderPath("/users/"+davSession.getEmail()+"/contacts")); - assertEquals(mailPath+davSession.contactsName, davSession.getFolderPath("/users/"+davSession.getEmail()+"/addressbook")); + assertEquals(mailPath + davSession.calendarName, davSession.getFolderPath("/users/" + davSession.getEmail() + "/calendar")); + assertEquals(mailPath + davSession.contactsName, davSession.getFolderPath("/users/" + davSession.getEmail() + "/contacts")); + assertEquals(mailPath + davSession.contactsName, davSession.getFolderPath("/users/" + davSession.getEmail() + "/addressbook")); - assertEquals(rootPath+"anotherUser/"+davSession.inboxName, davSession.getFolderPath("/users/anotherUser/inbox")); - assertEquals(rootPath+"anotherUser/"+davSession.calendarName, davSession.getFolderPath("/users/anotherUser/calendar")); - assertEquals(rootPath+"anotherUser/"+davSession.contactsName, davSession.getFolderPath("/users/anotherUser/contacts")); + assertEquals(rootPath + "anotherUser/" + davSession.inboxName, davSession.getFolderPath("/users/anotherUser/inbox")); + assertEquals(rootPath + "anotherUser/" + davSession.calendarName, davSession.getFolderPath("/users/anotherUser/calendar")); + assertEquals(rootPath + "anotherUser/" + davSession.contactsName, davSession.getFolderPath("/users/anotherUser/contacts")); // do not replace i18n names - assertEquals(mailPath+"Inbox", davSession.getFolderPath("/users/"+davSession.getEmail()+"/Inbox")); - assertEquals(mailPath+"Calendar", davSession.getFolderPath("/users/"+davSession.getEmail()+"/Calendar")); - assertEquals(mailPath+"Contacts", davSession.getFolderPath("/users/"+davSession.getEmail()+"/Contacts")); + assertEquals(mailPath + "Inbox", davSession.getFolderPath("/users/" + davSession.getEmail() + "/Inbox")); + assertEquals(mailPath + "Calendar", davSession.getFolderPath("/users/" + davSession.getEmail() + "/Calendar")); + assertEquals(mailPath + "Contacts", davSession.getFolderPath("/users/" + davSession.getEmail() + "/Contacts")); + } + + public void testGetCategoryList() throws IOException { + ArrayList attributes = new ArrayList(); + attributes.add("permanenturl"); + attributes.add("roamingxmlstream"); + MultiStatusResponse[] responses = davSession.searchItems("/users/" + davSession.getEmail() + "/calendar", attributes, davSession.and(davSession.isFalse("isfolder"), davSession.equals("messageclass", "IPM.Configuration.CategoryList")), DavExchangeSession.FolderQueryTraversal.Shallow); + String value = (String) responses[0].getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingxmlstream")).getValue(); + String propertyList = new String(Base64.decodeBase64(value.getBytes()), "UTF-8"); + + } + + public void testGetCalendarOptions() throws IOException { + ArrayList attributes = new ArrayList(); + attributes.add("permanenturl"); + attributes.add("roamingxmlstream"); + MultiStatusResponse[] responses = davSession.searchItems("/users/" + davSession.getEmail() + "/calendar", attributes, davSession.and(davSession.isFalse("isfolder"), davSession.equals("messageclass", "IPM.Configuration.Calendar")), DavExchangeSession.FolderQueryTraversal.Shallow); + String value = (String) responses[0].getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingxmlstream")).getValue(); + String propertyList = new String(Base64.decodeBase64(value.getBytes()), "UTF-8"); + + } + + public void testAllHidden() throws IOException { + ArrayList attributes = new ArrayList(); + attributes.add("messageclass"); + attributes.add("permanenturl"); + attributes.add("roamingxmlstream"); + attributes.add("displayname"); + + MultiStatusResponse[] responses = davSession.searchItems("/users/" + davSession.getEmail() + "/", attributes, davSession.and(davSession.isTrue("ishidden")), DavExchangeSession.FolderQueryTraversal.Deep); + for (MultiStatusResponse response : responses) { + System.out.println(response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("messageclass")).getValue() + ": " + + response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("displayname")).getValue()); + + DavProperty roamingxmlstreamProperty = response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingxmlstream")); + if (roamingxmlstreamProperty != null) { + System.out.println(new String(Base64.decodeBase64(((String) roamingxmlstreamProperty.getValue()).getBytes()), "UTF-8")); + } + + } + } + + public void testNonIpmSubtree() throws IOException { + ArrayList attributes = new ArrayList(); + attributes.add("messageclass"); + attributes.add("permanenturl"); + attributes.add("roamingxmlstream"); + attributes.add("roamingdictionary"); + attributes.add("displayname"); + + MultiStatusResponse[] responses = davSession.searchItems("/users/" + davSession.getEmail() + "/non_ipm_subtree", attributes, davSession.and(davSession.isTrue("ishidden")), DavExchangeSession.FolderQueryTraversal.Deep); + for (MultiStatusResponse response : responses) { + System.out.println(response.getHref() + ' ' + response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("messageclass")).getValue() + ": " + + response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("displayname")).getValue()); + + DavProperty roamingxmlstreamProperty = response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingxmlstream")); + if (roamingxmlstreamProperty != null) { + System.out.println("roamingxmlstream: " + new String(Base64.decodeBase64(((String) roamingxmlstreamProperty.getValue()).getBytes()), "UTF-8")); + } + + DavProperty roamingdictionaryProperty = response.getProperties(HttpStatus.SC_OK).get(Field.getPropertyName("roamingdictionary")); + if (roamingdictionaryProperty != null) { + System.out.println("roamingdictionary: " + new String(Base64.decodeBase64(((String) roamingdictionaryProperty.getValue()).getBytes()), "UTF-8")); + } + + + } + } + + public void testGetVtimezone() { + ExchangeSession.VTimezone timezone = davSession.getVTimezone(); + assertNotNull(timezone.timezoneId); + assertNotNull(timezone.timezoneBody); + System.out.println(timezone.timezoneId); + System.out.println(timezone.timezoneBody); + } + + public void testDumpVtimezones() { + Properties properties = new Properties() { + @Override + public synchronized Enumeration keys() { + Enumeration keysEnumeration = super.keys(); + TreeSet sortedKeySet = new TreeSet(); + while (keysEnumeration.hasMoreElements()) { + sortedKeySet.add((String) keysEnumeration.nextElement()); + } + final Iterator sortedKeysIterator = sortedKeySet.iterator(); + return new Enumeration() { + + public boolean hasMoreElements() { + return sortedKeysIterator.hasNext(); + } + + public Object nextElement() { + return sortedKeysIterator.next(); + } + }; + } + + }; + for (int i = 1; i < 100; i++) { + Settings.setProperty("davmail.timezoneId", String.valueOf(i)); + ExchangeSession.VTimezone timezone = davSession.getVTimezone(); + if (timezone.timezoneId != null) { + properties.put(timezone.timezoneId.replaceAll("\\\\", ""), String.valueOf(i)); + System.out.println(timezone.timezoneId + '=' + i); + } + davSession.vTimezone = null; + } + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = new FileOutputStream("timezoneids.properties"); + properties.store(fileOutputStream, "Timezone ids"); + } catch (IOException e) { + System.err.println(e); + } finally { + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException e) { + System.err.println(e); + } + } + } + } + + public void testSearchCalendar() throws IOException { + List events = davSession.searchEvents("/users/" + davSession.getEmail() + "/calendar/test", ExchangeSession.ITEM_PROPERTIES, null); + for (ExchangeSession.Event event:events) { + System.out.println(event.getBody()); + } } }